diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index e1889b269e6ae..4cf67defc9f94 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -5635,19 +5635,29 @@ private MethodGroupResolution BindExtensionMethod( diagnostics.Free(); continue; } - + RefOmitMode refOmitMode = RefOmitMode.None; if (actualArguments == null) { // Create a set of arguments for overload resolution of the // extension methods that includes the "this" parameter. actualArguments = AnalyzedArguments.GetInstance(); + refOmitMode = RefOmitMode.ExtensionMethod; CombineExtensionMethodArguments(left, analyzedArguments, actualArguments); } - var overloadResolutionResult = OverloadResolutionResult.GetInstance(); - bool allowRefOmittedArguments = methodGroup.Receiver.IsExpressionOfComImportType(); + var overloadResolutionResult = OverloadResolutionResult.GetInstance() ; + if (methodGroup.Receiver.IsExpressionOfComImportType()) + { + refOmitMode = RefOmitMode.All; + } HashSet useSiteDiagnostics = null; - OverloadResolution.MethodInvocationOverloadResolution(methodGroup.Methods, methodGroup.TypeArguments, actualArguments, overloadResolutionResult, ref useSiteDiagnostics, isMethodGroupConversion, allowRefOmittedArguments); + OverloadResolution.MethodInvocationOverloadResolution(methodGroup.Methods, + methodGroup.TypeArguments, + actualArguments, + overloadResolutionResult, + ref useSiteDiagnostics, + isMethodGroupConversion, + refOmitMode); diagnostics.Add(expression, useSiteDiagnostics); var sealedDiagnostics = diagnostics.ToReadOnlyAndFree(); var result = new MethodGroupResolution(methodGroup, null, overloadResolutionResult, actualArguments, methodGroup.ResultKind, sealedDiagnostics); @@ -6778,7 +6788,7 @@ private MethodGroupResolution ResolveDefaultMethodGroup( bool allowRefOmittedArguments = methodGroup.Receiver.IsExpressionOfComImportType(); OverloadResolution.MethodInvocationOverloadResolution( methodGroup.Methods, methodGroup.TypeArguments, analyzedArguments, - result, ref useSiteDiagnostics, isMethodGroupConversion, allowRefOmittedArguments, + result, ref useSiteDiagnostics, isMethodGroupConversion, allowRefOmittedArguments ? RefOmitMode.All : RefOmitMode.None, inferWithDynamic: inferWithDynamic, allowUnexpandedForm: allowUnexpandedForm); return new MethodGroupResolution(methodGroup, null, result, analyzedArguments, methodGroup.ResultKind, sealedDiagnostics); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index a23495d573511..d0f481bfc06e7 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -926,6 +926,14 @@ private BoundCall BindInvocationExpressionContinued( args = analyzedArguments.Arguments.ToImmutable(); } + // Verify that receiver is accessible if by-ref extension method + if (invokedAsExtensionMethod && + !method.ParameterRefKinds.IsDefaultOrEmpty && + method.ParameterRefKinds[0] == RefKind.Ref) + { + CheckIsVariable(expression, args[0], BindValueKind.RefOrOut, false, diagnostics); + } + // This will be the receiver of the BoundCall node that we create. // For extension methods, there is no receiver because the receiver in source was actually the first argument. // For instance methods, we may have synthesized an implicit this node. We'll keep it for the emitter. diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs index cb9366e44cb9a..02577af6e99ac 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MemberAnalysisResult.cs @@ -22,7 +22,7 @@ internal struct MemberAnalysisResult /// Omit ref feature for COM interop: We can pass arguments by value for ref parameters if we are invoking a method/property on an instance of a COM imported type. /// This property returns a flag indicating whether we had any ref omitted argument for the given call. /// - public readonly bool HasAnyRefOmittedArgument; + public readonly bool HasAnyComRefOmittedArgument; private MemberAnalysisResult(MemberResolutionKind kind) : this(kind, default(ImmutableArray), default(ImmutableArray), default(ImmutableArray)) @@ -35,14 +35,14 @@ private MemberAnalysisResult( ImmutableArray argsToParamsOpt, ImmutableArray conversionsOpt, int missingParameter = -1, - bool hasAnyRefOmittedArgument = false) + bool hasAnyComRefOmittedArgument = false) { this.Kind = kind; this.BadArgumentsOpt = badArgumentsOpt; this.ArgsToParamsOpt = argsToParamsOpt; this.ConversionsOpt = conversionsOpt; this.BadParameter = missingParameter; - this.HasAnyRefOmittedArgument = hasAnyRefOmittedArgument; + this.HasAnyComRefOmittedArgument = hasAnyComRefOmittedArgument; } public override bool Equals(object obj) @@ -246,14 +246,14 @@ public static MemberAnalysisResult LessDerived() return new MemberAnalysisResult(MemberResolutionKind.LessDerived); } - public static MemberAnalysisResult NormalForm(ImmutableArray argsToParamsOpt, ImmutableArray conversions, bool hasAnyRefOmittedArgument) + public static MemberAnalysisResult NormalForm(ImmutableArray argsToParamsOpt, ImmutableArray conversions, bool hasAnyComRefOmittedArgument) { - return new MemberAnalysisResult(MemberResolutionKind.ApplicableInNormalForm, default(ImmutableArray), argsToParamsOpt, conversions, hasAnyRefOmittedArgument: hasAnyRefOmittedArgument); + return new MemberAnalysisResult(MemberResolutionKind.ApplicableInNormalForm, default(ImmutableArray), argsToParamsOpt, conversions, hasAnyComRefOmittedArgument: hasAnyComRefOmittedArgument); } - public static MemberAnalysisResult ExpandedForm(ImmutableArray argsToParamsOpt, ImmutableArray conversions, bool hasAnyRefOmittedArgument) + public static MemberAnalysisResult ExpandedForm(ImmutableArray argsToParamsOpt, ImmutableArray conversions, bool hasAnyComRefOmittedArgument) { - return new MemberAnalysisResult(MemberResolutionKind.ApplicableInExpandedForm, default(ImmutableArray), argsToParamsOpt, conversions, hasAnyRefOmittedArgument: hasAnyRefOmittedArgument); + return new MemberAnalysisResult(MemberResolutionKind.ApplicableInExpandedForm, default(ImmutableArray), argsToParamsOpt, conversions, hasAnyComRefOmittedArgument: hasAnyComRefOmittedArgument); } public static MemberAnalysisResult Worse() diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index d424c113a8779..95911cc79d380 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -17,6 +17,13 @@ internal enum BetterResult Equal } + internal enum RefOmitMode + { + None, + All, + ExtensionMethod + } + internal sealed partial class OverloadResolution { private readonly Binder _binder; @@ -94,13 +101,13 @@ public void MethodInvocationOverloadResolution( OverloadResolutionResult result, ref HashSet useSiteDiagnostics, bool isMethodGroupConversion = false, - bool allowRefOmittedArguments = false, + RefOmitMode refOmitMode = RefOmitMode.None, bool inferWithDynamic = false, bool allowUnexpandedForm = true) { MethodOrPropertyOverloadResolution( methods, typeArguments, arguments, result, isMethodGroupConversion, - allowRefOmittedArguments, ref useSiteDiagnostics, inferWithDynamic: inferWithDynamic, + refOmitMode, ref useSiteDiagnostics, inferWithDynamic: inferWithDynamic, allowUnexpandedForm: allowUnexpandedForm); } @@ -114,7 +121,9 @@ public void PropertyOverloadResolution( ref HashSet useSiteDiagnostics) { ArrayBuilder typeArguments = ArrayBuilder.GetInstance(); - MethodOrPropertyOverloadResolution(indexers, typeArguments, arguments, result, isMethodGroupConversion: false, allowRefOmittedArguments: allowRefOmittedArguments, useSiteDiagnostics: ref useSiteDiagnostics); + MethodOrPropertyOverloadResolution(indexers, typeArguments, arguments, result, isMethodGroupConversion: false, + refOmitMode: allowRefOmittedArguments ? RefOmitMode.All : RefOmitMode.None, + useSiteDiagnostics: ref useSiteDiagnostics); typeArguments.Free(); } @@ -124,7 +133,7 @@ internal void MethodOrPropertyOverloadResolution( AnalyzedArguments arguments, OverloadResolutionResult result, bool isMethodGroupConversion, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, ref HashSet useSiteDiagnostics, bool inferWithDynamic = false, bool allowUnexpandedForm = true) @@ -135,7 +144,7 @@ internal void MethodOrPropertyOverloadResolution( // First, attempt overload resolution not getting complete results. PerformMemberOverloadResolution( results, members, typeArguments, arguments, false, isMethodGroupConversion, - allowRefOmittedArguments, ref useSiteDiagnostics, inferWithDynamic: inferWithDynamic, + refOmitMode, ref useSiteDiagnostics, inferWithDynamic: inferWithDynamic, allowUnexpandedForm: allowUnexpandedForm); if (!OverloadResolutionResultIsValid(results, arguments.HasDynamicArgument)) @@ -143,7 +152,7 @@ internal void MethodOrPropertyOverloadResolution( // We didn't get a single good result. Get full results of overload resolution and return those. result.Clear(); PerformMemberOverloadResolution(results, members, typeArguments, arguments, true, isMethodGroupConversion, - allowRefOmittedArguments, ref useSiteDiagnostics, allowUnexpandedForm: allowUnexpandedForm); + refOmitMode, ref useSiteDiagnostics, allowUnexpandedForm: allowUnexpandedForm); } } @@ -203,7 +212,7 @@ private void PerformMemberOverloadResolution( AnalyzedArguments arguments, bool completeResults, bool isMethodGroupConversion, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, ref HashSet useSiteDiagnostics, bool inferWithDynamic = false, bool allowUnexpandedForm = true) @@ -232,7 +241,7 @@ private void PerformMemberOverloadResolution( { AddMemberToCandidateSet( members[i], results, members, typeArguments, arguments, completeResults, - isMethodGroupConversion, allowRefOmittedArguments, containingTypeMapOpt, inferWithDynamic: inferWithDynamic, + isMethodGroupConversion, refOmitMode, containingTypeMapOpt, inferWithDynamic: inferWithDynamic, useSiteDiagnostics: ref useSiteDiagnostics, allowUnexpandedForm: allowUnexpandedForm); } @@ -347,9 +356,9 @@ private MemberAnalysisResult IsConstructorApplicableInNormalForm(MethodSymbol co return MemberAnalysisResult.UseSiteError(); } - var effectiveParameters = GetEffectiveParametersInNormalForm(constructor, arguments.Arguments.Count, argumentAnalysis.ArgsToParamsOpt, arguments.RefKinds, allowRefOmittedArguments: false); + var effectiveParameters = GetEffectiveParametersInNormalForm(constructor, arguments.Arguments.Count, argumentAnalysis.ArgsToParamsOpt, arguments.RefKinds, refOmitMode: RefOmitMode.None); - return IsApplicable(constructor, effectiveParameters, arguments, argumentAnalysis.ArgsToParamsOpt, isVararg: constructor.IsVararg, hasAnyRefOmittedArgument: false, ignoreOpenTypes: false, useSiteDiagnostics: ref useSiteDiagnostics); + return IsApplicable(constructor, effectiveParameters, arguments, argumentAnalysis.ArgsToParamsOpt, isVararg: constructor.IsVararg, hasAnyComRefOmittedArgument: false, ignoreOpenTypes: false, useSiteDiagnostics: ref useSiteDiagnostics); } private MemberAnalysisResult IsConstructorApplicableInExpandedForm(MethodSymbol constructor, AnalyzedArguments arguments, ref HashSet useSiteDiagnostics) @@ -366,14 +375,14 @@ private MemberAnalysisResult IsConstructorApplicableInExpandedForm(MethodSymbol return MemberAnalysisResult.UseSiteError(); } - var effectiveParameters = GetEffectiveParametersInExpandedForm(constructor, arguments.Arguments.Count, argumentAnalysis.ArgsToParamsOpt, arguments.RefKinds, allowRefOmittedArguments: false); + var effectiveParameters = GetEffectiveParametersInExpandedForm(constructor, arguments.Arguments.Count, argumentAnalysis.ArgsToParamsOpt, arguments.RefKinds, refOmitMode: RefOmitMode.None); // A vararg ctor is never applicable in its expanded form because // it is never a params method. Debug.Assert(!constructor.IsVararg); - var result = IsApplicable(constructor, effectiveParameters, arguments, argumentAnalysis.ArgsToParamsOpt, isVararg: false, hasAnyRefOmittedArgument: false, ignoreOpenTypes: false, useSiteDiagnostics: ref useSiteDiagnostics); + var result = IsApplicable(constructor, effectiveParameters, arguments, argumentAnalysis.ArgsToParamsOpt, isVararg: false, hasAnyComRefOmittedArgument: false, ignoreOpenTypes: false, useSiteDiagnostics: ref useSiteDiagnostics); - return result.IsValid ? MemberAnalysisResult.ExpandedForm(result.ArgsToParamsOpt, result.ConversionsOpt, hasAnyRefOmittedArgument: false) : result; + return result.IsValid ? MemberAnalysisResult.ExpandedForm(result.ArgsToParamsOpt, result.ConversionsOpt, hasAnyComRefOmittedArgument: false) : result; } private void AddMemberToCandidateSet( @@ -384,7 +393,7 @@ private void AddMemberToCandidateSet( AnalyzedArguments arguments, bool completeResults, bool isMethodGroupConversion, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, Dictionary> containingTypeMapOpt, bool inferWithDynamic, ref HashSet useSiteDiagnostics, @@ -492,7 +501,7 @@ private void AddMemberToCandidateSet( // Second, we need to determine if the method is applicable in its normal form or its expanded form. var normalResult = (allowUnexpandedForm || !IsValidParams(leastOverriddenMember)) - ? IsMemberApplicableInNormalForm(member, leastOverriddenMember, typeArguments, arguments, isMethodGroupConversion, allowRefOmittedArguments, inferWithDynamic, ref useSiteDiagnostics, completeResults: completeResults) + ? IsMemberApplicableInNormalForm(member, leastOverriddenMember, typeArguments, arguments, isMethodGroupConversion, refOmitMode, inferWithDynamic, ref useSiteDiagnostics, completeResults: completeResults) : default(MemberResolutionResult); var result = normalResult; @@ -505,7 +514,7 @@ private void AddMemberToCandidateSet( if (!isMethodGroupConversion && IsValidParams(leastOverriddenMember)) { - var expandedResult = IsMemberApplicableInExpandedForm(member, leastOverriddenMember, typeArguments, arguments, allowRefOmittedArguments, ref useSiteDiagnostics); + var expandedResult = IsMemberApplicableInExpandedForm(member, leastOverriddenMember, typeArguments, arguments, refOmitMode, ref useSiteDiagnostics); if (PreferExpandedFormOverNormalForm(normalResult.Result, expandedResult.Result)) { result = expandedResult; @@ -1163,8 +1172,8 @@ private BetterResult BetterFunctionMember( // when determining the BetterFunctionMember. // During argument rewriting, we will replace the argument value with a temporary local and pass that local by reference. - bool hasAnyRefOmittedArgument1 = m1.Result.HasAnyRefOmittedArgument; - bool hasAnyRefOmittedArgument2 = m2.Result.HasAnyRefOmittedArgument; + bool hasAnyRefOmittedArgument1 = m1.Result.HasAnyComRefOmittedArgument; + bool hasAnyRefOmittedArgument2 = m2.Result.HasAnyComRefOmittedArgument; if (hasAnyRefOmittedArgument1 != hasAnyRefOmittedArgument2) { return hasAnyRefOmittedArgument1 ? BetterResult.Right : BetterResult.Left; @@ -2402,11 +2411,11 @@ private EffectiveParameters GetEffectiveParametersInNormalForm( int argumentCount, ImmutableArray argToParamMap, ArrayBuilder argumentRefKinds, - bool allowRefOmittedArguments) + RefOmitMode refOmitMode) where TMember : Symbol { bool discarded; - return GetEffectiveParametersInNormalForm(member, argumentCount, argToParamMap, argumentRefKinds, allowRefOmittedArguments, hasAnyRefOmittedArgument: out discarded); + return GetEffectiveParametersInNormalForm(member, argumentCount, argToParamMap, argumentRefKinds, refOmitMode, hasAnyRefOmittedArgument: out discarded); } private EffectiveParameters GetEffectiveParametersInNormalForm( @@ -2414,7 +2423,7 @@ private EffectiveParameters GetEffectiveParametersInNormalForm( int argumentCount, ImmutableArray argToParamMap, ArrayBuilder argumentRefKinds, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, out bool hasAnyRefOmittedArgument) where TMember : Symbol { Debug.Assert(argumentRefKinds != null); @@ -2450,7 +2459,7 @@ private EffectiveParameters GetEffectiveParametersInNormalForm( types.Add(parameter.Type); RefKind argRefKind = hasAnyRefArg ? argumentRefKinds[arg] : RefKind.None; - RefKind paramRefKind = GetEffectiveParameterRefKind(parameter, argRefKind, allowRefOmittedArguments, ref hasAnyRefOmittedArgument); + RefKind paramRefKind = GetEffectiveParameterRefKind(parameter, argRefKind, ShouldOmitRefArgument(refOmitMode, arg), ref hasAnyRefOmittedArgument); if (refs == null) { @@ -2492,10 +2501,10 @@ private EffectiveParameters GetEffectiveParametersInExpandedForm( int argumentCount, ImmutableArray argToParamMap, ArrayBuilder argumentRefKinds, - bool allowRefOmittedArguments) where TMember : Symbol + RefOmitMode refOmitMode) where TMember : Symbol { bool discarded; - return GetEffectiveParametersInExpandedForm(member, argumentCount, argToParamMap, argumentRefKinds, allowRefOmittedArguments, hasAnyRefOmittedArgument: out discarded); + return GetEffectiveParametersInExpandedForm(member, argumentCount, argToParamMap, argumentRefKinds, refOmitMode, hasAnyRefOmittedArgument: out discarded); } private EffectiveParameters GetEffectiveParametersInExpandedForm( @@ -2503,7 +2512,7 @@ private EffectiveParameters GetEffectiveParametersInExpandedForm( int argumentCount, ImmutableArray argToParamMap, ArrayBuilder argumentRefKinds, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, out bool hasAnyRefOmittedArgument) where TMember : Symbol { Debug.Assert(argumentRefKinds != null); @@ -2523,7 +2532,7 @@ private EffectiveParameters GetEffectiveParametersInExpandedForm( types.Add(parm == parameters.Length - 1 ? elementType : parameter.Type); var argRefKind = hasAnyRefArg ? argumentRefKinds[arg] : RefKind.None; - var paramRefKind = GetEffectiveParameterRefKind(parameter, argRefKind, allowRefOmittedArguments, ref hasAnyRefOmittedArgument); + var paramRefKind = GetEffectiveParameterRefKind(parameter, argRefKind, ShouldOmitRefArgument(refOmitMode, arg), ref hasAnyRefOmittedArgument); refs.Add(paramRefKind); if (paramRefKind != RefKind.None) @@ -2543,7 +2552,7 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm ArrayBuilder typeArguments, AnalyzedArguments arguments, bool isMethodGroupConversion, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, bool inferWithDynamic, ref HashSet useSiteDiagnostics, bool completeResults = false) @@ -2575,7 +2584,7 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.UseSiteError()); } - bool hasAnyRefOmittedArgument; + bool hasAnyComRefOmittedArgument; // To determine parameter types we use the originalMember. EffectiveParameters originalEffectiveParameters = GetEffectiveParametersInNormalForm( @@ -2583,10 +2592,16 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm arguments.Arguments.Count, argumentAnalysis.ArgsToParamsOpt, arguments.RefKinds, - allowRefOmittedArguments, - out hasAnyRefOmittedArgument); + refOmitMode, + out hasAnyComRefOmittedArgument); - Debug.Assert(!hasAnyRefOmittedArgument || allowRefOmittedArguments); + Debug.Assert(!hasAnyComRefOmittedArgument || refOmitMode != RefOmitMode.None); + + // Not setting ref omitted flag for extension methods + if (refOmitMode == RefOmitMode.ExtensionMethod) + { + hasAnyComRefOmittedArgument = false; + } // To determine parameter types we use the originalMember. EffectiveParameters constructedEffectiveParameters = GetEffectiveParametersInNormalForm( @@ -2594,14 +2609,14 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm arguments.Arguments.Count, argumentAnalysis.ArgsToParamsOpt, arguments.RefKinds, - allowRefOmittedArguments); + refOmitMode); // The member passed to the following call is returned in the result (possibly a constructed version of it). // The applicability is checked based on effective parameters passed in. var applicableResult = IsApplicable( member, leastOverriddenMember, typeArguments, arguments, originalEffectiveParameters, constructedEffectiveParameters, - argumentAnalysis.ArgsToParamsOpt, hasAnyRefOmittedArgument, + argumentAnalysis.ArgsToParamsOpt, hasAnyComRefOmittedArgument, ref useSiteDiagnostics, inferWithDynamic); @@ -2620,7 +2635,7 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm typeArguments, AnalyzedArguments arguments, - bool allowRefOmittedArguments, + RefOmitMode refOmitMode, ref HashSet useSiteDiagnostics) where TMember : Symbol { @@ -2639,7 +2654,7 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm(member, leastOverriddenMember, MemberAnalysisResult.UseSiteError()); } - bool hasAnyRefOmittedArgument; + bool hasAnyComRefOmittedArgument; // To determine parameter types we use the least derived member. EffectiveParameters originalEffectiveParameters = GetEffectiveParametersInExpandedForm( @@ -2647,10 +2662,16 @@ private MemberResolutionResult IsMemberApplicableInExpandedForm IsMemberApplicableInExpandedForm( result.Member, result.LeastOverriddenMember, - MemberAnalysisResult.ExpandedForm(result.Result.ArgsToParamsOpt, result.Result.ConversionsOpt, hasAnyRefOmittedArgument)) : + MemberAnalysisResult.ExpandedForm(result.Result.ArgsToParamsOpt, result.Result.ConversionsOpt, hasAnyComRefOmittedArgument)) : result; } @@ -2683,7 +2704,7 @@ private MemberResolutionResult IsApplicable( EffectiveParameters originalEffectiveParameters, EffectiveParameters constructedEffectiveParameters, ImmutableArray argsToParamsMap, - bool hasAnyRefOmittedArgument, + bool hasAnyComRefOmittedArgument, ref HashSet useSiteDiagnostics, bool inferWithDynamic = false) where TMember : Symbol @@ -2794,7 +2815,7 @@ private MemberResolutionResult IsApplicable( return new MemberResolutionResult( member, leastOverriddenMember, - IsApplicable(member, effectiveParameters, arguments, argsToParamsMap, member.GetIsVararg(), hasAnyRefOmittedArgument, ignoreOpenTypes, ref useSiteDiagnostics)); + IsApplicable(member, effectiveParameters, arguments, argsToParamsMap, member.GetIsVararg(), hasAnyComRefOmittedArgument, ignoreOpenTypes, ref useSiteDiagnostics)); } private ImmutableArray InferMethodTypeArguments( @@ -2847,7 +2868,7 @@ private MemberAnalysisResult IsApplicable( AnalyzedArguments arguments, ImmutableArray argsToParameters, bool isVararg, - bool hasAnyRefOmittedArgument, + bool hasAnyComRefOmittedArgument, bool ignoreOpenTypes, ref HashSet useSiteDiagnostics) { @@ -2931,7 +2952,7 @@ private MemberAnalysisResult IsApplicable( } else { - result = MemberAnalysisResult.NormalForm(argsToParameters, conversionsArray, hasAnyRefOmittedArgument); + result = MemberAnalysisResult.NormalForm(argsToParameters, conversionsArray, hasAnyComRefOmittedArgument); } return result; @@ -3012,5 +3033,20 @@ private static TMember GetConstructedFrom(TMember member) where TMember throw ExceptionUtilities.UnexpectedValue(member.Kind); } } + + public static bool ShouldOmitRefArgument(RefOmitMode refOmitMode, int argument) + { + switch (refOmitMode) + { + case RefOmitMode.None: + return false; + case RefOmitMode.All: + return true; + case RefOmitMode.ExtensionMethod: + return argument == 0; + default: + throw ExceptionUtilities.UnexpectedValue(refOmitMode); + } + } } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 0be0ab29a2c23..ab45e0607f4b1 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -7864,6 +7864,15 @@ internal static string ERR_ReferenceDirectiveOnlyAllowedInScripts { } } + /// + /// Looks up a localized string similar to A ref extension methods can only be defined on value types.. + /// + internal static string ERR_RefExtensionMethodOnNonValueType { + get { + return ResourceManager.GetString("ERR_RefExtensionMethodOnNonValueType", resourceCulture); + } + } + /// /// Looks up a localized string similar to A ref or out value must be an assignable variable. /// @@ -10043,6 +10052,15 @@ internal static string IDS_FeatureReadonlyAutoImplementedProperties { } } + /// + /// Looks up a localized string similar to by-ref extension method. + /// + internal static string IDS_FeatureRefExtensionMethod { + get { + return ResourceManager.GetString("IDS_FeatureRefExtensionMethod", resourceCulture); + } + } + /// /// Looks up a localized string similar to byref locals and returns. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 74a3103b3b29e..86ac2753f2ecb 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -252,6 +252,9 @@ partial method + + by-ref extension method + method @@ -4981,4 +4984,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A throw expression is not allowed in this context. + + A ref extension methods can only be defined on value types. + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 38cbd6abfc5bc..97b1d8e87a5d9 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1442,5 +1442,7 @@ internal enum ErrorCode ERR_VarInvocationLvalueReserved = 8199, ERR_ExpressionVariableInConstructorOrFieldInitializer = 8200, #endregion diagnostics for out var + + ERR_RefExtensionMethodOnNonValueType = 8201 } } diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 1f2a6d8994c76..3eb2ce5bdc190 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -126,6 +126,7 @@ internal enum MessageID IDS_FeatureExpressionBodiedAccessor = MessageBase + 12715, IDS_FeatureExpressionBodiedDeOrConstructor = MessageBase + 12716, IDS_ThrowExpression = MessageBase + 12717, + IDS_FeatureRefExtensionMethod = MessageBase + 12718, } // Message IDs may refer to strings that need to be localized. @@ -171,6 +172,8 @@ internal static string RequiredFeature(this MessageID feature) { case MessageID.IDS_FeatureIOperation: return "IOperation"; + case MessageID.IDS_FeatureRefExtensionMethod: + return "RefExtensionMethod"; default: return null; } diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 8297206437946..3c53f3cf4dd58 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -4257,7 +4257,7 @@ private void ParseParameterModifiers(SyntaxListBuilder modifiers, bool allowThis { mod = this.AddError(mod, ErrorCode.ERR_BadOutWithThis); } - else if ((flags & ParamFlags.Ref) != 0) + else if ((flags & ParamFlags.Ref) != 0 && !IsFeatureEnabled(MessageID.IDS_FeatureRefExtensionMethod)) { mod = this.AddError(mod, ErrorCode.ERR_BadRefWithThis); } @@ -4276,7 +4276,7 @@ private void ParseParameterModifiers(SyntaxListBuilder modifiers, bool allowThis { mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.RefKeyword)); } - else if ((flags & ParamFlags.This) != 0) + else if ((flags & ParamFlags.This) != 0 && !IsFeatureEnabled(MessageID.IDS_FeatureRefExtensionMethod)) { mod = this.AddError(mod, ErrorCode.ERR_BadRefWithThis); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs index 356fd02ba3470..17200f14e0233 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs @@ -784,7 +784,8 @@ private bool IsValidExtensionMethodSignature() } var parameter = parameters[0]; - return (parameter.RefKind == RefKind.None) && !parameter.IsParams; + return (parameter.RefKind == RefKind.None || parameter.RefKind == RefKind.Ref) + && !parameter.IsParams; } private bool IsValidUserDefinedOperatorSignature(int parameterCount) => diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs index 3deb8c961e8e9..118e4ad9d821a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs @@ -202,6 +202,10 @@ private void MethodChecks(MethodDeclarationSyntax syntax, Binder withTypeParamsB { diagnostics.Add(ErrorCode.ERR_BadExtensionMeth, location); } + else if (!this.ParameterRefKinds.IsDefaultOrEmpty && this.ParameterRefKinds[0] == RefKind.Ref && !parameter0Type.IsValueType) + { + diagnostics.Add(ErrorCode.ERR_RefExtensionMethodOnNonValueType, location); + } else { // Verify ExtensionAttribute is available. diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs index 00aedb211f357..9798a5773fa87 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs @@ -15519,5 +15519,143 @@ .maxstack 1 }"); } + [Fact] + public void LocalStructIsPassedByrefIntoRefExtensionMethod() + { + var source = +@" +using System; + +public struct S +{ + public int I; +} + +class Program +{ + public static void Main(string[] args) + { + S s = new S(); + s.E(); + Console.WriteLine(s.I); + } +} + +public static class Ext +{ + public static void E(this ref S s) + { + s.I++; + } +} +"; + CompileAndVerifyExperimental(source, MessageID.IDS_FeatureRefExtensionMethod, additionalRefs: new[] { SystemRef, SystemCoreRef }, + expectedOutput: "1"); + } + + [Fact] + public void HeapStructIsPassedByrefIntoRefExtensionMethod() + { + var source = +@" +using System; + +public struct S +{ + public int I; +} + +public class C +{ + public S S; +} + +class Program +{ + public static void Main(string[] args) + { + C c = new C(); + c.S.E(); + Console.WriteLine(c.S.I); + } +} + +public static class Ext +{ + public static void E(this ref S s) + { + s.I++; + } +} +"; + CompileAndVerifyExperimental(source, MessageID.IDS_FeatureRefExtensionMethod, additionalRefs: new[] { SystemRef, SystemCoreRef }, + expectedOutput: "1"); + } + + [Fact] + public void ArrayStructIsPassedByrefIntoRefExtensionMethod() + { + var source = +@" +using System; + +public struct S +{ + public int I; +} + +class Program +{ + public static void Main(string[] args) + { + S[] a = new S[1]; + a[0].E(); + Console.WriteLine(a[0].I); + } +} + +public static class Ext +{ + public static void E(this ref S s) + { + s.I++; + } +} +"; + CompileAndVerifyExperimental(source, MessageID.IDS_FeatureRefExtensionMethod, additionalRefs: new[] { SystemRef, SystemCoreRef }, + expectedOutput: "1"); + } + + [Fact] + public void ByRefExtensionMethodOnByrefReturn() + { + var source = +@" +public struct S { public int I; } + +class Program +{ + static S s = new S(); + + public static ref S Get() + { + return ref s; + } + + public static void Main() + { + Get().E(); + System.Console.WriteLine(s.I); + } +} + +public static class Ext +{ + public static void E(this ref S s) { s.I++; } +} +"; + CompileAndVerifyExperimental(source, MessageID.IDS_FeatureRefExtensionMethod, additionalRefs: new[] { SystemRef, SystemCoreRef }, + expectedOutput: "1"); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs index 7bf73c0d58aca..16db91bb5f701 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs @@ -3412,5 +3412,49 @@ void Dummy() Assert.Null(symbolInfo1.Symbol); Assert.True(symbolInfo1.CandidateSymbols.IsEmpty); } + + + + [Fact] + public void ByrefExtensionMethodOnNonVariables() + { + var text = @" +public struct S { } + +class Program +{ + static readonly S s = new S(); + readonly S i = new S(); + S p { get; set; } + + public Program() + { + i.E(); + } + + static Program() + { + s.E(); + } + + public void Main() + { + i.E(); // CS0192 + s.E(); // CS0199 + p.E(); // CS0206 + } +} + +public static class Ext +{ + public static void E(this ref S s) { } +} +"; + CreateExperimentalCompilationWithMscorlib45(text, MessageID.IDS_FeatureRefExtensionMethod) + .VerifyDiagnostics( + Diagnostic(ErrorCode.ERR_RefReadonly, "i.E").WithLocation(22, 9), + Diagnostic(ErrorCode.ERR_RefReadonlyStatic, "s.E").WithLocation(23, 9), + Diagnostic(ErrorCode.ERR_RefProperty, "p.E").WithArguments("Program.p").WithLocation(24, 9)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs index 1d042df3a5140..7408b6c7bb769 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs @@ -9068,5 +9068,31 @@ public enum Something // should be NO errors. CompileAndVerify(source, expectedOutput: @"False"); } + + [Fact] + public void RefExtensionMethodAmbiguity() + { + string source = @" +public struct S { } + +class Program +{ + public static void Main(string[] args) + { + new S().E(); + } +} + +public static class Ext { public static void E(this ref S s) { } } +public static class Ext2 { public static void E(this S s) { } } +"; + + CreateExperimentalCompilationWithMscorlib45(source, MessageID.IDS_FeatureRefExtensionMethod) + .VerifyDiagnostics( + // (8,17): error CS0121: The call is ambiguous between the following methods or properties: 'Ext.E(ref S)' and 'Ext2.E(S)' + // new S().E(); + Diagnostic(ErrorCode.ERR_AmbigCall, "E").WithArguments("Ext.E(ref S)", "Ext2.E(S)") + ); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/test.exe b/src/Compilers/CSharp/Test/Semantic/test.exe new file mode 100644 index 0000000000000..f2618d34214f6 Binary files /dev/null and b/src/Compilers/CSharp/Test/Semantic/test.exe differ diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs index 146816590c3bc..eeff73974e7b1 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ExtensionMethodTests.cs @@ -3898,5 +3898,42 @@ public class BaseClass : I1 Assert.Empty(model.LookupSymbols(instance.Position, baseClass, "SetMember", includeReducedExtensionMethods: true)); Assert.Empty(model.LookupSymbols(instance.Position, baseClass, includeReducedExtensionMethods: true).Where(s => s.Name == "SetMembers")); } + + [Fact] + public void RefExtensionMethod() + { + var ilSource = +@".assembly extern mscorlib { .ver 4:0:0:0 .publickeytoken = (B7 7A 5C 56 19 34 E0 89) } +.assembly extern System.Core {} +.assembly '<>' +{ + .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) +} +.class public S +{ + .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .method public static void M2(object& o) + { + .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + ret + } +} +"; + var source = @"class A +{ + internal static S G = null; +}"; + var compilation = CreateCompilationWithCustomILSource(source, ilSource, appendDefaultHeader: false); + + var refType = compilation.Assembly.GlobalNamespace.GetMember("A"); + + var type = (NamedTypeSymbol)refType.GetMember("G").Type; + + // Static method ref param. + var method = type.GetMember("M2"); + Assert.Equal(1, method.Parameters.Length); + Assert.True(method.IsStatic); + Assert.True(method.IsExtensionMethod); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs index caac3e40ed41b..55bb0af00256b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -19412,6 +19412,22 @@ public void AbstractInSubmission() // internal abstract event System.EventHandler E; Diagnostic(ErrorCode.ERR_AbstractInConcreteClass, "E").WithArguments("E", "Script").WithLocation(3, 45)); } + + [Fact] + public void ERR_RefExtensionMethodOnNonValueType() + { + var source = @"public static class Extensions +{ + public static void Test1(this ref System.String s) { } //CS8201 + public static void Test2(this ref T s) { } //CS8201 + public static void Test3(this ref T s) where T: struct { } +} +"; + CreateExperimentalCompilationWithMscorlib45(source, MessageID.IDS_FeatureRefExtensionMethod) + .VerifyDiagnostics( + Diagnostic(ErrorCode.ERR_RefExtensionMethodOnNonValueType, "Test1").WithLocation(3, 24), + Diagnostic(ErrorCode.ERR_RefExtensionMethodOnNonValueType, "Test2").WithLocation(4, 24)); + } } } diff --git a/src/Compilers/CSharp/csc/test.exe b/src/Compilers/CSharp/csc/test.exe new file mode 100644 index 0000000000000..8efc71c19cb03 Binary files /dev/null and b/src/Compilers/CSharp/csc/test.exe differ