提交 0a498595 编写于 作者: O Omar Tawfik 提交者: GitHub

Support ref/readonly-ref extension methods (#19703)

* Parse ref/readonly-ref extension methods

* Address PR Comments

* Fix break

* More PR Comments

* Fix break
上级 801e0065
......@@ -898,32 +898,33 @@ private static void CheckRestrictedTypeReceiver(BoundExpression expression, Comp
// (i.e. the first argument, if invokedAsExtensionMethod).
var gotError = MemberGroupFinalValidation(receiver, method, expression, diagnostics, invokedAsExtensionMethod);
// Skip building up a new array if the first argument doesn't have to be modified.
ImmutableArray<BoundExpression> args;
if (invokedAsExtensionMethod && !ReferenceEquals(receiver, methodGroup.Receiver))
if (invokedAsExtensionMethod)
{
ArrayBuilder<BoundExpression> builder = ArrayBuilder<BoundExpression>.GetInstance();
BoundExpression receiverArgument;
ParameterSymbol receiverParameter = method.Parameters.First();
// Because the receiver didn't pass through CoerceArguments, we need to apply an appropriate
// conversion here.
Debug.Assert(method.ParameterCount > 0);
Debug.Assert(argsToParams.IsDefault || argsToParams[0] == 0);
BoundExpression convertedReceiver = CreateConversion(receiver, methodResult.Result.ConversionForArg(0), method.Parameters[0].Type, diagnostics);
builder.Add(convertedReceiver);
if ((object)receiver != methodGroup.Receiver)
{
// Because the receiver didn't pass through CoerceArguments, we need to apply an appropriate conversion here.
Debug.Assert(argsToParams.IsDefault || argsToParams[0] == 0);
receiverArgument = CreateConversion(receiver, methodResult.Result.ConversionForArg(0), receiverParameter.Type, diagnostics);
}
else
{
receiverArgument = analyzedArguments.Argument(0);
}
bool first = true;
foreach (BoundExpression arg in analyzedArguments.Arguments)
if (receiverParameter.RefKind == RefKind.Ref)
{
if (first)
{
// Skip the first argument (the receiver), since we added our own.
first = false;
}
else
{
builder.Add(arg);
}
// If this was a ref extension method, receiverArgument must be checked for L-value constraints.
// This helper method will also replace it with a BoundBadExpression if it was invalid.
receiverArgument = CheckValue(receiverArgument, BindValueKind.RefOrOut, diagnostics);
}
ArrayBuilder<BoundExpression> builder = ArrayBuilder<BoundExpression>.GetInstance(analyzedArguments.Arguments.Count);
builder.Add(receiverArgument);
builder.AddRange(analyzedArguments.Arguments.Skip(1));
args = builder.ToImmutableAndFree();
}
else
......
......@@ -2945,7 +2945,6 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind
for (int argumentPosition = 0; argumentPosition < paramCount; argumentPosition++)
{
BoundExpression argument = arguments.Argument(argumentPosition);
RefKind argumentRefKind = arguments.RefKind(argumentPosition);
Conversion conversion;
if (isVararg && argumentPosition == paramCount - 1)
......@@ -2964,9 +2963,22 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind
}
else
{
RefKind argumentRefKind = arguments.RefKind(argumentPosition);
RefKind parameterRefKind = parameters.ParameterRefKinds.IsDefault ? RefKind.None : parameters.ParameterRefKinds[argumentPosition];
bool forExtensionMethodThisArg = arguments.IsExtensionMethodThisArgument(argumentPosition);
if (forExtensionMethodThisArg)
{
Debug.Assert(argumentRefKind == RefKind.None);
if (parameterRefKind == RefKind.Ref)
{
// For ref extension methods, we omit the "ref" modifier on the receiver arguments
// Passing the parameter RefKind for finding the correct conversion.
// For ref-readonly extension methods, argumentRefKind is always None.
argumentRefKind = parameterRefKind;
}
}
conversion = CheckArgumentForApplicability(
candidate,
argument,
......
......@@ -915,6 +915,18 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed
RefKind refArg = arguments.RefKind(arg);
RefKind refParm = parameter.RefKind;
if (arguments.IsExtensionMethodThisArgument(arg))
{
Debug.Assert(refArg == RefKind.None);
if (refParm == RefKind.Ref || refParm == RefKind.RefReadOnly)
{
// For ref and ref-readonly extension methods, we omit the "ref" modifier on receiver arguments.
// Setting the correct RefKind for finding the correct diagnostics message.
// For other ref kinds, keeping it as it is to find mismatch errors.
refArg = refParm;
}
}
// If the expression is untyped because it is a lambda, anonymous method, method group or null
// then we never want to report the error "you need a ref on that thing". Rather, we want to
// say that you can't convert "null" to "ref int".
......
......@@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class CSharpResources {
......@@ -1771,6 +1771,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to The parameter modifier &apos;{0}&apos; cannot be used after the modifier &apos;{1}&apos;.
/// </summary>
internal static string ERR_BadParameterModifiersOrder {
get {
return ResourceManager.GetString("ERR_BadParameterModifiersOrder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Parameter {0} should not be declared with the &apos;{1}&apos; keyword.
/// </summary>
......@@ -8134,6 +8143,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to The first parameter of the reference extension method &apos;{0}&apos; must be a value type or a generic type constrained to struct..
/// </summary>
internal static string ERR_RefExtensionMustBeValueTypeOrConstrainedToOne {
get {
return ResourceManager.GetString("ERR_RefExtensionMustBeValueTypeOrConstrainedToOne", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A ref or out value must be an assignable variable.
/// </summary>
......@@ -8197,6 +8215,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to The first parameter of the readonly reference extension method &apos;{0}&apos; must be a value type..
/// </summary>
internal static string ERR_RefReadOnlyExtensionMustBeValueType {
get {
return ResourceManager.GetString("ERR_RefReadOnlyExtensionMustBeValueType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot use &apos;{0}&apos; as a ref or out value because it is read-only.
/// </summary>
......
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
......@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
......@@ -5123,6 +5123,15 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_TypeReserved" xml:space="preserve">
<value>The type name '{0}' is reserved to be used by the compiler.</value>
</data>
<data name="ERR_RefExtensionMustBeValueTypeOrConstrainedToOne" xml:space="preserve">
<value>The first parameter of the reference extension method '{0}' must be a value type or a generic type constrained to struct.</value>
</data>
<data name="ERR_RefReadOnlyExtensionMustBeValueType" xml:space="preserve">
<value>The first parameter of the readonly reference extension method '{0}' must be a value type.</value>
</data>
<data name="ERR_BadParameterModifiersOrder" xml:space="preserve">
<value>The parameter modifier '{0}' cannot be used after the modifier '{1}'</value>
</data>
<data name="ERR_FieldsInRoStruct" xml:space="preserve">
<value>Instance fields of readonly structs must be readonly.</value>
</data>
......@@ -5132,4 +5141,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_FieldlikeEventsInRoStruct" xml:space="preserve">
<value>Field-like events are not allowed in readonly structs.</value>
</data>
</root>
</root>
\ No newline at end of file
......@@ -1508,6 +1508,9 @@ internal enum ErrorCode
ERR_RefReturnReadonlyNotField2 = 8411,
ERR_ExplicitReservedAttr = 8412,
ERR_TypeReserved = 8413,
ERR_RefExtensionMustBeValueTypeOrConstrainedToOne = 8414,
ERR_RefReadOnlyExtensionMustBeValueType = 8415,
ERR_BadParameterModifiersOrder = 8416,
//PROTOTYPE(ReadonlyRefs): make err IDs contiguous before merging to master.
ERR_FieldsInRoStruct = 8514,
......
......@@ -785,7 +785,15 @@ private bool IsValidExtensionMethodSignature()
}
var parameter = parameters[0];
return (parameter.RefKind == RefKind.None) && !parameter.IsParams;
switch(parameter.RefKind)
{
case RefKind.None:
case RefKind.Ref:
case RefKind.RefReadOnly:
return !parameter.IsParams;
default:
return false;
}
}
private bool IsValidUserDefinedOperatorSignature(int parameterCount) =>
......
......@@ -374,6 +374,15 @@ internal void DoGetExtensionMethods(ArrayBuilder<MethodSymbol> methods, string n
if (method.IsExtensionMethod &&
((options & LookupOptions.AllMethodsOnArityZero) != 0 || arity == method.Arity))
{
var thisParam = method.Parameters.First();
if ((thisParam.RefKind == RefKind.Ref && !thisParam.Type.IsValueType) ||
(thisParam.RefKind == RefKind.RefReadOnly && thisParam.Type.TypeKind != TypeKind.Struct))
{
// For ref and ref-readonly extension methods, receivers need to be of the correct types to be considered in lookup
continue;
}
Debug.Assert(method.MethodKind != MethodKind.ReducedExtension);
methods.Add(method);
}
......
......@@ -92,7 +92,7 @@ internal static class ParameterHelpers
parameterIndex == 0 && thisKeyword.Kind() != SyntaxKind.None,
diagnostics);
ReportParameterErrors(owner, parameterSyntax, parameter, firstDefault, diagnostics);
ReportParameterErrors(owner, parameterSyntax, parameter, thisKeyword, paramsKeyword, firstDefault, diagnostics);
builder.Add(parameter);
++parameterIndex;
......@@ -152,18 +152,10 @@ internal static void EnsureIsReadOnlyAttributeExists(ImmutableArray<ParameterSym
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.ThisKeyword), SyntaxFacts.GetText(SyntaxKind.OutKeyword));
}
else if (seenRef || seenRefReadOnly)
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.ThisKeyword), SyntaxFacts.GetText(SyntaxKind.RefKeyword));
}
else if (seenParams)
{
diagnostics.Add(ErrorCode.ERR_BadParamModThis, modifier.GetLocation());
}
else if (seenIn)
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.ThisKeyword), SyntaxFacts.GetText(SyntaxKind.InKeyword));
}
else
{
seenThis = true;
......@@ -177,7 +169,7 @@ internal static void EnsureIsReadOnlyAttributeExists(ImmutableArray<ParameterSym
}
else if (seenThis)
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.RefKeyword), SyntaxFacts.GetText(SyntaxKind.ThisKeyword));
diagnostics.Add(ErrorCode.ERR_BadParameterModifiersOrder, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.RefKeyword), SyntaxFacts.GetText(SyntaxKind.ThisKeyword));
}
else if (seenParams)
{
......@@ -262,7 +254,7 @@ internal static void EnsureIsReadOnlyAttributeExists(ImmutableArray<ParameterSym
}
else if (seenThis)
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.InKeyword), SyntaxFacts.GetText(SyntaxKind.ThisKeyword));
diagnostics.Add(ErrorCode.ERR_BadParameterModifiersOrder, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.InKeyword), SyntaxFacts.GetText(SyntaxKind.ThisKeyword));
}
else if (seenRef || seenRefReadOnly)
{
......@@ -289,7 +281,7 @@ internal static void EnsureIsReadOnlyAttributeExists(ImmutableArray<ParameterSym
}
else if (seenThis)
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.ReadOnlyKeyword), SyntaxFacts.GetText(SyntaxKind.ThisKeyword));
diagnostics.Add(ErrorCode.ERR_BadParameterModifiersOrder, modifier.GetLocation(), SyntaxFacts.GetText(SyntaxKind.ReadOnlyKeyword), SyntaxFacts.GetText(SyntaxKind.ThisKeyword));
}
else if (seenIn)
{
......@@ -320,13 +312,14 @@ internal static void EnsureIsReadOnlyAttributeExists(ImmutableArray<ParameterSym
Symbol owner,
ParameterSyntax parameterSyntax,
SourceParameterSymbol parameter,
SyntaxToken thisKeyword,
SyntaxToken paramsKeyword,
int firstDefault,
DiagnosticBag diagnostics)
{
TypeSymbol parameterType = parameter.Type;
int parameterIndex = parameter.Ordinal;
bool isDefault = parameterSyntax.Default != null;
SyntaxToken thisKeyword = parameterSyntax.Modifiers.FirstOrDefault(SyntaxKind.ThisKeyword);
if (thisKeyword.Kind() == SyntaxKind.ThisKeyword && parameterIndex != 0)
{
......@@ -339,12 +332,12 @@ internal static void EnsureIsReadOnlyAttributeExists(ImmutableArray<ParameterSym
else if (parameter.IsParams && owner.IsOperator())
{
// error CS1670: params is not valid in this context
diagnostics.Add(ErrorCode.ERR_IllegalParams, parameterSyntax.Modifiers.First(t => t.Kind() == SyntaxKind.ParamsKeyword).GetLocation());
diagnostics.Add(ErrorCode.ERR_IllegalParams, paramsKeyword.GetLocation());
}
else if (parameter.IsParams && !parameterType.IsSZArray())
{
// error CS0225: The params parameter must be a single dimensional array
diagnostics.Add(ErrorCode.ERR_ParamsMustBeArray, parameterSyntax.Modifiers.First(t => t.Kind() == SyntaxKind.ParamsKeyword).GetLocation());
diagnostics.Add(ErrorCode.ERR_ParamsMustBeArray, paramsKeyword.GetLocation());
}
else if (parameter.Type.IsStatic && !parameter.ContainingSymbol.ContainingType.IsInterfaceType())
{
......
......@@ -211,6 +211,7 @@ private void MethodChecks(MethodDeclarationSyntax syntax, Binder withTypeParamsB
if (IsExtensionMethod)
{
var parameter0Type = this.Parameters[0].Type;
var parameter0RefKind = this.Parameters[0].RefKind;
if (!parameter0Type.IsValidExtensionParameterType())
{
// Duplicate Dev10 behavior by selecting the parameter type.
......@@ -219,6 +220,14 @@ private void MethodChecks(MethodDeclarationSyntax syntax, Binder withTypeParamsB
var loc = parameterSyntax.Type.Location;
diagnostics.Add(ErrorCode.ERR_BadTypeforThis, loc, parameter0Type);
}
else if (parameter0RefKind == RefKind.Ref && !parameter0Type.IsValueType)
{
diagnostics.Add(ErrorCode.ERR_RefExtensionMustBeValueTypeOrConstrainedToOne, location, Name);
}
else if (parameter0RefKind == RefKind.RefReadOnly && parameter0Type.TypeKind != TypeKind.Struct)
{
diagnostics.Add(ErrorCode.ERR_RefReadOnlyExtensionMustBeValueType, location, Name);
}
else if ((object)ContainingType.ContainingType != null)
{
diagnostics.Add(ErrorCode.ERR_ExtensionMethodsDecl, location, ContainingType.Name);
......
......@@ -86,6 +86,7 @@
<Compile Include="Semantics\PatternMatchingTests_Global.cs" />
<Compile Include="Semantics\BindingAsyncTasklikeMoreTests.cs" />
<Compile Include="Semantics\BindingAsyncTasklikeTests.cs" />
<Compile Include="Semantics\RefExtensionMethodsTests.cs" />
<Compile Include="Semantics\ReadOnlyStructsTests.cs" />
<Compile Include="Semantics\TargetTypedDefaultTests.cs" />
<Compile Include="Semantics\DeconstructionTests.cs" />
......
......@@ -2791,81 +2791,80 @@ static void Main()
Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "message").WithArguments("message").WithLocation(8, 22));
}
[WorkItem(863401, "DevDiv/Personal")]
[Fact]
public void CS1101ERR_BadRefWithThis()
public void BadParameterModifiers_ThisWithRef()
{
var test = @"
using System;
public static class Extensions
{
//No type parameters
public static void Foo1(ref this int i) {}
public static void Foo1(this ref int i) {}
//Single type parameter
public static void Foo1<T>(ref this T t) {}
public static void Foo1<T>(this ref T t) where T : struct {}
//Multiple type parameters
public static void Foo1<T,U,V>(ref this U u) {}
public static void Foo1<T,U,V>(this ref U u) where U : struct {}
}
public static class GenExtensions<X>
public static class GenExtensions<X> where X : struct
{
//No type parameters
public static void Foo2(ref this int i) {}
public static void Foo2(ref this X x) {}
public static void Foo2(this ref int i) {}
public static void Foo2(this ref X x) {}
//Single type parameter
public static void Foo2<T>(ref this T t) {}
public static void Foo2<T>(ref this X x) {}
public static void Foo2<T>(this ref T t) where T : struct {}
public static void Foo2<T>(this ref X x) {}
//Multiple type parameters
public static void Foo2<T,U,V>(ref this U u) {}
public static void Foo2<T,U,V>(ref this X x) {}
public static void Foo2<T,U,V>(this ref U u) where U : struct {}
public static void Foo2<T,U,V>(this ref X x) {}
}
";
CreateCompilationWithMscorlibAndSystemCore(test).GetDeclarationDiagnostics().Verify(
// (22,40): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo2<T,U,V>(ref this X x) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(22, 40),
// (10,41): error CS8404: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo1<T,U,V>(this ref U u) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(10, 41),
// (22,41): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo2<T,U,V>(this ref X x) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(22, 41),
// (12,21): error CS1106: Extension method must be defined in a non-generic static class
// public static class GenExtensions<X>
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21),
// (10,40): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo1<T,U,V>(ref this U u) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(10, 40),
// (16,33): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo2(ref this X x) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(16, 33),
// (8,37): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo1<T>(this ref T t) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(8, 37),
// (16,34): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo2(this ref X x) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(16, 34),
// (12,21): error CS1106: Extension method must be defined in a non-generic static class
// public static class GenExtensions<X>
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21),
// (18,36): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo2<T>(ref this T t) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(18, 36),
// (6,34): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo1(this ref int i) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(6, 34),
// (18,37): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo2<T>(this ref T t) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(18, 37),
// (12,21): error CS1106: Extension method must be defined in a non-generic static class
// public static class GenExtensions<X>
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21),
// (19,36): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo2<T>(ref this X x) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(19, 36),
// (19,37): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo2<T>(this ref X x) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(19, 37),
// (12,21): error CS1106: Extension method must be defined in a non-generic static class
// public static class GenExtensions<X>
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21),
// (21,40): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo2<T,U,V>(ref this U u) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(21, 40),
// (21,41): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo2<T,U,V>(this ref U u) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(21, 41),
// (12,21): error CS1106: Extension method must be defined in a non-generic static class
// public static class GenExtensions<X>
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21),
// (15,33): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo2(ref this int i) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(15, 33),
// (15,34): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Foo2(this ref int i) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(15, 34),
// (12,21): error CS1106: Extension method must be defined in a non-generic static class
// public static class GenExtensions<X>
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21),
// (8,36): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo1<T>(ref this T t) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(8, 36),
// (6,33): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Foo1(ref this int i) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(6, 33));
Diagnostic(ErrorCode.ERR_BadExtensionAgg, "GenExtensions").WithLocation(12, 21));
}
[Fact]
......@@ -2971,23 +2970,13 @@ public static class Extensions
{
//No type parameters
public static void Test(this in int i) {}
//Single type parameter
public static void Test<T>(this in T t) {}
//Multiple type parameters
public static void Test<T,U,V>(this in U u) {}
}
";
CreateCompilationWithMscorlibAndSystemCore(test).GetDeclarationDiagnostics().Verify(
// (5,34): error CS8404: The parameter modifier 'in' cannot be used with 'this'
// (5,34): error CS8416: The parameter modifier 'in' cannot be used after the modifier 'this'
// public static void Test(this in int i) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "in").WithArguments("in", "this").WithLocation(5, 34),
// (7,37): error CS8404: The parameter modifier 'in' cannot be used with 'this'
// public static void Test<T>(this in T t) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "in").WithArguments("in", "this").WithLocation(7, 37),
// (9,41): error CS8404: The parameter modifier 'in' cannot be used with 'this'
// public static void Test<T,U,V>(this in U u) {}
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "in").WithArguments("in", "this").WithLocation(9, 41));
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "in").WithArguments("in", "this").WithLocation(5, 34));
}
[Fact]
......@@ -3404,33 +3393,24 @@ public static class TestType
";
CreateCompilationWithMscorlibAndSystemCore(test).GetDeclarationDiagnostics().Verify(
// (14,42): error CS8404: The parameter modifier 'ref' cannot be used with 'this'
// (14,42): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Method6<T, U, V>(this ref readonly int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "ref").WithArguments("ref", "this").WithLocation(14, 42),
// (14,46): error CS8404: The parameter modifier 'readonly' cannot be used with 'this'
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(14, 42),
// (14,46): error CS8416: The parameter modifier 'readonly' cannot be used after the modifier 'this'
// public static void Method6<T, U, V>(this ref readonly int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "readonly").WithArguments("readonly", "this").WithLocation(14, 46),
// (6,33): error CS8404: The parameter modifier 'ref' cannot be used with 'this'
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "readonly").WithArguments("readonly", "this").WithLocation(14, 46),
// (6,33): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Method2(this ref readonly int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "ref").WithArguments("ref", "this").WithLocation(6, 33),
// (6,37): error CS8404: The parameter modifier 'readonly' cannot be used with 'this'
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(6, 33),
// (6,37): error CS8416: The parameter modifier 'readonly' cannot be used after the modifier 'this'
// public static void Method2(this ref readonly int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "readonly").WithArguments("readonly", "this").WithLocation(6, 37),
// (9,44): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Method3<T>(ref readonly this int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(9, 44),
// (10,36): error CS8404: The parameter modifier 'ref' cannot be used with 'this'
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "readonly").WithArguments("readonly", "this").WithLocation(6, 37),
// (10,36): error CS8416: The parameter modifier 'ref' cannot be used after the modifier 'this'
// public static void Method4<T>(this ref readonly int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "ref").WithArguments("ref", "this").WithLocation(10, 36),
// (10,40): error CS8404: The parameter modifier 'readonly' cannot be used with 'this'
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "ref").WithArguments("ref", "this").WithLocation(10, 36),
// (10,40): error CS8416: The parameter modifier 'readonly' cannot be used after the modifier 'this'
// public static void Method4<T>(this ref readonly int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "readonly").WithArguments("readonly", "this").WithLocation(10, 40),
// (13,50): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Method5<T, U, V>(ref readonly this int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(13, 50),
// (5,41): error CS8404: The parameter modifier 'this' cannot be used with 'ref'
// public static void Method1(ref readonly this int i) { }
Diagnostic(ErrorCode.ERR_BadParameterModifiers, "this").WithArguments("this", "ref").WithLocation(5, 41));
Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "readonly").WithArguments("readonly", "this").WithLocation(10, 40));
}
[Fact]
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.IO
Imports Microsoft.CodeAnalysis.CSharp
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
......@@ -2485,6 +2486,64 @@ Dim o As New Object()
Assert.True(s1.SourceAssembly.MightContainExtensionMethods)
End Sub
<Fact>
Public Sub ConsumeRefExtensionMethods()
Dim options = New CSharpParseOptions(CodeAnalysis.CSharp.LanguageVersion.Latest)
Dim csharp = CreateCSharpCompilation("
public static class Extensions
{
public static void PrintValue(ref this int p)
{
System.Console.Write(p);
}
}", referencedAssemblies:={MscorlibRef, SystemCoreRef}, parseOptions:=options).EmitToImageReference()
Dim vb = CreateCompilationWithMscorlibAndVBRuntime(
<compilation name="AssemblyName">
<file name="a.vb">
<![CDATA[
Module Program
Sub Main()
Dim value = 5
value.PrintValue()
End Sub
End Module
]]>
</file>
</compilation>, options:=TestOptions.ReleaseExe, additionalRefs:={csharp})
CompileAndVerify(vb, expectedOutput:="5")
End Sub
<Fact>
Public Sub ConsumeRefReadOnlyExtensionMethods()
Dim options = New CSharpParseOptions(CodeAnalysis.CSharp.LanguageVersion.Latest)
Dim csharp = CreateCSharpCompilation("
public static class Extensions
{
public static void PrintValue(ref readonly this int p)
{
System.Console.Write(p);
}
}", referencedAssemblies:={MscorlibRef, SystemCoreRef}, parseOptions:=options).EmitToImageReference()
Dim vb = CreateCompilationWithMscorlibAndVBRuntime(
<compilation name="AssemblyName">
<file name="a.vb">
<![CDATA[
Module Program
Sub Main()
Dim value = 5
value.PrintValue()
End Sub
End Module
]]>
</file>
</compilation>, options:=TestOptions.ReleaseExe, additionalRefs:={csharp})
CompileAndVerify(vb, expectedOutput:="5")
End Sub
End Class
End Namespace
......
......@@ -632,5 +632,209 @@ void Main(string[] args)
}
", matchPriority: SymbolMatchPriority.Keyword);
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefKeyword_InClass()
{
await VerifyKeywordAsync(@"
public static class Extensions
{
public static void Extension(ref $$");
await VerifyKeywordAsync(@"
public static class Extensions
{
public static void Extension(ref $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefReadOnlyKeywords_InClass()
{
await VerifyKeywordAsync(@"
public static class Extensions
{
public static void Extension(ref readonly $$");
await VerifyKeywordAsync(@"
public static class Extensions
{
public static void Extension(ref readonly $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterInKeyword_InClass()
{
await VerifyKeywordAsync(@"
public static class Extensions
{
public static void Extension(in $$");
await VerifyKeywordAsync(@"
public static class Extensions
{
public static void Extension(in $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_SecondParameter_AfterRefKeyword_InClass()
{
await VerifyAbsenceAsync(@"
public static class Extensions
{
public static void Extension(int x, ref $$");
await VerifyAbsenceAsync(@"
public static class Extensions
{
public static void Extension(int x, ref $$ object obj) { }
}");
}
[Fact]
public async Task TestExtensionMethods_SecondParameter_AfterRefReadonlyKeywords_InClass()
{
await VerifyAbsenceAsync(@"
public static class Extensions
{
public static void Extension(int x, ref readonly $$");
await VerifyAbsenceAsync(@"
public static class Extensions
{
public static void Extension(int x, ref readonly $$ object obj) { }
}");
}
[Fact]
public async Task TestExtensionMethods_SecondParameter_AfterInKeyword_InClass()
{
await VerifyAbsenceAsync(@"
public static class Extensions
{
public static void Extension(int x, in $$");
await VerifyAbsenceAsync(@"
public static class Extensions
{
public static void Extension(int x, in $$ object obj) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefKeyword_OutsideClass()
{
await VerifyAbsenceAsync("public static void Extension(ref $$");
await VerifyAbsenceAsync("public static void Extension(ref $$ object obj, int x) { }");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefReadOnlyKeywords_OutsideClass()
{
await VerifyAbsenceAsync("public static void Extension(ref readonly $$");
await VerifyAbsenceAsync("public static void Extension(ref readonly $$ object obj, int x) { }");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterInKeyword_OutsideClass()
{
await VerifyAbsenceAsync("public static void Extension(in $$");
await VerifyAbsenceAsync("public static void Extension(in $$ object obj, int x) { }");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefKeyword_NonStaticClass()
{
await VerifyAbsenceAsync(@"
public class Extensions
{
public static void Extension(ref $$");
await VerifyAbsenceAsync(@"
public class Extensions
{
public static void Extension(ref $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefReadOnlyKeywords_NonStaticClass()
{
await VerifyAbsenceAsync(@"
public class Extensions
{
public static void Extension(ref readonly $$");
await VerifyAbsenceAsync(@"
public class Extensions
{
public static void Extension(ref readonly $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterInKeyword_NonStaticClass()
{
await VerifyAbsenceAsync(@"
public class Extensions
{
public static void Extension(in $$");
await VerifyAbsenceAsync(@"
public class Extensions
{
public static void Extension(in $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefKeyword_NonStaticMethod()
{
await VerifyAbsenceAsync(@"
public static class Extensions
{
public void Extension(ref $$");
await VerifyAbsenceAsync(@"
public static class Extensions
{
public void Extension(ref $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterRefReadOnlyKeywords_NonStaticMethod()
{
await VerifyAbsenceAsync(@"
public static class Extensions
{
public void Extension(ref readonly $$");
await VerifyAbsenceAsync(@"
public static class Extensions
{
public void Extension(ref readonly $$ object obj, int x) { }
}");
}
[Fact]
public async Task TestExtensionMethods_FirstParameter_AfterInKeyword_NonStaticMethod()
{
await VerifyAbsenceAsync(@"
public static class Extensions
{
public void Extension(in $$");
await VerifyAbsenceAsync(@"
public static class Extensions
{
public void Extension(in $$ object obj, int x) { }
}");
}
}
}
......@@ -61,7 +61,7 @@ private bool IsConstructorInitializerContext(CSharpSyntaxContext context)
private static bool IsExtensionMethodParameterContext(CSharpSyntaxContext context, CancellationToken cancellationToken)
{
// TODO(cyrusn): lambda/anon methods can have out/ref parameters
if (!context.SyntaxTree.IsParameterModifierContext(context.Position, context.LeftToken, cancellationToken, allowableIndex: 0))
if (!context.SyntaxTree.IsParameterModifierContext(context.Position, context.LeftToken, cancellationToken, isThisKeyword: true))
{
return false;
}
......
......@@ -979,7 +979,7 @@ public static bool IsDefaultExpressionContext(this SyntaxTree syntaxTree, int po
int position,
SyntaxToken tokenOnLeftOfPosition,
CancellationToken cancellationToken,
int? allowableIndex = null)
bool isThisKeyword = false)
{
// cases:
// Foo(|
......@@ -991,29 +991,18 @@ public static bool IsDefaultExpressionContext(this SyntaxTree syntaxTree, int po
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.Parent.IsDelegateOrConstructorOrLocalFunctionOrMethodParameterList())
{
if (allowableIndex.HasValue)
{
if (allowableIndex.Value != 0)
{
return false;
}
}
return true;
}
if (token.IsKind(SyntaxKind.CommaToken) &&
token.Parent.IsDelegateOrConstructorOrLocalFunctionOrMethodParameterList())
{
if (allowableIndex.HasValue)
if (isThisKeyword)
{
var parameterList = token.GetAncestor<ParameterListSyntax>();
var commaIndex = parameterList.Parameters.GetWithSeparators().IndexOf(token);
var index = commaIndex / 2 + 1;
if (index != allowableIndex.Value)
{
return false;
}
return index == 0;
}
return true;
......@@ -1024,21 +1013,29 @@ public static bool IsDefaultExpressionContext(this SyntaxTree syntaxTree, int po
token.Parent.IsParentKind(SyntaxKind.Parameter) &&
token.Parent.GetParent().GetParent().IsDelegateOrConstructorOrLocalFunctionOrMethodParameterList())
{
if (allowableIndex.HasValue)
if (isThisKeyword)
{
var parameter = token.GetAncestor<ParameterSyntax>();
var parameterList = parameter.GetAncestorOrThis<ParameterListSyntax>();
int parameterIndex = parameterList.Parameters.IndexOf(parameter);
if (allowableIndex.Value != parameterIndex)
{
return false;
}
return parameterIndex == 0;
}
return true;
}
if (isThisKeyword &&
(token.IsKind(SyntaxKind.RefKeyword) || token.IsKind(SyntaxKind.ReadOnlyKeyword) || token.IsKind(SyntaxKind.InKeyword)) &&
token.Parent.GetParent().IsDelegateOrConstructorOrLocalFunctionOrMethodParameterList())
{
var parameter = token.GetAncestor<ParameterSyntax>();
var parameterList = parameter.GetAncestorOrThis<ParameterListSyntax>();
int parameterIndex = parameterList.Parameters.IndexOf(parameter);
return parameterIndex == 0;
}
return false;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册