Merge pull request #21220 from michaelnebel/csharp14/extension

C# 14: Support `extension` types.
This commit is contained in:
Michael Nebel
2026-02-10 14:15:57 +01:00
committed by GitHub
49 changed files with 8623 additions and 126 deletions

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Semmle.Util;
using Semmle.Extraction.CSharp.Entities;
namespace Semmle.Extraction.CSharp
@@ -164,6 +165,7 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Enum:
case TypeKind.Delegate:
case TypeKind.Error:
case TypeKind.Extension:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType);
return;
@@ -275,6 +277,20 @@ namespace Semmle.Extraction.CSharp
public static IEnumerable<IFieldSymbol?> GetTupleElementsMaybeNull(this INamedTypeSymbol type) =>
type.TupleElements;
private static void BuildExtensionTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile)
{
trapFile.Write("extension(");
if (named.ExtensionMarkerName is not null)
{
trapFile.Write(named.ExtensionMarkerName);
}
else
{
trapFile.Write("unknown");
}
trapFile.Write(")");
}
private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
{
if (named.ContainingType is not null)
@@ -289,8 +305,18 @@ namespace Semmle.Extraction.CSharp
named.ContainingNamespace.BuildNamespace(cx, trapFile);
}
var name = named.IsFileLocal ? named.MetadataName : named.Name;
trapFile.Write(name);
if (named.IsFileLocal)
{
trapFile.Write(named.MetadataName);
}
else if (named.IsExtension)
{
named.BuildExtensionTypeId(cx, trapFile);
}
else
{
trapFile.Write(named.Name);
}
}
private static void BuildTupleId(INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined)
@@ -391,6 +417,7 @@ namespace Semmle.Extraction.CSharp
case TypeKind.Enum:
case TypeKind.Delegate:
case TypeKind.Error:
case TypeKind.Extension:
var named = (INamedTypeSymbol)type;
named.BuildNamedTypeDisplayName(cx, trapFile, constructUnderlyingTupleType);
return;
@@ -465,6 +492,20 @@ namespace Semmle.Extraction.CSharp
private static void BuildFunctionPointerTypeDisplayName(this IFunctionPointerTypeSymbol funptr, Context cx, TextWriter trapFile) =>
BuildFunctionPointerSignature(funptr, trapFile, s => s.BuildDisplayName(cx, trapFile));
private static void BuildExtensionTypeDisplayName(this INamedTypeSymbol named, Context cx, TextWriter trapFile)
{
trapFile.Write("extension(");
if (named.ExtensionParameter?.Type is ITypeSymbol type)
{
type.BuildDisplayName(cx, trapFile);
}
else
{
trapFile.Write("unknown");
}
trapFile.Write(")");
}
private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && namedType.IsTupleType)
@@ -484,6 +525,12 @@ namespace Semmle.Extraction.CSharp
return;
}
if (namedType.IsExtension)
{
namedType.BuildExtensionTypeDisplayName(cx, trapFile);
return;
}
if (namedType.IsAnonymousType)
{
namedType.BuildAnonymousName(cx, trapFile);
@@ -596,6 +643,84 @@ namespace Semmle.Extraction.CSharp
return true;
}
/// <summary>
/// Return true if this method is a compiler-generated extension method.
/// </summary>
public static bool IsCompilerGeneratedExtensionMethod(this IMethodSymbol method) =>
method.TryGetExtensionMethod() is not null;
/// <summary>
/// Returns the extension method corresponding to this compiler-generated extension method, if it exists.
/// </summary>
public static IMethodSymbol? TryGetExtensionMethod(this IMethodSymbol method)
{
if (method.IsImplicitlyDeclared && method.ContainingSymbol is INamedTypeSymbol containingType)
{
// Extension types are declared within the same type as the generated
// extension method implementation.
var extensions = containingType.GetMembers()
.OfType<INamedTypeSymbol>()
.Where(t => t.IsExtension);
// Find the (possibly unbound) original extension method that maps to this implementation (if any).
var unboundDeclaration = extensions.SelectMany(e => e.GetMembers())
.OfType<IMethodSymbol>()
.FirstOrDefault(m => SymbolEqualityComparer.Default.Equals(m.AssociatedExtensionImplementation, method.ConstructedFrom));
var isFullyConstructed = method.IsBoundGenericMethod();
if (isFullyConstructed && unboundDeclaration?.ContainingType is INamedTypeSymbol extensionType)
{
try
{
// Use the type arguments from the constructed extension method to construct the extension type.
var arguments = method.TypeArguments.ToArray();
var (extensionTypeArguments, extensionMethodArguments) = arguments.SplitAt(extensionType.TypeParameters.Length);
// Construct the extension type.
var boundExtensionType = extensionType.IsUnboundGenericType()
? extensionType.Construct(extensionTypeArguments.ToArray())
: extensionType;
// Find the extension method declaration within the constructed extension type.
var extensionDeclaration = boundExtensionType.GetMembers()
.OfType<IMethodSymbol>()
.First(c => SymbolEqualityComparer.Default.Equals(c.OriginalDefinition, unboundDeclaration));
// If the extension declaration is unbound apply the remaning type arguments and construct it.
return extensionDeclaration.IsUnboundGenericMethod()
? extensionDeclaration.Construct(extensionMethodArguments.ToArray())
: extensionDeclaration;
}
catch
{
// If anything goes wrong, fall back to the unbound declaration.
return unboundDeclaration;
}
}
else
{
return unboundDeclaration;
}
}
return null;
}
/// <summary>
/// Returns true if this method is an unbound generic method.
/// </summary>
public static bool IsUnboundGenericMethod(this IMethodSymbol method) =>
method.IsGenericMethod && SymbolEqualityComparer.Default.Equals(method.ConstructedFrom, method);
/// <summary>
/// Returns true if this method is a bound generic method.
/// </summary>
public static bool IsBoundGenericMethod(this IMethodSymbol method) => method.IsGenericMethod && !method.IsUnboundGenericMethod();
/// <summary>
/// Returns true if this type is an unbound generic type.
/// </summary>
public static bool IsUnboundGenericType(this INamedTypeSymbol type) =>
type.IsGenericType && SymbolEqualityComparer.Default.Equals(type.ConstructedFrom, type);
/// <summary>
/// Gets the base type of `symbol`. Unlike `symbol.BaseType`, this excludes effective base
/// types of type parameters as well as `object` base types.
@@ -692,5 +817,35 @@ namespace Semmle.Extraction.CSharp
/// </summary>
public static IEnumerable<T> ExtractionCandidates<T>(this IEnumerable<T> symbols) where T : ISymbol =>
symbols.Where(symbol => symbol.ShouldExtractSymbol());
/// <summary>
/// Returns the parameter kind for this parameter symbol, e.g. `ref`, `out`, `params`, etc.
/// </summary>
public static Parameter.Kind GetParameterKind(this IParameterSymbol parameter)
{
switch (parameter.RefKind)
{
case RefKind.Out:
return Parameter.Kind.Out;
case RefKind.Ref:
return Parameter.Kind.Ref;
case RefKind.In:
return Parameter.Kind.In;
case RefKind.RefReadOnlyParameter:
return Parameter.Kind.RefReadOnly;
default:
if (parameter.IsParams)
return Parameter.Kind.Params;
if (parameter.Ordinal == 0)
{
if (parameter.ContainingSymbol is IMethodSymbol method && method.IsExtensionMethod)
{
return Parameter.Kind.This;
}
}
return Parameter.Kind.None;
}
}
}
}

