From 0f5b10ac92573603591f623a5e55f1f87fae70e5 Mon Sep 17 00:00:00 2001 From: Kevin Pilch-Bisson Date: Tue, 5 Jul 2016 19:58:03 -0700 Subject: [PATCH] Rework SymbolId handling of generics 1. Use ConstructedFrom instead of OriginalDefinition to handle partially constructed types. 2. Always encode type parameter references using `n or ``n, since they can only refer to type parameters within the current symbol being encoded. 3. When *parsing* SymbolIds, *do* set the type parameter context when parsing types. That way we can handle self-constructed types. However, we don't want to reset the context when we're parsing out the type of a parameter/return type/etc, because the signature *can't* refer to the type parameters of *that* simple. So, now we keep track of when the parser has finished parsing the top level type, and only reset the type parameter context while we're at the top level. 4. Add test cases for a bunch of partially constructed symbols. (While I was here, I noticed that a GetMembers() call could be simplified to GetTypeMembers() and a type check/cast elimiated, so I did so. The diff looks better with whitespace ignored because of that). --- .../Core/Portable/SymbolId/SymbolId.cs | 116 +++++++----------- src/Workspaces/CoreTest/SymbolIdTests.cs | 63 +++++++++- 2 files changed, 104 insertions(+), 75 deletions(-) diff --git a/src/Workspaces/Core/Portable/SymbolId/SymbolId.cs b/src/Workspaces/Core/Portable/SymbolId/SymbolId.cs index 5362ce963a7..51536b90f68 100644 --- a/src/Workspaces/Core/Portable/SymbolId/SymbolId.cs +++ b/src/Workspaces/Core/Portable/SymbolId/SymbolId.cs @@ -47,7 +47,7 @@ public static string CreateId(ISymbol symbol) } var builder = new StringBuilder(); - var generator = new Generator(builder, typeParameterContext: null); + var generator = new Generator(builder); generator.Visit(symbol); return builder.ToString(); } @@ -202,12 +202,10 @@ private static string DecodePropertyName(string name, string language) private struct Generator { private readonly StringBuilder _builder; - private readonly ISymbol _typeParameterContext; - public Generator(StringBuilder builder, ISymbol typeParameterContext) + public Generator(StringBuilder builder) { _builder = builder; - _typeParameterContext = typeParameterContext; } public bool Visit(ISymbol symbol) @@ -363,7 +361,7 @@ private void EncodeGenericMethodInfo(IMethodSymbol symbol) { if (symbol.IsGenericMethod) { - if (object.ReferenceEquals(symbol.OriginalDefinition, symbol)) + if (Equals(symbol, symbol.ConstructedFrom)) { if (symbol.TypeParameters.Length > 0) { @@ -382,7 +380,7 @@ private void EncodeGenericMethodInfo(IMethodSymbol symbol) _builder.Append(","); } - new Generator(_builder, symbol.ConstructedFrom).Visit(symbol.TypeArguments[i]); + Visit(symbol.TypeArguments[i]); } _builder.Append("}"); @@ -404,7 +402,7 @@ private void EncodeParameters(ImmutableArray parameters) } var p = parameters[i]; - new Generator(_builder, p.ContainingSymbol).Visit(p.Type); + Visit(p.Type); if (p.RefKind != RefKind.None) { _builder.Append("@"); @@ -467,7 +465,7 @@ private void EncodeGenericTypeInfo(INamedTypeSymbol symbol) { if (symbol.IsGenericType) { - if (Equals(symbol.OriginalDefinition, symbol)) + if (Equals(symbol, symbol.ConstructedFrom)) { _builder.Append("`"); _builder.Append(symbol.TypeParameters.Length); @@ -485,14 +483,7 @@ private void EncodeGenericTypeInfo(INamedTypeSymbol symbol) // If we already have a type parameter context (say because we're encoding parameters of a generic method), then use it. // Otherwise, use the current type as the context, so that it's type parameters resolve - if (_typeParameterContext == null) - { - new Generator(_builder, symbol).Visit(symbol.TypeArguments[i]); - } - else - { - this.Visit(symbol.TypeArguments[i]); - } + this.Visit(symbol.TypeArguments[i]); } _builder.Append("}"); @@ -539,15 +530,6 @@ private bool VisitPointerType(IPointerTypeSymbol symbol) private bool VisitTypeParameter(ITypeParameterSymbol symbol) { - if (!IsInScope(symbol)) - { - // reference to type parameter not in scope, make explicit scope reference - if (this.Visit(symbol.ContainingSymbol)) - { - _builder.Append(":"); - } - } - if (symbol.DeclaringMethod != null) { _builder.Append("``"); @@ -565,22 +547,6 @@ private bool VisitTypeParameter(ITypeParameterSymbol symbol) return true; } - private bool IsInScope(ITypeParameterSymbol typeParameterSymbol) - { - // determine if the type parameter is declared in scope defined by the typeParameterContext symbol - var typeParameterDeclarer = typeParameterSymbol.ContainingSymbol; - - for (var scope = _typeParameterContext; scope != null; scope = scope.ContainingSymbol) - { - if (Equals(scope, typeParameterDeclarer)) - { - return true; - } - } - - return false; - } - private bool VisitLocal(ILocalSymbol symbol) { if (this.Visit(symbol.ContainingSymbol)) @@ -806,6 +772,7 @@ private struct Parser private readonly string _id; private readonly Compilation _compilation; private ISymbol _typeParameterContext; + private bool _inSignature; private int _index; private Parser(string id, int index, Compilation compilation, ISymbol typeParameterContext) @@ -814,6 +781,7 @@ private Parser(string id, int index, Compilation compilation, ISymbol typeParame _compilation = compilation; _typeParameterContext = typeParameterContext; _index = index; + _inSignature = false; } public static bool Parse(string id, Compilation compilation, List results) @@ -882,18 +850,22 @@ private void ParseNamedSymbol(List results) switch (prefix) { case MethodPrefix: + _inSignature = true; GetMatchingMethods(containers, name, results); break; case NamedTypePrefix: GetMatchingTypes(containers, name, results); break; case PropertyPrefix: + _inSignature = true; GetMatchingProperties(containers, name, results); break; case EventPrefix: + _inSignature = true; GetMatchingEvents(containers, name, results); break; case FieldPrefix: + _inSignature = true; GetMatchingFields(containers, name, results); break; case NamespacePrefix: @@ -1258,55 +1230,56 @@ private void GetMatchingTypes(IReadOnlyList containers, string name, Li private void GetMatchingTypes(INamespaceOrTypeSymbol container, string name, List results) { + var originalContext = _typeParameterContext; var typeArguments = s_symbolListPool.Allocate(); try { var startIndex = _index; var endIndex = _index; - var members = container.GetMembers(name); + var members = container.GetTypeMembers(name); foreach (var symbol in members) { - if (symbol.Kind == SymbolKind.NamedType) - { - var namedType = (INamedTypeSymbol)symbol; - _index = startIndex; + var namedType = symbol; + _index = startIndex; - // has type arguments? - if (PeekNextChar() == '{') + // has type arguments? + if (PeekNextChar() == '{') + { + if (!_inSignature) { - //_typeParameterContext = namedType; + _typeParameterContext = namedType; + } - typeArguments.Clear(); - ParseTypeArguments(typeArguments); + typeArguments.Clear(); + ParseTypeArguments(typeArguments); - if (namedType.Arity != typeArguments.Count) - { - // if no type arguments are found then the type cannot be identified - continue; - } - - namedType = namedType.Construct(typeArguments.Cast().ToArray()); - } - // has type parameters? - else if (PeekNextChar() == '`') + if (namedType.Arity != typeArguments.Count) { - _index++; - var arity = ParseIntegerLiteral(); - if (arity != namedType.Arity) - { - continue; - } + // if no type arguments are found then the type cannot be identified + continue; } - else if (namedType.Arity > 0) + + namedType = namedType.Construct(typeArguments.Cast().ToArray()); + } + // has type parameters? + else if (PeekNextChar() == '`') + { + _index++; + var arity = ParseIntegerLiteral(); + if (arity != namedType.Arity) { continue; } - - endIndex = _index; - results.Add(namedType); } + else if (namedType.Arity > 0) + { + continue; + } + + endIndex = _index; + results.Add(namedType); } _index = endIndex; @@ -1314,6 +1287,7 @@ private void GetMatchingTypes(INamespaceOrTypeSymbol container, string name, Lis finally { s_symbolListPool.ClearAndFree(typeArguments); + _typeParameterContext = originalContext; } } diff --git a/src/Workspaces/CoreTest/SymbolIdTests.cs b/src/Workspaces/CoreTest/SymbolIdTests.cs index 7f429b16643..979dc7ede13 100644 --- a/src/Workspaces/CoreTest/SymbolIdTests.cs +++ b/src/Workspaces/CoreTest/SymbolIdTests.cs @@ -446,8 +446,58 @@ public class B var outer = GetDeclaredSymbols(compilation).OfType().First(s => s.Name == "A"); var constructed = outer.Construct(compilation.GetSpecialType(SpecialType.System_String)); var inner = constructed.GetTypeMembers().Single(); - var id = SymbolId.CreateId(inner); - Assert.Equal("T:A{N:System.T:String}.T:B{`1}", id); + TestRoundTrip(inner, compilation, "T:A{N:System.T:String}.T:B`1"); + } + + [Fact, WorkItem(235912, "https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=235912&_a=edit")] + public void TestNestedGenericType1() + { + var source = @" +using System.Collections.Generic; + +public class A +{ + public class B + { + void M(T1 t1, T2, T3 t3, List l1, List l2) { } + } +}"; + var compilation = GetCompilation(source); + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var a = GetDeclaredSymbols(compilation).OfType().Single(s => s.Name == "A"); + var a_b = a.GetTypeMembers().Single(); + var a_b_m = a_b.GetMembers().Single(s => s.Name == "M"); + + TestRoundTrip(a, compilation, "T:A`1"); + TestRoundTrip(a_b, compilation, "T:A`1.T:B`1"); + TestRoundTrip(a_b_m, compilation, "T:A`1.T:B`1.M:M`1(`0,`1,``0,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{``0})"); + + var a_string = a.Construct(compilation.GetSpecialType(SpecialType.System_String)); + var a_string_b = a_string.GetTypeMembers().Single(); + var a_string_b_m = a_string_b.GetMembers().Single(s => s.Name == "M"); + TestRoundTrip(a_string, compilation, "T:A{N:System.T:String}"); + TestRoundTrip(a_string_b, compilation, "T:A{N:System.T:String}.T:B`1"); + TestRoundTrip(a_string_b_m, compilation, "T:A{N:System.T:String}.T:B`1.M:M`1(N:System.T:String,`1,``0,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{``0})"); + + var a_string_b_int = a_string_b.Construct(compilation.GetSpecialType(SpecialType.System_Int32)); + var a_string_b_int_m = a_string_b_int.GetMembers().Single(s => s.Name == "M"); + TestRoundTrip(a_string_b_int, compilation, "T:A{N:System.T:String}.T:B{N:System.T:Int32}"); + TestRoundTrip(a_string_b_int_m, compilation, "T:A{N:System.T:String}.T:B{N:System.T:Int32}.M:M`1(N:System.T:String,N:System.T:Int32,``0,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{``0})"); + + var a_string_b_int_m_datetime = ((IMethodSymbol)a_string_b_int_m).Construct(compilation.GetSpecialType(SpecialType.System_DateTime)); + TestRoundTrip(a_string_b_int_m_datetime, compilation, "T:A{N:System.T:String}.T:B{N:System.T:Int32}.M:M{N:System.T:DateTime}(N:System.T:String,N:System.T:Int32,N:System.T:DateTime,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{N:System.T:DateTime})"); + + var a_b_int = a_b.Construct(compilation.GetSpecialType(SpecialType.System_Int32)); + var a_b_int_m = a_b_int.GetMembers().Single(s => s.Name == "M"); + var a_b_int_m_datetime = ((IMethodSymbol)a_b_int_m).Construct(compilation.GetSpecialType(SpecialType.System_DateTime)); + TestRoundTrip(a_b_int, compilation, "T:A`1.T:B{N:System.T:Int32}"); + TestRoundTrip(a_b_int_m, compilation, "T:A`1.T:B{N:System.T:Int32}.M:M`1(`0,N:System.T:Int32,``0,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{``0})"); + TestRoundTrip(a_b_int_m_datetime, compilation, "T:A`1.T:B{N:System.T:Int32}.M:M{N:System.T:DateTime}(`0,N:System.T:Int32,N:System.T:DateTime,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{N:System.T:DateTime})"); + + var a_b_m_datetime = ((IMethodSymbol)a_b_m).Construct(compilation.GetSpecialType(SpecialType.System_DateTime)); + TestRoundTrip(a_b_m_datetime, compilation, "T:A`1.T:B`1.M:M{N:System.T:DateTime}(`0,`1,N:System.T:DateTime,N:System.N:Collections.N:Generic.T:List{N:System.T:Int32},N:System.N:Collections.N:Generic.T:List{N:System.T:DateTime})"); } [Fact, WorkItem(11193, "https://github.com/dotnet/roslyn/issues/11193")] @@ -500,14 +550,19 @@ private void TestRoundTrip(IEnumerable symbols, Compilation compilation { foreach (var symbol in symbols) { - TestRoundTrip(symbol, compilation, fnId); + TestRoundTrip(symbol, compilation, fnId: fnId); } } - private void TestRoundTrip(ISymbol symbol, Compilation compilation, Func fnId = null) + private void TestRoundTrip(ISymbol symbol, Compilation compilation, string expectedId = null, Func fnId = null) { var id = SymbolId.CreateId(symbol); Assert.NotNull(id); + if (expectedId != null) + { + Assert.Equal(expectedId, id); + } + var found = SymbolId.GetFirstSymbolForId(id, compilation); Assert.NotNull(found); -- GitLab