Skip to content

Commit

Permalink
Merge pull request #7646 from hvitved/csharp/roslyn-tuple-elements-wo…
Browse files Browse the repository at this point in the history
…rkaround

C#: Workaround Roslyn bug in `INamedTypeSymbol.TupleElements`
  • Loading branch information
hvitved authored Jan 19, 2022
2 parents 40c8881 + dacb33d commit 70f4efb
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ protected override void Populate(TextWriter trapFile)
var tts = (TupleTypeSyntax)syntax;
var tt = (TupleType)type;
Emit(trapFile, loc ?? syntax.GetLocation(), parent, type);
tts.Elements.Zip(tt.TupleElements, (s, t) => Create(Context, s.Type, this, t.Type)).Enumerate();
foreach (var (s, t) in tts.Elements.Zip(tt.TupleElements, (s, t) => (s, t?.Type)))
{
if (t is not null)
Create(Context, s.Type, this, t);
}
return;
case SyntaxKind.GenericName:
Emit(trapFile, loc ?? syntax.GetLocation(), parent, type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private class TupleTypeFactory : CachedEntityFactory<INamedTypeSymbol, TupleType

private TupleType(Context cx, INamedTypeSymbol init) : base(cx, init)
{
tupleElementsLazy = new Lazy<Field[]>(() => Symbol.TupleElements.Select(t => Field.Create(cx, t)).ToArray());
tupleElementsLazy = new Lazy<Field?[]>(() => Symbol.GetTupleElementsMaybeNull().Select(t => t is null ? null : Field.Create(cx, t)).ToArray());
}

// All tuple types are "local types"
Expand All @@ -47,7 +47,10 @@ public override void Populate(TextWriter trapFile)

var index = 0;
foreach (var element in TupleElements)
trapFile.tuple_element(this, index++, element);
{
if (element is not null)
trapFile.tuple_element(this, index++, element);
}

// Note: symbol.Locations seems to be very inconsistent
// about what locations are available for a tuple type.
Expand All @@ -56,9 +59,10 @@ public override void Populate(TextWriter trapFile)
trapFile.type_location(this, Context.CreateLocation(l));
}

private readonly Lazy<Field[]> tupleElementsLazy;
public Field[] TupleElements => tupleElementsLazy.Value;
private readonly Lazy<Field?[]> tupleElementsLazy;
public Field?[] TupleElements => tupleElementsLazy.Value;

public override IEnumerable<Type> TypeMentions => TupleElements.Select(e => e.Type);
public override IEnumerable<Type> TypeMentions =>
TupleElements.OfType<Field>().Select(e => e.Type);
}
}
33 changes: 26 additions & 7 deletions csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,30 @@ private static void BuildAssembly(IAssemblySymbol asm, EscapingTextWriter trapFi
private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol funptr, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined) =>
BuildFunctionPointerSignature(funptr, trapFile, s => s.BuildOrWriteId(cx, trapFile, symbolBeingDefined));

/// <summary>
/// Workaround for a Roslyn bug: https://github.com/dotnet/roslyn/issues/53943
/// </summary>
public static IEnumerable<IFieldSymbol?> GetTupleElementsMaybeNull(this INamedTypeSymbol type) =>
type.TupleElements;

private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && named.IsTupleType)
{
trapFile.Write('(');
trapFile.BuildList(",", named.TupleElements,
f =>
trapFile.BuildList(",", named.GetTupleElementsMaybeNull(),
(i, f) =>
{
trapFile.Write((f.CorrespondingTupleField ?? f).Name);
trapFile.Write(":");
f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
if (f is null)
{
trapFile.Write($"null({i})");
}
else
{
trapFile.Write((f.CorrespondingTupleField ?? f).Name);
trapFile.Write(":");
f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
}
}
);
trapFile.Write(")");
Expand Down Expand Up @@ -464,8 +477,14 @@ private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, C
trapFile.Write('(');
trapFile.BuildList(
",",
namedType.TupleElements.Select(f => f.Type),
t => t.BuildDisplayName(cx, trapFile));
namedType.GetTupleElementsMaybeNull(),
(i, f) =>
{
if (f is null)
trapFile.Write($"null({i})");
else
f.Type.BuildDisplayName(cx, trapFile);
});
trapFile.Write(")");
return;
}
Expand Down
18 changes: 16 additions & 2 deletions csharp/extractor/Semmle.Extraction/TrapExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,19 +232,33 @@ public static TextWriter AppendList<T>(this EscapingTextWriter trapFile, string
/// <param name="items">The list of items.</param>
/// <param name="action">The action on each item.</param>
/// <returns>The original trap builder (fluent interface).</returns>
public static T1 BuildList<T1, T2>(this T1 trapFile, string separator, IEnumerable<T2> items, Action<T2> action)
public static T1 BuildList<T1, T2>(this T1 trapFile, string separator, IEnumerable<T2> items, Action<int, T2> action)
where T1 : TextWriter
{
var first = true;
var i = 0;
foreach (var item in items)
{
if (first)
first = false;
else
trapFile.Write(separator);
action(item);
action(i++, item);
}
return trapFile;
}

/// <summary>
/// Builds a trap builder using a separator and an action for each item in the list.
/// </summary>
/// <typeparam name="T">The type of the items.</typeparam>
/// <param name="trapFile">The trap builder to append to.</param>
/// <param name="separator">The separator string (e.g. ",")</param>
/// <param name="items">The list of items.</param>
/// <param name="action">The action on each item.</param>
/// <returns>The original trap builder (fluent interface).</returns>
public static T1 BuildList<T1, T2>(this T1 trapFile, string separator, IEnumerable<T2> items, Action<T2> action)
where T1 : TextWriter =>
trapFile.BuildList(separator, items, (_, item) => action(item));
}
}

0 comments on commit 70f4efb

Please sign in to comment.