View File

@@ -54,22 +54,6 @@ namespace Semmle.Extraction.CSharp.Entities
}
}
protected static void WriteLocationToTrap<T1>(Action<T1, Location> writeAction, T1 entity, Location l)
{
if (l is not EmptyLocation)
{
writeAction(entity, l);
}
}
protected static void WriteLocationsToTrap<T1>(Action<T1, Location> writeAction, T1 entity, IEnumerable<Location> locations)
{
foreach (var loc in locations)
{
WriteLocationToTrap(writeAction, entity, loc);
}
}
public override bool NeedsPopulation { get; }
public override int GetHashCode() => Symbol is null ? 0 : Symbol.GetHashCode();

View File

@@ -32,32 +32,6 @@ namespace Semmle.Extraction.CSharp.Entities
Attribute.ExtractAttributes(Context, Symbol, this);
}
protected void PopulateNullability(TextWriter trapFile, AnnotatedTypeSymbol type)
{
var n = NullabilityEntity.Create(Context, Nullability.Create(type));
if (!type.HasObliviousNullability())
{
trapFile.type_nullability(this, n);
}
}
protected void PopulateRefKind(TextWriter trapFile, RefKind kind)
{
switch (kind)
{
case RefKind.Out:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Out);
break;
case RefKind.Ref:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Ref);
break;
case RefKind.RefReadOnly:
case RefKind.RefReadOnlyParameter:
trapFile.type_annotation(this, Kinds.TypeAnnotation.ReadonlyRef);
break;
}
}
protected void PopulateScopedKind(TextWriter trapFile, ScopedKind kind)
{
switch (kind)

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Semmle.Extraction.CSharp.Entities;
namespace Semmle.Extraction.CSharp
{
@@ -24,7 +26,7 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteUnescaped('\"');
}
public abstract Location? ReportingLocation { get; }
public abstract Microsoft.CodeAnalysis.Location? ReportingLocation { get; }
public abstract TrapStackBehaviour TrapStackBehaviour { get; }
@@ -65,6 +67,48 @@ namespace Semmle.Extraction.CSharp
}
#endif
protected void PopulateRefKind(TextWriter trapFile, RefKind kind)
{
switch (kind)
{
case RefKind.Out:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Out);
break;
case RefKind.Ref:
trapFile.type_annotation(this, Kinds.TypeAnnotation.Ref);
break;
case RefKind.RefReadOnly:
case RefKind.RefReadOnlyParameter:
trapFile.type_annotation(this, Kinds.TypeAnnotation.ReadonlyRef);
break;
}
}
protected void PopulateNullability(TextWriter trapFile, AnnotatedTypeSymbol type)
{
var n = NullabilityEntity.Create(Context, Nullability.Create(type));
if (!type.HasObliviousNullability())
{
trapFile.type_nullability(this, n);
}
}
protected static void WriteLocationToTrap<T1>(Action<T1, Entities.Location> writeAction, T1 entity, Entities.Location l)
{
if (l is not EmptyLocation)
{
writeAction(entity, l);
}
}
protected static void WriteLocationsToTrap<T1>(Action<T1, Entities.Location> writeAction, T1 entity, IEnumerable<Entities.Location> locations)
{
foreach (var loc in locations)
{
WriteLocationToTrap(writeAction, entity, loc);
}
}
public override string ToString() => Label.ToString();
}
}

