提交 e3b9c5d4 编写于 作者: J Julien Couvreur 提交者: Julien Couvreur

Refactoring on inferred tuple names (C#): Complexify makes tuple inferred...

Refactoring on inferred tuple names (C#): Complexify makes tuple inferred names explicit, and simplify removes them.
Do the same for anonymous types
上级 27933801
......@@ -5,3 +5,5 @@ Microsoft.CodeAnalysis.CSharp.SyntaxKind.DefaultLiteralExpression = 8755 -> Micr
static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.MapSpecifiedToEffectiveVersion(this Microsoft.CodeAnalysis.CSharp.LanguageVersion version) -> Microsoft.CodeAnalysis.CSharp.LanguageVersion
static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.ToDisplayString(this Microsoft.CodeAnalysis.CSharp.LanguageVersion version) -> string
static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.TryParse(this string version, out Microsoft.CodeAnalysis.CSharp.LanguageVersion result) -> bool
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsReservedTupleElementName(string elementName) -> bool
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.TryGetInferredMemberName(this Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax input) -> string
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
......@@ -425,5 +426,28 @@ internal static bool IsDeclarationExpressionType(SyntaxNode node, out Declaratio
parent = node.Parent as DeclarationExpressionSyntax;
return node == parent?.Type;
}
/// <summary>
/// Given an initializer expression infer the name of anonymous property or tuple element.
/// Returns null if unsuccessful
/// </summary>
public static string TryGetInferredMemberName(this ExpressionSyntax input)
{
var nameToken = input.ExtractAnonymousTypeMemberName();
return nameToken.Kind() == SyntaxKind.IdentifierToken ? nameToken.ValueText : null;
}
/// <summary>
/// Checks whether the element name is reserved.
///
/// For example:
/// "Item3" is reserved (at certain positions).
/// "Rest", "ToString" and other members of System.ValueTuple are reserved (in any position).
/// Names that are not reserved return false.
/// </summary>
public static bool IsReservedTupleElementName(string elementName)
{
return TupleTypeSymbol.IsElementNameReserved(elementName) != -1;
}
}
}
\ No newline at end of file
......@@ -117,8 +117,8 @@ internal static CSharpSyntaxNode AnonymousFunctionBody(this SyntaxNode lambda)
}
/// <summary>
/// Given an initializer expression infer the name of anonymous property.
/// Returns default(SyntaxToken) if unsuccessful
/// Given an initializer expression infer the name of anonymous property or tuple element.
/// Returns default if unsuccessful
/// </summary>
internal static SyntaxToken ExtractAnonymousTypeMemberName(this ExpressionSyntax input)
{
......
......@@ -4929,6 +4929,36 @@ static void Main()
Assert.Equal("System.Int32", model.GetTypeInfo(discard).Type.ToTestDisplayString());
}
[Fact]
public void EscapedUnderscoreInDeclaration()
{
var source =
@"
class C
{
static void Main()
{
(@_, var x) = (1, 2);
}
}
";
var comp = CreateStandardCompilation(source, options: TestOptions.DebugExe, references: s_valueTupleRefs);
comp.VerifyDiagnostics(
// (6,10): error CS0103: The name '_' does not exist in the current context
// (@_, var x) = (1, 2);
Diagnostic(ErrorCode.ERR_NameNotInContext, "@_").WithArguments("_").WithLocation(6, 10),
// (6,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side.
// (@_, var x) = (1, 2);
Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(@_, var x)").WithLocation(6, 9)
);
var tree = comp.SyntaxTrees.First();
var model = comp.GetSemanticModel(tree);
Assert.Empty(GetDiscardIdentifiers(tree));
}
[Fact]
public void UnderscoreLocalInDeconstructDeclaration()
{
......
......@@ -3664,6 +3664,54 @@ void M()
verifier.VerifyDiagnostics();
}
[Fact]
public void TupleCreationWithInferredNames2()
{
var source = @"
class C
{
private int e = 5;
}
class C2
{
C instance = null;
C2 instance2 = null;
int M()
{
var y = (instance?.e, (instance.e, instance2.M(), checked(instance.e), default(int)));
System.Console.Write(y);
return 42;
}
}
";
var compilation = CreateStandardCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1),
references: new[] { MscorlibRef, ValueTupleRef, SystemRuntimeFacadeRef });
compilation.VerifyDiagnostics(
// (12,27): error CS0122: 'C.e' is inaccessible due to its protection level
// var y = (instance?.e, (instance.e, instance2.M(), checked(instance.e), default(int)));
Diagnostic(ErrorCode.ERR_BadAccess, ".e").WithArguments("C.e").WithLocation(12, 27),
// (12,41): error CS0122: 'C.e' is inaccessible due to its protection level
// var y = (instance?.e, (instance.e, instance2.M(), checked(instance.e), default(int)));
Diagnostic(ErrorCode.ERR_BadAccess, "e").WithArguments("C.e").WithLocation(12, 41),
// (12,76): error CS0122: 'C.e' is inaccessible due to its protection level
// var y = (instance?.e, (instance.e, instance2.M(), checked(instance.e), default(int)));
Diagnostic(ErrorCode.ERR_BadAccess, "e").WithArguments("C.e").WithLocation(12, 76),
// (4,17): warning CS0414: The field 'C.e' is assigned but its value is never used
// private int e = 5;
Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "e").WithArguments("C.e")
);
var tree = compilation.SyntaxTrees.First();
var model = compilation.GetSemanticModel(tree);
var nodes = tree.GetCompilationUnitRoot().DescendantNodes();
// PROTOTYPE(tuple-names) The type for int? was not picked up
var yTuple = nodes.OfType<TupleExpressionSyntax>().ElementAt(0);
Assert.Equal("(? e, (System.Int32 e, System.Int32, System.Int32, System.Int32))",
model.GetTypeInfo(yTuple).Type.ToTestDisplayString());
}
[Fact]
public void InferredNamesInLinq()
{
......@@ -3700,6 +3748,58 @@ static void M(IEnumerable<C> list)
verifier.VerifyDiagnostics();
}
[Fact]
public void InferredNamesInTernary()
{
var source = @"
class C
{
static void Main()
{
var i = 1;
var flag = false;
var t = flag ? (i, 2) : (i, 3);
System.Console.Write(t.i);
}
}
";
var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1),
additionalRefs: new[] { MscorlibRef, ValueTupleRef, SystemRuntimeFacadeRef, LinqAssemblyRef },
expectedOutput:"1");
verifier.VerifyDiagnostics();
}
[Fact]
public void InferredNames_ExtensionInvokedInCSharp7ButNotCSharp7_1()
{
var source = @"
using System;
class C
{
static void Main()
{
Action M = () => Console.Write(""lambda"");
(1, M).M();
}
}
static class Extension
{
public static void M(this (int, Action) t) => Console.Write(""extension"");
}
";
var verifier7 = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7),
additionalRefs: new[] { MscorlibRef, ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef },
expectedOutput: "extension");
verifier7.VerifyDiagnostics();
var verifier7_1 = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1),
additionalRefs: new[] { MscorlibRef, ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef },
expectedOutput: "lambda");
verifier7_1.VerifyDiagnostics();
}
[Fact]
public void LongTupleWithArgumentEvaluation()
{
......
......@@ -156,5 +156,37 @@ public void TestBug991510()
var span = section.Span;
Assert.Equal(default(TextSpan), span);
}
[Theory]
[InlineData("x", "x")]
[InlineData("x.y", "y")]
[InlineData("x?.y", "y")]
[InlineData("this.y", "y")]
[InlineData("M()", null)]
[InlineData("x.M()", null)]
[InlineData("default(x)", null)]
[InlineData("typeof(x)", null)]
public void ExtractAnonymousTypeMemberName(string source, string expected)
{
var expr = SyntaxFactory.ParseExpression(source, options: TestOptions.Regular);
var actual = SyntaxFacts.TryGetInferredMemberName(expr);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData("Item0", false)]
[InlineData("Item1", true)]
[InlineData("Item2", true)]
[InlineData("Item10", true)]
[InlineData("Rest", true)]
[InlineData("ToString", true)]
[InlineData("GetHashCode", true)]
[InlineData("item1", false)]
[InlineData("item10", false)]
[InlineData("Alice", false)]
public void IsReservedTupleElementName(string elementName, bool isReserved)
{
Assert.Equal(isReserved, SyntaxFacts.IsReservedTupleElementName(elementName));
}
}
}
......@@ -5896,7 +5896,7 @@ End Class
End Sub
<Fact>
Public Sub TupleCreationWithInferredNamesWithVB15_3()
Public Sub TupleCreationWithInferredNames()
Dim verifier = CompileAndVerify(
<compilation>
<file name="a.vb">
......@@ -5939,6 +5939,39 @@ End Class
verifier.VerifyDiagnostics()
End Sub
<Fact>
Public Sub TupleCreationWithInferredNames2()
Dim verifier = CompileAndVerify(
<compilation>
<file name="a.vb">
Class C
Dim e As Integer = 5
Dim instance As C = Nothing
Function M() As Integer
Dim y As (Integer?, object) = (instance?.e, (e, instance.M()))
System.Console.Write(y)
Return 42
End Function
End Class
</file>
</compilation>,
additionalRefs:=s_valueTupleRefs,
parseOptions:=TestOptions.Regular.WithLanguageVersion(LanguageVersion.VisualBasic15_3),
sourceSymbolValidator:=
Sub(m As ModuleSymbol)
Dim compilation = m.DeclaringCompilation
Dim tree = compilation.SyntaxTrees.First()
Dim model = compilation.GetSemanticModel(tree)
Dim nodes = tree.GetCompilationUnitRoot().DescendantNodes()
Dim yTuple = nodes.OfType(Of TupleExpressionSyntax)().ElementAt(0)
Assert.Equal("(e As System.Nullable(Of System.Int32), (e As System.Int32, M As System.Int32))",
model.GetTypeInfo(yTuple).Type.ToTestDisplayString())
End Sub)
verifier.VerifyDiagnostics()
End Sub
<Fact>
Public Sub MissingMemberAccessWithVB15()
Dim comp = CreateCompilationWithMscorlibAndVBRuntime(
......@@ -6030,6 +6063,61 @@ End Class
verifier.VerifyDiagnostics()
End Sub
<Fact>
Public Sub InferredNamesInTernary()
Dim verifier = CompileAndVerify(
<compilation>
<file name="a.vb">
Class C
Shared Sub Main()
Dim i = 1
Dim flag = False
Dim t = If(flag, (i, 2), (i, 3))
System.Console.Write(t.i)
End Sub
End Class
</file>
</compilation>,
additionalRefs:={ValueTupleRef, SystemRuntimeFacadeRef, LinqAssemblyRef},
parseOptions:=TestOptions.Regular.WithLanguageVersion(LanguageVersion.VisualBasic15_3),
expectedOutput:="1")
verifier.VerifyDiagnostics()
End Sub
<Fact>
Public Sub InferredNames_ExtensionInvokedInVB15ButNotVB15_3()
Dim source = <compilation>
<file name="a.vb">
Imports System
Class C
Shared Sub Main()
Dim M As Action = Sub() Console.Write("lambda")
Dim t = (1, M)
t.M()
End Sub
End Class
Module Extensions
&lt;System.Runtime.CompilerServices.Extension()&gt;
Public Sub M(self As (Integer, Action))
Console.Write("extension")
End Sub
End Module
</file>
</compilation>
Dim verifier15 = CompileAndVerify(source,
additionalRefs:={ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef},
parseOptions:=TestOptions.Regular.WithLanguageVersion(LanguageVersion.VisualBasic15),
expectedOutput:="extension")
verifier15.VerifyDiagnostics()
Dim verifier15_3 = CompileAndVerify(source,
additionalRefs:={ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef},
parseOptions:=TestOptions.Regular.WithLanguageVersion(LanguageVersion.VisualBasic15_3),
expectedOutput:="lambda")
verifier15_3.VerifyDiagnostics()
End Sub
<Fact>
Public Sub LongTupleWithArgumentEvaluation()
......
......@@ -1892,5 +1892,35 @@ private static int GetValue(int value)
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.ExtractMethod)]
public async Task TestTupleWithInferredNames()
{
await TestAsync(@"
class Program
{
void M()
{
int a = 1;
var t = [|(a, b: 2)|];
System.Console.Write(t.a);
}
}",
@"
class Program
{
void M()
{
int a = 1;
var t = {|Rename:GetT|}(a);
System.Console.Write(t.a);
}
private static (int a, int b) GetT(int a)
{
return (a, b: 2);
}
}", TestOptions.Regular7_1);
}
}
}
\ No newline at end of file
......@@ -4135,5 +4135,363 @@ void M()
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = (i, 3);
}
}";
var expected = @"
class C
{
void M()
{
var t = (i: 1 + 2, 3);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_Trivia()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = ( /*comment*/ i, 3);
}
}";
var expected = @"
class C
{
void M()
{
var t = ( /*comment*/ i: 1 + 2, 3);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_Trivia2()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = (
/*comment*/ i,
/*comment*/ 3
);
}
}";
var expected = @"
class C
{
void M()
{
var t = (
/*comment*/ i: 1 + 2,
/*comment*/ 3
);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_NoDuplicateNames()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = (i, i);
}
}";
var expected = @"
class C
{
void M()
{
var t = (1 + 2, 1 + 2);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_DeconstructionDeclaration()
{
var code = @"
class C
{
static int y = 1;
void M()
{
int [||]i = C.y;
var t = ((i, (i, _)) = (1, (i, 3)));
}
}";
var expected = @"
class C
{
static int y = 1;
void M()
{
var t = ((C.y, (C.y, _)) = (1, (C.y, 3)));
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_DeconstructionDeclaration2()
{
var code = @"
class C
{
static int y = 1;
void M()
{
int [||]i = C.y;
var t = ((i, _) = (1, 2));
}
}";
var expected = @"
class C
{
static int y = 1;
void M()
{
var t = ((C.y, _) = (1, 2));
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_NoReservedNames()
{
var code = @"
class C
{
void M()
{
int [||]Rest = 1 + 2;
var t = (Rest, 3);
}
}";
var expected = @"
class C
{
void M()
{
var t = (1 + 2, 3);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_NoReservedNames2()
{
var code = @"
class C
{
void M()
{
int [||]Item1 = 1 + 2;
var t = (Item1, 3);
}
}";
var expected = @"
class C
{
void M()
{
var t = (1 + 2, 3);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_EscapeKeywords()
{
var code = @"
class C
{
void M()
{
int [||]@int = 1 + 2;
var t = (@int, 3);
}
}";
var expected = @"
class C
{
void M()
{
var t = (@int: 1 + 2, 3);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitTupleNameAdded_DoNotEscapeContextualKeywords()
{
var code = @"
class C
{
void M()
{
int [||]@where = 1 + 2;
var t = (@where, 3);
}
}";
var expected = @"
class C
{
void M()
{
var t = (where: 1 + 2, 3);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitAnonymousTypeMemberNameAdded_DuplicateNames()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = new { i, i }; // error already
}
}";
var expected = @"
class C
{
void M()
{
var t = new { i = 1 + 2, i = 1 + 2 }; // error already
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitAnonymousTypeMemberNameAdded_AssignmentEpression()
{
var code = @"
class C
{
void M()
{
int j = 0;
int [||]i = j = 1;
var t = new { i, k = 3 };
}
}";
var expected = @"
class C
{
void M()
{
int j = 0;
var t = new { i = j = 1, k = 3 };
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitAnonymousTypeMemberNameAdded_Comment()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = new { /*comment*/ i, j = 3 };
}
}";
var expected = @"
class C
{
void M()
{
var t = new { /*comment*/ i = 1 + 2, j = 3 };
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)]
public async Task ExplicitAnonymousTypeMemberNameAdded_Comment2()
{
var code = @"
class C
{
void M()
{
int [||]i = 1 + 2;
var t = new {
/*comment*/ i,
/*comment*/ j = 3
};
}
}";
var expected = @"
class C
{
void M()
{
var t = new {
/*comment*/
i = 1 + 2,
/*comment*/ j = 3
};
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
}
}
......@@ -4051,5 +4051,208 @@ void Method(MySpan span)
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task TupleWithInferredName_LeaveExplicitName()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int a = 1;
var t = (a, x: [|C.y|]);
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int a = 1;
int {|Rename:y1|} = C.y;
var t = (a, x: y1);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task TupleWithInferredName_InferredNameBecomesExplicit()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
var t = (x, [|C.y|]);
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
int {|Rename:y1|} = C.y;
var t = (x, y: y1);
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task TupleWithInferredName_AllOccurrences()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
var t = (x, [|C.y|]);
var t2 = (C.y, x);
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
int {|Rename:y1|} = C.y;
var t = (x, y: y1);
var t2 = (y: y1, x);
}
}";
await TestInRegularAndScriptAsync(code, expected, index: 1, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task TupleWithInferredName_NoDuplicateNames()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
var t = (C.y, [|C.y|]);
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
int {|Rename:y1|} = C.y;
var t = (y1, y1);
}
}";
await TestInRegularAndScriptAsync(code, expected, index: 1, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task AnonymousTypeWithInferredName_LeaveExplicitName()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int a = 1;
var t = new { a, x= [|C.y|] };
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int a = 1;
int {|Rename:y1|} = C.y;
var t = new { a, x= y1 };
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task AnonymousTypeWithInferredName_InferredNameBecomesExplicit()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
var t = new { x, [|C.y|] };
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
int {|Rename:y1|} = C.y;
var t = new { x, y = y1 };
}
}";
await TestInRegularAndScriptAsync(code, expected, ignoreTrivia: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)]
public async Task AnonymousTypeWithInferredName_NoDuplicatesAllowed()
{
var code =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
var t = new { C.y, [|C.y|] }; // this is an error already
}
}";
var expected =
@"class C
{
static int y = 2;
void M()
{
int x = 1;
int {|Rename:y1|} = C.y;
var t = new { y= y1, y= y1 }; // this is an error already
}
}";
await TestInRegularAndScriptAsync(code, expected, index: 1, ignoreTrivia: false);
}
}
}
\ No newline at end of file
......@@ -71,6 +71,40 @@ void A()
index: 1);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectFromCSharp5ToCSharp6()
{
await TestLanguageVersionUpgradedAsync(
@"
class Program
{
void A()
{
var x = [|nameof(A)|];
}
}",
LanguageVersion.CSharp6,
new CSharpParseOptions(LanguageVersion.CSharp5),
index: 1);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectFromCSharp4ToCSharp5()
{
await TestLanguageVersionUpgradedAsync(
@"
class Program
{
void A()
{
Func<int, Task<int>> f = [|async|] x => x;
}
}",
LanguageVersion.CSharp5,
new CSharpParseOptions(LanguageVersion.CSharp4),
index: 1);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectFromCSharp7ToLatest()
{
......
......@@ -303,9 +303,7 @@ private bool CanUseVar(ITypeSymbol typeSymbol)
var matches = FindMatches(document, expression, document, oldOutermostBlock, allOccurrences, cancellationToken);
Debug.Assert(matches.Contains(expression));
var complexified = await ComplexifyParentingStatements(document, matches, cancellationToken).ConfigureAwait(false);
document = complexified.Item1;
matches = complexified.Item2;
(document, matches) = await ComplexifyParentingStatements(document, matches, cancellationToken).ConfigureAwait(false);
// Our original expression should have been one of the matches, which were tracked as part
// of complexification, so we can retrieve the latest version of the expression here.
......
......@@ -212,6 +212,8 @@
<Compile Include="Simplification\AbstractCSharpReducer.AbstractExpressionRewriter.cs" />
<Compile Include="Simplification\AbstractCSharpReducer.cs" />
<Compile Include="Simplification\CSharpCastReducer.cs" />
<Compile Include="Simplification\CSharpInferredMemberNameReducer.cs" />
<Compile Include="Simplification\CSharpInferredMemberNameReducer.Rewriter.cs" />
<Compile Include="Simplification\CSharpCastReducer.Rewriter.cs" />
<Compile Include="Simplification\CSharpEscapingReducer.cs" />
<Compile Include="Simplification\CSharpEscapingReducer.Rewriter.cs" />
......
......@@ -115,6 +115,21 @@ public static bool CanRemoveParentheses(this ParenthesizedExpressionSyntax node,
return true;
}
// Cases:
// new {(x)} -> {x}
// new { a = (x)} -> { a = x }
// new { a = (x = c)} -> { a = x = c }
if (node.Parent is AnonymousObjectMemberDeclaratorSyntax anonymousDeclarator)
{
// Assignment expressions are not allowed unless member is named
if (anonymousDeclarator.NameEquals == null && expression.IsAnyAssignExpression())
{
return false;
}
return true;
}
// Cases:
// where (x + 1 > 14) -> where x + 1 > 14
if (node.Parent is QueryClauseSyntax)
......
......@@ -46,6 +46,16 @@ private static SyntaxNode GetParentNode(SyntaxNode node)
return GetParentNode(cref);
}
if (node.IsKind(SyntaxKind.Argument) && node.Parent.IsKind(SyntaxKind.TupleExpression))
{
return node.Parent;
}
if (node.IsKind(SyntaxKind.AnonymousObjectMemberDeclarator))
{
return node.Parent;
}
return null;
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.CodeAnalysis.CSharp.Simplification
{
internal partial class CSharpInferredMemberNameReducer
{
private class Rewriter : AbstractExpressionRewriter
{
public Rewriter(OptionSet optionSet, CancellationToken cancellationToken)
: base(optionSet, cancellationToken)
{
}
public override SyntaxNode VisitArgument(ArgumentSyntax node)
{
return SimplifyExpression(
node,
newNode: base.VisitArgument(node),
simplifier: SimplifyTupleName);
}
public override SyntaxNode VisitAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax node)
{
return SimplifyExpression(
node,
newNode: base.VisitAnonymousObjectMemberDeclarator(node),
simplifier: SimplifyAnonymousTypeMemberName);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.CodeAnalysis.CSharp.Simplification
{
internal partial class CSharpInferredMemberNameReducer : AbstractCSharpReducer
{
public override IExpressionRewriter CreateExpressionRewriter(OptionSet optionSet, CancellationToken cancellationToken)
{
return new Rewriter(optionSet, cancellationToken);
}
private static ArgumentSyntax SimplifyTupleName(ArgumentSyntax node, SemanticModel semanticModel, OptionSet optionSet, CancellationToken cancellationToken)
{
if (node.NameColon == null || !node.IsParentKind(SyntaxKind.TupleExpression))
{
return node;
}
var inferredName = node.Expression.TryGetInferredMemberName();
if (inferredName == null || inferredName != node.NameColon.Name.Identifier.ValueText)
{
return node;
}
return node.WithNameColon(null).WithTriviaFrom(node);
}
private static SyntaxNode SimplifyAnonymousTypeMemberName(AnonymousObjectMemberDeclaratorSyntax node, SemanticModel semanticModel, OptionSet optionSet, CancellationToken canellationToken)
{
if (node.NameEquals == null)
{
return node;
}
var inferredName = node.Expression.TryGetInferredMemberName();
if (inferredName == null || inferredName != node.NameEquals.Name.Identifier.ValueText)
{
return node;
}
return node.WithNameEquals(null).WithTriviaFrom(node);
}
}
}
......@@ -257,6 +257,24 @@ public override SyntaxNode VisitArgument(ArgumentSyntax node)
var newArgument = (ArgumentSyntax)base.VisitArgument(node);
if (node.NameColon == null
&& node.Parent is TupleExpressionSyntax tuple
&& !IsTupleInDeconstruction(tuple)) // The language currently does not allow explicit element names in deconstruction
{
var inferredName = node.Expression.TryGetInferredMemberName();
if (CanMakeNameExplicitInTuple(tuple, inferredName))
{
var identifier = SyntaxFactory.Identifier(inferredName);
identifier = TryEscapeIdentifierToken(identifier, node, _semanticModel);
newArgument = newArgument
.WithoutLeadingTrivia()
.WithNameColon(SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(identifier)))
.WithAdditionalAnnotations(Simplifier.Annotation)
.WithLeadingTrivia(node.GetLeadingTrivia());
}
}
var argumentType = _semanticModel.GetTypeInfo(node.Expression).ConvertedType;
if (argumentType != null &&
!IsPassedToDelegateCreationExpression(node, argumentType) &&
......@@ -271,6 +289,62 @@ public override SyntaxNode VisitArgument(ArgumentSyntax node)
return newArgument;
}
private static bool CanMakeNameExplicitInTuple(TupleExpressionSyntax tuple, string name)
{
if (name == null || SyntaxFacts.IsReservedTupleElementName(name))
{
return false;
}
bool found = false;
foreach (var argument in tuple.Arguments)
{
string elementName = null;
if (argument.NameColon != null)
{
elementName = argument.NameColon.Name.Identifier.ValueText;
}
else
{
elementName = argument.Expression?.TryGetInferredMemberName();
}
if (elementName?.Equals(name, StringComparison.Ordinal) == true)
{
if (found)
{
// No duplicate names allowed
return false;
}
found = true;
}
}
return true;
}
public override SyntaxNode VisitAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax node)
{
var newDeclarator = (AnonymousObjectMemberDeclaratorSyntax)base.VisitAnonymousObjectMemberDeclarator(node);
if (node.NameEquals == null)
{
var inferredName = node.Expression.TryGetInferredMemberName();
if (inferredName != null)
{
var identifier = SyntaxFactory.Identifier(inferredName);
identifier = TryEscapeIdentifierToken(identifier, node, _semanticModel);
newDeclarator = newDeclarator
.WithoutLeadingTrivia()
.WithNameEquals(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(identifier))
.WithLeadingTrivia(node.GetLeadingTrivia()))
.WithAdditionalAnnotations(Simplifier.Annotation);
}
}
return newDeclarator;
}
public override SyntaxNode VisitBinaryExpression(BinaryExpressionSyntax node)
{
_cancellationToken.ThrowIfCancellationRequested();
......
......@@ -30,7 +30,8 @@ internal partial class CSharpSimplificationService : AbstractSimplificationServi
new CSharpExtensionMethodReducer(),
new CSharpParenthesesReducer(),
new CSharpEscapingReducer(),
new CSharpMiscellaneousReducer());
new CSharpMiscellaneousReducer(),
new CSharpInferredMemberNameReducer());
public CSharpSimplificationService() : base(s_reducers)
{
......@@ -90,6 +91,11 @@ public static SyntaxToken TryEscapeIdentifierToken(SyntaxToken syntaxToken, Synt
return syntaxToken;
}
if (SyntaxFacts.GetContextualKeywordKind(syntaxToken.ValueText) == SyntaxKind.UnderscoreToken)
{
return syntaxToken;
}
var parent = parentOfToken.Parent;
if (parentOfToken is SimpleNameSyntax && parent.Kind() == SyntaxKind.XmlNameAttribute)
{
......@@ -184,5 +190,34 @@ protected override void GetUnusedNamespaceImports(SemanticModel model, HashSet<S
}
}
}
// Is the tuple on either side of a deconstruction (top-level or nested)?
private static bool IsTupleInDeconstruction(SyntaxNode tuple)
{
Contract.Assert(tuple.IsKind(SyntaxKind.TupleExpression));
var currentTuple = tuple;
do
{
var parent = currentTuple.Parent;
if (parent.IsKind(SyntaxKind.SimpleAssignmentExpression))
{
return true;
}
if (!parent.IsKind(SyntaxKind.Argument))
{
return false;
}
var grandParent = parent.Parent;
if (!grandParent.IsKind(SyntaxKind.TupleExpression))
{
return false;
}
currentTuple = grandParent;
}
while (true);
}
}
}
......@@ -17,11 +17,12 @@ namespace Microsoft.CodeAnalysis.Simplification
/// Expands and Reduces subtrees.
///
/// Expansion:
/// 1) Replaces names with fully qualified dotted names.
/// 2) Adds parentheses around expressions
/// 3) Adds explicit casts/conversions where implicit conversions exist
/// 4) Adds escaping to identifiers
/// 5) Rewrites extension method invocations with explicit calls on the class containing the extension method.
/// 1) Makes inferred names explicit (on anoymous types and tuples).
/// 2) Replaces names with fully qualified dotted names.
/// 3) Adds parentheses around expressions
/// 4) Adds explicit casts/conversions where implicit conversions exist
/// 5) Adds escaping to identifiers
/// 6) Rewrites extension method invocations with explicit calls on the class containing the extension method.
///
/// Reduction:
/// 1) Shortens dotted names to their minimally qualified form
......@@ -29,6 +30,7 @@ namespace Microsoft.CodeAnalysis.Simplification
/// 3) Removes unnecessary casts/conversions
/// 4) Removes unnecessary escaping
/// 5) Rewrites explicit calls to extension methods to use dot notation
/// 6) Removes unnecessary tuple element names and anonymous type member names
/// </summary>
public static partial class Simplifier
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册