C#: Explicitly handle underlying tuple types

This commit is contained in:
Tamas Vajk
2020-09-07 15:46:53 +02:00
committed by Tom Hvitved
parent 221b92de04
commit 643a8b57c3
6 changed files with 53 additions and 29 deletions

View File

@@ -11,13 +11,20 @@ namespace Semmle.Extraction.CSharp.Entities
{
class NamedType : Type<INamedTypeSymbol>
{
NamedType(Context cx, INamedTypeSymbol init)
NamedType(Context cx, INamedTypeSymbol init, bool fromTuple)
: base(cx, init)
{
typeArgumentsLazy = new Lazy<Type[]>(() => symbol.TypeArguments.Select(t => Create(cx, t)).ToArray());
this.fromTuple = fromTuple;
}
public static NamedType Create(Context cx, INamedTypeSymbol type) => NamedTypeFactory.Instance.CreateEntityFromSymbol(cx, type);
public static NamedType Create(Context cx, INamedTypeSymbol type) =>
type.IsTupleType
? NamedTupleTypeFactory.Instance.CreateEntity(cx, (type, false), (type, false))
: NamedTypeFactory.Instance.CreateEntityFromSymbol(cx, type);
public static NamedType CreateNamedTypeFromTupleType(Context cx, INamedTypeSymbol type) =>
NamedTupleTypeFactory.Instance.CreateEntity(cx, (type, true), (type, true));
public override bool NeedsPopulation => base.NeedsPopulation || symbol.TypeKind == TypeKind.Error;
@@ -51,7 +58,8 @@ namespace Semmle.Extraction.CSharp.Entities
}
else
{
trapFile.constructed_generic(this, Type.Create(Context, symbol.ConstructedFrom).TypeRef);
var unbound = fromTuple ? CreateNamedTypeFromTupleType(Context, symbol.ConstructedFrom) : Type.Create(Context, symbol.ConstructedFrom);
trapFile.constructed_generic(this, unbound.TypeRef);
for (int i = 0; i < symbol.TypeArguments.Length; ++i)
{
@@ -60,7 +68,7 @@ namespace Semmle.Extraction.CSharp.Entities
}
}
PopulateType(trapFile);
PopulateType(trapFile, getTupleAsTuple: !fromTuple);
if (symbol.EnumUnderlyingType != null)
{
@@ -76,6 +84,8 @@ namespace Semmle.Extraction.CSharp.Entities
}
readonly Lazy<Type[]> typeArgumentsLazy;
private readonly bool fromTuple;
public Type[] TypeArguments => typeArgumentsLazy.Value;
public override IEnumerable<Type> TypeMentions => TypeArguments;
@@ -115,7 +125,7 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.Write('*');
else
{
symbol.BuildTypeId(Context, trapFile, symbol);
symbol.BuildTypeId(Context, trapFile, symbol, getTupleAsTuple: !fromTuple);
trapFile.Write(";type");
}
}
@@ -161,7 +171,14 @@ namespace Semmle.Extraction.CSharp.Entities
{
public static readonly NamedTypeFactory Instance = new NamedTypeFactory();
public NamedType Create(Context cx, INamedTypeSymbol init) => new NamedType(cx, init);
public NamedType Create(Context cx, INamedTypeSymbol init) => new NamedType(cx, init, false);
}
class NamedTupleTypeFactory : ICachedEntityFactory<(INamedTypeSymbol, bool), NamedType>
{
public static readonly NamedTupleTypeFactory Instance = new NamedTupleTypeFactory();
public NamedType Create(Context cx, (INamedTypeSymbol, bool) init) => new NamedType(cx, init.Item1, init.Item2);
}
// Do not create typerefs of constructed generics as they are always in the current trap file.

View File

@@ -41,7 +41,8 @@ namespace Semmle.Extraction.CSharp.Entities
PopulateType(trapFile);
PopulateGenerics();
var underlyingType = NamedType.Create(Context, symbol.TupleUnderlyingType);
var underlyingType = NamedType.CreateNamedTypeFromTupleType(Context, symbol.TupleUnderlyingType ?? symbol);
trapFile.tuple_underlying_type(this, underlyingType);
int index = 0;

View File