View File

@@ -24,6 +24,16 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
private bool IsExplicitDelegateInvokeCall() => Kind == ExprKind.DELEGATE_INVOCATION && Context.GetModel(Syntax.Expression).GetSymbolInfo(Syntax.Expression).Symbol is IMethodSymbol m && m.MethodKind == MethodKind.DelegateInvoke;
private bool IsOperatorCall() => Kind == ExprKind.OPERATOR_INVOCATION;
private bool IsValidMemberAccessKind()
{
return Kind == ExprKind.METHOD_INVOCATION ||
IsEventDelegateCall() ||
IsExplicitDelegateInvokeCall() ||
IsOperatorCall();
}
protected override void PopulateExpression(TextWriter trapFile)
{
if (IsNameof(Syntax))
@@ -37,7 +47,7 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
var target = TargetSymbol;
switch (Syntax.Expression)
{
case MemberAccessExpressionSyntax memberAccess when Kind == ExprKind.METHOD_INVOCATION || IsEventDelegateCall() || IsExplicitDelegateInvokeCall():
case MemberAccessExpressionSyntax memberAccess when IsValidMemberAccessKind():
memberName = memberAccess.Name.Identifier.Text;
if (Syntax.Expression.Kind() == SyntaxKind.SimpleMemberAccessExpression)
// Qualified method call; `x.M()`
@@ -113,14 +123,24 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
public SymbolInfo SymbolInfo => info.SymbolInfo;
private static bool IsOperatorLikeCall(ExpressionNodeInfo info)
{
return info.SymbolInfo.Symbol is IMethodSymbol method &&
method.TryGetExtensionMethod()?.MethodKind == MethodKind.UserDefinedOperator;
}
public IMethodSymbol? TargetSymbol
{
get
{
var si = SymbolInfo;
if (si.Symbol is not null)
return si.Symbol as IMethodSymbol;
if (si.Symbol is ISymbol symbol)
{
var method = symbol as IMethodSymbol;
// Case for compiler-generated extension methods.
return method?.TryGetExtensionMethod() ?? method;
}
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure)
{
@@ -196,15 +216,25 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
private static ExprKind GetKind(ExpressionNodeInfo info)
{
return IsNameof((InvocationExpressionSyntax)info.Node)
? ExprKind.NAMEOF
: IsDelegateLikeCall(info)
? IsDelegateInvokeCall(info)
? ExprKind.DELEGATE_INVOCATION
: ExprKind.FUNCTION_POINTER_INVOCATION
: IsLocalFunctionInvocation(info)
? ExprKind.LOCAL_FUNCTION_INVOCATION
: ExprKind.METHOD_INVOCATION;
if (IsNameof((InvocationExpressionSyntax)info.Node))
{
return ExprKind.NAMEOF;
}
if (IsDelegateLikeCall(info))
{
return IsDelegateInvokeCall(info)
? ExprKind.DELEGATE_INVOCATION
: ExprKind.FUNCTION_POINTER_INVOCATION;
}
if (IsLocalFunctionInvocation(info))
{
return ExprKind.LOCAL_FUNCTION_INVOCATION;
}
if (IsOperatorLikeCall(info))
{
return ExprKind.OPERATOR_INVOCATION;
}
return ExprKind.METHOD_INVOCATION;
}
private static bool IsNameof(InvocationExpressionSyntax syntax)

View File

@@ -0,0 +1,9 @@
namespace Semmle.Extraction.CSharp.Entities
{
/// <summary>
/// Marker interface for parameter entities.
/// </summary>
internal interface IParameter : IEntity
{
}
}

View File

@@ -14,9 +14,28 @@ namespace Semmle.Extraction.CSharp.Entities
protected Method(Context cx, IMethodSymbol init)
: base(cx, init) { }
private SyntheticExtensionParameter? SyntheticParameter { get; set; }
private int SynthesizeExtensionParameter()
{
// Synthesize implicit parameter for extension methods declared using extension(...) syntax.
if (Symbol.ContainingSymbol is INamedTypeSymbol type &&
type.IsExtension && type.ExtensionParameter is IParameterSymbol parameter &&
!string.IsNullOrEmpty(parameter.Name) && !Symbol.IsStatic)
{
var originalSyntheticParam = OriginalDefinition.SyntheticParameter;
SyntheticParameter = SyntheticExtensionParameter.Create(Context, this, parameter, originalSyntheticParam);
return 1;
}
return 0;
}
protected void PopulateParameters()
{
var originalMethod = OriginalDefinition;
var positionOffset = SynthesizeExtensionParameter();
IEnumerable<IParameterSymbol> parameters = Symbol.Parameters;
IEnumerable<IParameterSymbol> originalParameters = originalMethod.Symbol.Parameters;
@@ -24,8 +43,8 @@ namespace Semmle.Extraction.CSharp.Entities
{
var original = SymbolEqualityComparer.Default.Equals(p.paramSymbol, p.originalParam)
? null
: Parameter.Create(Context, p.originalParam, originalMethod);
Parameter.Create(Context, p.paramSymbol, this, original);
: Parameter.Create(Context, p.originalParam, originalMethod, null, positionOffset);
Parameter.Create(Context, p.paramSymbol, this, original, positionOffset);
}
if (Symbol.IsVararg)
@@ -302,9 +321,9 @@ namespace Semmle.Extraction.CSharp.Entities
/// <summary>
/// Whether this method has unbound type parameters.
/// </summary>
public bool IsUnboundGeneric => IsGeneric && SymbolEqualityComparer.Default.Equals(Symbol.ConstructedFrom, Symbol);
public bool IsUnboundGeneric => Symbol.IsUnboundGenericMethod();
public bool IsBoundGeneric => IsGeneric && !IsUnboundGeneric;
public bool IsBoundGeneric => Symbol.IsBoundGenericMethod();
protected IMethodSymbol ConstructedFromSymbol => Symbol.ConstructedFrom;

View File

@@ -23,7 +23,11 @@ namespace Semmle.Extraction.CSharp.Entities
? Symbol.ContainingType.GetSymbolLocation()
: BodyDeclaringSymbol.GetSymbolLocation();
public override bool NeedsPopulation => base.NeedsPopulation || IsCompilerGeneratedDelegate();
public override bool NeedsPopulation =>
(base.NeedsPopulation || IsCompilerGeneratedDelegate()) &&
// Exclude compiler-generated extension methods. A call to such a method
// is replaced by a call to the defining extension method.
!Symbol.IsCompilerGeneratedExtensionMethod();
public override void Populate(TextWriter trapFile)
{

View File

@@ -7,16 +7,23 @@ using Semmle.Extraction.CSharp.Populators;
namespace Semmle.Extraction.CSharp.Entities
{
internal class Parameter : CachedSymbol<IParameterSymbol>, IExpressionParentEntity
internal class Parameter : CachedSymbol<IParameterSymbol>, IExpressionParentEntity, IParameter
{
protected IEntity? Parent { get; set; }
protected Parameter Original { get; }
private int PositionOffset { get; set; }
protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original)
private Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original, int positionOffset)
: base(cx, init)
{
Parent = parent;
Original = original ?? this;
PositionOffset = positionOffset;
}
protected Parameter(Context cx, IParameterSymbol init, IEntity? parent, Parameter? original)
: this(cx, init, parent, original, 0)
{
}
public override Microsoft.CodeAnalysis.Location ReportingLocation => Symbol.GetSymbolLocation();
@@ -32,46 +39,18 @@ namespace Semmle.Extraction.CSharp.Entities
RefReadOnly = 6
}
protected virtual int Ordinal => Symbol.Ordinal;
protected virtual int Ordinal => Symbol.Ordinal + PositionOffset;
private Kind ParamKind
{
get
{
switch (Symbol.RefKind)
{
case RefKind.Out:
return Kind.Out;
case RefKind.Ref:
return Kind.Ref;
case RefKind.In:
return Kind.In;
case RefKind.RefReadOnlyParameter:
return Kind.RefReadOnly;
default:
if (Symbol.IsParams)
return Kind.Params;
if (Ordinal == 0)
{
if (Symbol.ContainingSymbol is IMethodSymbol method && method.IsExtensionMethod)
return Kind.This;
}
return Kind.None;
}
}
}
public static Parameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null)
public static Parameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null, int positionOffset = 0)
{
var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param);
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original));
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, parent, original, positionOffset));
}
public static Parameter Create(Context cx, IParameterSymbol param)
{
var cachedSymbol = cx.GetPossiblyCachedParameterSymbol(param);
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null));
return ParameterFactory.Instance.CreateEntity(cx, cachedSymbol, (cachedSymbol, null, null, 0));
}
public override void WriteId(EscapingTextWriter trapFile)
@@ -79,6 +58,9 @@ namespace Semmle.Extraction.CSharp.Entities
if (Parent is null)
Parent = Method.Create(Context, Symbol.ContainingSymbol as IMethodSymbol);
if (Parent is null && Symbol.ContainingSymbol is INamedTypeSymbol type && type.IsExtension)
Parent = Type.Create(Context, type);
if (Parent is null)
throw new InternalError(Symbol, "Couldn't get parent of symbol.");
@@ -113,7 +95,8 @@ namespace Semmle.Extraction.CSharp.Entities
Context.ModelError(Symbol, "Inconsistent parameter declaration");
var type = Type.Create(Context, Symbol.Type);
trapFile.@params(this, Name, type.TypeRef, Ordinal, ParamKind, Parent!, Original);
var kind = Symbol.GetParameterKind();
trapFile.@params(this, Name, type.TypeRef, Ordinal, kind, Parent!, Original);
if (Context.OnlyScaffold)
{
@@ -194,11 +177,11 @@ namespace Semmle.Extraction.CSharp.Entities
return syntax?.Default;
}
private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?), Parameter>
private class ParameterFactory : CachedEntityFactory<(IParameterSymbol, IEntity?, Parameter?, int), Parameter>
{
public static ParameterFactory Instance { get; } = new ParameterFactory();
public override Parameter Create(Context cx, (IParameterSymbol, IEntity?, Parameter?) init) => new Parameter(cx, init.Item1, init.Item2, init.Item3);
public override Parameter Create(Context cx, (IParameterSymbol, IEntity?, Parameter?, int) init) => new Parameter(cx, init.Item1, init.Item2, init.Item3, init.Item4);
}
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;

View File

@@ -0,0 +1,77 @@
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Semmle.Extraction.CSharp.Entities
{
/// <summary>
/// Synthetic parameter for extension methods declared using the extension syntax.
/// That is, we add a synthetic parameter `s` to `IsValid` in the following example:
/// extension(string s) {
/// public bool IsValid() { ... }
/// }
///
/// Note, that we use the characteristics of the parameter of the extension type
/// to populate the database.
/// </summary>
internal class SyntheticExtensionParameter : FreshEntity, IParameter
{
private Method ExtensionMethod { get; }
private IParameterSymbol ExtensionParameter { get; }
private SyntheticExtensionParameter Original { get; }
private SyntheticExtensionParameter(Context cx, Method method, IParameterSymbol parameter, SyntheticExtensionParameter? original) : base(cx)
{
ExtensionMethod = method;
ExtensionParameter = parameter;
Original = original ?? this;
}
private static int Ordinal => 0;
private string Name => ExtensionParameter.Name;
private bool IsSourceDeclaration => ExtensionMethod.Symbol.IsSourceDeclaration();
protected override void Populate(TextWriter trapFile)
{
PopulateNullability(trapFile, ExtensionParameter.GetAnnotatedType());
PopulateRefKind(trapFile, ExtensionParameter.RefKind);
var type = Type.Create(Context, ExtensionParameter.Type);
var kind = ExtensionParameter.GetParameterKind();
trapFile.@params(this, Name, type.TypeRef, Ordinal, kind, ExtensionMethod, Original);
if (Context.OnlyScaffold)
{
return;
}
if (Context.ExtractLocation(ExtensionParameter))
{
var locations = Context.GetLocations(ExtensionParameter);
WriteLocationsToTrap(trapFile.param_location, this, locations);
}
if (IsSourceDeclaration)
{
foreach (var syntax in ExtensionParameter.DeclaringSyntaxReferences
.Select(d => d.GetSyntax())
.OfType<ParameterSyntax>()
.Where(s => s.Type is not null))
{
TypeMention.Create(Context, syntax.Type!, this, type);
}
}
}
public static SyntheticExtensionParameter Create(Context cx, Method method, IParameterSymbol parameter, SyntheticExtensionParameter? original)
{
var p = new SyntheticExtensionParameter(cx, method, parameter, original);
p.TryPopulate();
return p;
}
}
}