@@ -47,7 +47,7 @@ namespace Semmle.Extraction.CSharp.Entities
symbol.ContainingType != null && ConstructedOrParentIsConstructed(symbol.ContainingType);
}
static Kinds.TypeKind GetClassType(Context cx, ITypeSymbol t)
static Kinds.TypeKind GetClassType(Context cx, ITypeSymbol t, bool getTupleAsTuple)
{
switch (t.SpecialType)
{
@@ -72,7 +72,9 @@ namespace Semmle.Extraction.CSharp.Entities
{
case TypeKind.Class: return Kinds.TypeKind.CLASS;
case TypeKind.Struct:
return ((INamedTypeSymbol)t).IsTupleType ? Kinds.TypeKind.TUPLE : Kinds.TypeKind.STRUCT;
return ((INamedTypeSymbol)t).IsTupleType && getTupleAsTuple
? Kinds.TypeKind.TUPLE
: Kinds.TypeKind.STRUCT;
case TypeKind.Interface: return Kinds.TypeKind.INTERFACE;
case TypeKind.Array: return Kinds.TypeKind.ARRAY;
case TypeKind.Enum: return Kinds.TypeKind.ENUM;
@@ -85,7 +87,7 @@ namespace Semmle.Extraction.CSharp.Entities
}
}
protected void PopulateType(TextWriter trapFile)
protected void PopulateType(TextWriter trapFile, bool getTupleAsTuple = true)
{
PopulateMetadataHandle(trapFile);
PopulateAttributes();
@@ -93,9 +95,9 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.Write("types(");
trapFile.WriteColumn(this);
trapFile.Write(',');
trapFile.WriteColumn((int)GetClassType(Context, symbol));
trapFile.WriteColumn((int)GetClassType(Context, symbol, getTupleAsTuple));
trapFile.Write(",\"");
symbol.BuildDisplayName(Context, trapFile);
symbol.BuildDisplayName(Context, trapFile, getTupleAsTuple);
trapFile.WriteLine("\")");
// Visit base types

View File

@@ -117,7 +117,7 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Delegate:
case TypeKind.Error:
var named = (INamedTypeSymbol)type;
if (named.IsTupleType)
if (named.IsTupleType && named.TupleUnderlyingType is object)
named = named.TupleUnderlyingType;
if (IdDependsOnImpl(named.ContainingType))
return true;
@@ -152,10 +152,10 @@ namespace Semmle.Extraction.CSharp
/// <param name="cx">The extraction context.</param>
/// <param name="trapFile">The trap builder used to store the result.</param>
/// <param name="symbolBeingDefined">The outer symbol being defined (to avoid recursive ids).</param>
public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined) =>
type.BuildTypeId(cx, trapFile, symbolBeingDefined, true);
public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool getTupleAsTuple = true) =>
type.BuildTypeId(cx, trapFile, symbolBeingDefined, true, getTupleAsTuple);
static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass)
static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool getTupleAsTuple)
{
using (cx.StackGuard)
{
@@ -173,7 +173,7 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Delegate:
case TypeKind.Error:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, addBaseClass);
named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, addBaseClass, getTupleAsTuple);
return;
case TypeKind.Pointer:
var ptr = (IPointerTypeSymbol)type;
@@ -195,7 +195,7 @@ namespace Semmle.Extraction.CSharp
}
}
static void BuildOrWriteId(this ISymbol symbol, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass)
static void BuildOrWriteId(this ISymbol symbol, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool getTupleAsTuple = true)
{
// We need to keep track of the symbol being defined in order to avoid cyclic labels.
// For example, in
@@ -210,11 +210,13 @@ namespace Semmle.Extraction.CSharp
//
// ```
// #123 = @"C`1 : IEnumerable<__self___T>"
// ```
// ```
if (SymbolEqualityComparer.Default.Equals(symbol, symbolBeingDefined))
trapFile.Write("__self__");
else if (symbol is ITypeSymbol type && type.IdDependsOn(cx, symbolBeingDefined))
type.BuildTypeId(cx, trapFile, symbolBeingDefined, addBaseClass);
type.BuildTypeId(cx, trapFile, symbolBeingDefined, addBaseClass, getTupleAsTuple);
else if (symbol is INamedTypeSymbol namedType && namedType.IsTupleType && !getTupleAsTuple)
trapFile.WriteSubId(NamedType.CreateNamedTypeFromTupleType(cx, namedType));
else
trapFile.WriteSubId(CreateEntity(cx, symbol));
}
@@ -262,9 +264,9 @@ namespace Semmle.Extraction.CSharp
trapFile.Write("::");
}
static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass)
static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool getTupleAsTuple)
{
if (named.IsTupleType)
if (getTupleAsTuple && named.IsTupleType)
{
trapFile.Write('(');
trapFile.BuildList(",", named.TupleElements,
@@ -308,10 +310,10 @@ namespace Semmle.Extraction.CSharp
}
else
{
named.ConstructedFrom.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass);
named.ConstructedFrom.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass, getTupleAsTuple);
trapFile.Write('<');
// Encode the nullability of the type arguments in the label.
// Type arguments with different nullability can result in
// Type arguments with different nullability can result in
// a constructed type with different nullability of its members and methods,
// so we need to create a distinct database entity for it.
trapFile.BuildList(",", named.GetAnnotatedTypeArguments(),
@@ -360,7 +362,7 @@ namespace Semmle.Extraction.CSharp
/// Constructs a display name string for this type symbol.
/// </summary>
/// <param name="trapFile">The trap builder used to store the result.</param>
public static void BuildDisplayName(this ITypeSymbol type, Context cx, TextWriter trapFile)
public static void BuildDisplayName(this ITypeSymbol type, Context cx, TextWriter trapFile, bool getTupleAsTuple = true)
{
using (cx.StackGuard)
{
@@ -384,7 +386,7 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Delegate:
case TypeKind.Error:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeDisplayName(cx, trapFile);
named.BuildNamedTypeDisplayName(cx, trapFile, getTupleAsTuple);
return;
case TypeKind.Pointer:
var ptr = (IPointerTypeSymbol)type;
@@ -403,9 +405,9 @@ namespace Semmle.Extraction.CSharp
}
}
public static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile)
public static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile, bool getTupleAsTuple)
{
if (namedType.IsTupleType)
if (getTupleAsTuple && namedType.IsTupleType)
{
trapFile.Write('(');
trapFile.BuildList(",", namedType.TupleElements.Select(f => f.Type),