View File

@@ -20,6 +20,8 @@ namespace Semmle.Extraction.CSharp.Entities
public static NamedType Create(Context cx, INamedTypeSymbol type) =>
NamedTypeFactory.Instance.CreateEntityFromSymbol(cx, type);
public NamedType OriginalDefinition => Create(Context, Symbol.OriginalDefinition);
/// <summary>
/// Creates a named type entity from a tuple type. Unlike <see cref="Create"/>, this
/// will create an entity for the underlying `System.ValueTuple` struct.
@@ -90,6 +92,25 @@ namespace Semmle.Extraction.CSharp.Entities
{
trapFile.anonymous_types(this);
}
if (Symbol.IsExtension && Symbol.ExtensionParameter is IParameterSymbol parameter)
{
// For some reason an extension type has a receiver parameter with an empty name
// even when there is no parameter.
if (!string.IsNullOrEmpty(parameter.Name))
{
var originalType = OriginalDefinition;
// In case this is a constructed generic, we also need to create the unbound parameter.
var originalParameter = SymbolEqualityComparer.Default.Equals(Symbol, originalType.Symbol.ExtensionParameter) || originalType.Symbol.ExtensionParameter is null
? null
: Parameter.Create(Context, originalType.Symbol.ExtensionParameter, originalType);
Parameter.Create(Context, parameter, this, originalParameter);
}
// Use the parameter type as the receiver type.
var receiverType = Type.Create(Context, parameter.Type).TypeRef;
trapFile.extension_receiver_type(this, receiverType);
}
}
private readonly Lazy<Type[]> typeArgumentsLazy;

View File

@@ -105,6 +105,7 @@ namespace Semmle.Extraction.CSharp.Entities
case TypeKind.Pointer: return Kinds.TypeKind.POINTER;
case TypeKind.FunctionPointer: return Kinds.TypeKind.FUNCTION_POINTER;
case TypeKind.Error: return Kinds.TypeKind.UNKNOWN;
case TypeKind.Extension: return Kinds.TypeKind.EXTENSION;
default:
cx.ModelError(Symbol, $"Unhandled type kind '{Symbol.TypeKind}'");
return Kinds.TypeKind.UNKNOWN;
@@ -366,7 +367,7 @@ namespace Semmle.Extraction.CSharp.Entities
private DelegateTypeParameter(Context cx, IParameterSymbol init, IEntity parent, Parameter? original)
: base(cx, init, parent, original) { }
public static new DelegateTypeParameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null) =>
public static DelegateTypeParameter Create(Context cx, IParameterSymbol param, IEntity parent, Parameter? original = null) =>
// We need to use a different cache key than `param` to avoid mixing up
// `DelegateTypeParameter`s and `Parameter`s
DelegateTypeParameterFactory.Instance.CreateEntity(cx, (typeof(DelegateTypeParameter), new SymbolEqualityWrapper(param)), (param, parent, original));

View File

@@ -38,5 +38,6 @@ namespace Semmle.Extraction.Kinds
TUPLE = 32,
FUNCTION_POINTER = 33,
INLINE_ARRAY = 34,
EXTENSION = 35
}
}

View File

@@ -202,6 +202,9 @@ namespace Semmle.Extraction.CSharp
internal static void extend(this TextWriter trapFile, Type type, Type super) =>
trapFile.WriteTuple("extend", type, super);
internal static void extension_receiver_type(this TextWriter trapFile, Type @extension, Type receiverType) =>
trapFile.WriteTuple("extension_receiver_type", extension, receiverType);
internal static void anonymous_types(this TextWriter trapFile, Type type) =>
trapFile.WriteTuple("anonymous_types", type);
@@ -292,10 +295,10 @@ namespace Semmle.Extraction.CSharp
internal static void overrides(this TextWriter trapFile, Method overriding, Method overridden) =>
trapFile.WriteTuple("overrides", overriding, overridden);
internal static void param_location(this TextWriter trapFile, Parameter param, Location location) =>
internal static void param_location(this TextWriter trapFile, IParameter param, Location location) =>
trapFile.WriteTuple("param_location", param, location);
internal static void @params(this TextWriter trapFile, Parameter param, string name, Type type, int child, Parameter.Kind mode, IEntity method, Parameter originalDefinition) =>
internal static void @params(this TextWriter trapFile, IParameter param, string name, Type type, int child, Parameter.Kind mode, IEntity method, IParameter originalDefinition) =>
trapFile.WriteTuple("params", param, name, type, child, (int)mode, method, originalDefinition);
internal static void parent_namespace(this TextWriter trapFile, IEntity type, Namespace parent) =>

View File

@@ -119,5 +119,28 @@ namespace Semmle.Util
/// </summary>
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> items) where T : class =>
items.Where(i => i is not null)!;
/// <summary>
/// Splits the sequence at the given index.
/// </summary>
public static (IEnumerable<T>, IEnumerable<T>) SplitAt<T>(this IEnumerable<T> items, int index)
{
var left = new List<T>();
var right = new List<T>();
var i = 0;
foreach (var item in items)
{
if (i < index)
{
left.Add(item);
}
else
{
right.Add(item);
}
i++;
}
return (left, right);
}
}
}