Skip to content

Commit

Permalink
Add declaration scopes to lambda cache key
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Oct 12, 2022
1 parent 4b76f8a commit fd3c176
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 16 deletions.
58 changes: 44 additions & 14 deletions src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -754,8 +754,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType, bool inExpressionTr
}
else
{
var parameterDeclarationScopes = invokeMethod.GetByValueParameterDeclarationScopes();
lambdaSymbol = CreateLambdaSymbol(Binder.ContainingMemberOrLambda, returnType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds, parameterDeclarationScopes, refKind);
lambdaSymbol = CreateLambdaSymbol(Binder.ContainingMemberOrLambda, returnType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds, cacheKey.ParameterDeclarationScopes, refKind);
lambdaBodyBinder = new ExecutableCodeBinder(_unboundLambda.Syntax, lambdaSymbol, GetWithParametersBinder(lambdaSymbol, Binder), inExpressionTree ? BinderFlags.InExpressionTree : BinderFlags.None);
block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, diagnostics);
}
Expand Down Expand Up @@ -846,8 +845,10 @@ internal LambdaSymbol CreateLambdaSymbol(NamedTypeSymbol delegateType, Symbol co
{
var invokeMethod = DelegateInvokeMethod(delegateType);
var returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out RefKind refKind);
ReturnInferenceCacheKey.GetFields(delegateType, IsAsync, out var parameterTypes, out var parameterRefKinds, out _);
return CreateLambdaSymbol(containingSymbol, returnType, parameterTypes, parameterRefKinds, invokeMethod.GetByValueParameterDeclarationScopes(), refKind);
ReturnInferenceCacheKey.GetFields(delegateType, IsAsync, out var parameterTypes, out var parameterRefKinds,
out var parameterDeclarationScopes, out _);

return CreateLambdaSymbol(containingSymbol, returnType, parameterTypes, parameterRefKinds, parameterDeclarationScopes, refKind);
}

private void ValidateUnsafeParameters(BindingDiagnosticBag diagnostics, ImmutableArray<TypeWithAnnotations> targetParameterTypes)
Expand Down Expand Up @@ -961,9 +962,7 @@ public BoundLambda BindForReturnTypeInference(NamedTypeSymbol delegateType)
BoundLambda? result;
if (!_returnInferenceCache!.TryGetValue(cacheKey, out result))
{
// TODO2 do we need to include the parameter scopes in the bound lambda cache?
var invokeMethod = DelegateInvokeMethod(delegateType);
result = ReallyInferReturnType(delegateType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds, invokeMethod.GetByValueParameterDeclarationScopes());
result = ReallyInferReturnType(delegateType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds, cacheKey.ParameterDeclarationScopes);
result = ImmutableInterlocked.GetOrAdd(ref _returnInferenceCache, cacheKey, result);
}

Expand All @@ -977,16 +976,22 @@ private sealed class ReturnInferenceCacheKey
{
public readonly ImmutableArray<TypeWithAnnotations> ParameterTypes;
public readonly ImmutableArray<RefKind> ParameterRefKinds;
public readonly ImmutableArray<DeclarationScope> ParameterDeclarationScopes;
public readonly NamedTypeSymbol? TaskLikeReturnTypeOpt;

public static readonly ReturnInferenceCacheKey Empty = new ReturnInferenceCacheKey(ImmutableArray<TypeWithAnnotations>.Empty, ImmutableArray<RefKind>.Empty, null);
public static readonly ReturnInferenceCacheKey Empty = new ReturnInferenceCacheKey(ImmutableArray<TypeWithAnnotations>.Empty, ImmutableArray<RefKind>.Empty,
parameterDeclarationScopes: default, taskLikeReturnTypeOpt: null);

private ReturnInferenceCacheKey(ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds, NamedTypeSymbol? taskLikeReturnTypeOpt)
private ReturnInferenceCacheKey(ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds,
ImmutableArray<DeclarationScope> parameterDeclarationScopes, NamedTypeSymbol? taskLikeReturnTypeOpt)
{
Debug.Assert(parameterTypes.Length == parameterRefKinds.Length);
Debug.Assert(parameterDeclarationScopes.IsDefault || parameterTypes.Length == parameterDeclarationScopes.Length);
Debug.Assert(taskLikeReturnTypeOpt is null || ((object)taskLikeReturnTypeOpt == taskLikeReturnTypeOpt.ConstructedFrom && taskLikeReturnTypeOpt.IsCustomTaskType(out var builderArgument)));

this.ParameterTypes = parameterTypes;
this.ParameterRefKinds = parameterRefKinds;
this.ParameterDeclarationScopes = parameterDeclarationScopes;
this.TaskLikeReturnTypeOpt = taskLikeReturnTypeOpt;
}

Expand Down Expand Up @@ -1015,6 +1020,24 @@ public override bool Equals(object? obj)
}
}

if (this.ParameterDeclarationScopes.IsDefault || other.ParameterDeclarationScopes.IsDefault)
{
return this.ParameterDeclarationScopes.IsDefault == other.ParameterDeclarationScopes.IsDefault;
}

if (this.ParameterDeclarationScopes.Length != other.ParameterDeclarationScopes.Length)
{
return false;
}

for (int i = 0; i < this.ParameterDeclarationScopes.Length; i++)
{
if (this.ParameterDeclarationScopes[i] != other.ParameterDeclarationScopes[i])
{
return false;
}
}

return true;
}

Expand All @@ -1028,28 +1051,31 @@ public override int GetHashCode()
return value;
}

// TODO2 should ReturnInferenceCacheKey includes scopes?
public static ReturnInferenceCacheKey Create(NamedTypeSymbol? delegateType, bool isAsync)
{
GetFields(delegateType, isAsync, out var parameterTypes, out var parameterRefKinds, out var taskLikeReturnTypeOpt);
GetFields(delegateType, isAsync, out var parameterTypes, out var parameterRefKinds,
out var parameterDeclarationScopes, out var taskLikeReturnTypeOpt);

if (parameterTypes.IsEmpty && parameterRefKinds.IsEmpty && taskLikeReturnTypeOpt is null)
{
return Empty;
}
return new ReturnInferenceCacheKey(parameterTypes, parameterRefKinds, taskLikeReturnTypeOpt);
return new ReturnInferenceCacheKey(parameterTypes, parameterRefKinds, parameterDeclarationScopes, taskLikeReturnTypeOpt);
}

public static void GetFields(
NamedTypeSymbol? delegateType,
bool isAsync,
out ImmutableArray<TypeWithAnnotations> parameterTypes,
out ImmutableArray<RefKind> parameterRefKinds,
out ImmutableArray<DeclarationScope> parameterDeclarationScopes,
out NamedTypeSymbol? taskLikeReturnTypeOpt)
{
// delegateType or DelegateInvokeMethod can be null in cases of malformed delegates
// in such case we would want something trivial with no parameters
parameterTypes = ImmutableArray<TypeWithAnnotations>.Empty;
parameterRefKinds = ImmutableArray<RefKind>.Empty;
parameterDeclarationScopes = default;
taskLikeReturnTypeOpt = null;
MethodSymbol? invoke = DelegateInvokeMethod(delegateType);
if (invoke is not null)
Expand All @@ -1068,6 +1094,7 @@ public static void GetFields(

parameterTypes = typesBuilder.ToImmutableAndFree();
parameterRefKinds = refKindsBuilder.ToImmutableAndFree();
parameterDeclarationScopes = MethodSymbolExtensions.GetByValueParameterDeclarationScopes(invoke);
}

if (isAsync)
Expand Down Expand Up @@ -1141,9 +1168,12 @@ private BoundLambda ReallyBindForErrorRecovery()
{
if (lambda is null)
return null;

var delegateType = (NamedTypeSymbol?)lambda.Type;
ReturnInferenceCacheKey.GetFields(delegateType, IsAsync, out var parameterTypes, out var parameterRefKinds, taskLikeReturnTypeOpt: out _);
return ReallyBindForErrorRecovery(delegateType, lambda.InferredReturnType, parameterTypes, parameterRefKinds, parameterDeclarationScopes: default); // TODO2 not sure what to do here
ReturnInferenceCacheKey.GetFields(delegateType, IsAsync, out var parameterTypes, out var parameterRefKinds,
out var parameterDeclarationScopes, taskLikeReturnTypeOpt: out _);

return ReallyBindForErrorRecovery(delegateType, lambda.InferredReturnType, parameterTypes, parameterRefKinds, parameterDeclarationScopes);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ internal static ImmutableArray<DeclarationScope> GetByValueParameterDeclarationS

static DeclarationScope getByValueParameterDeclarationScope(ParameterSymbol parameter)
{
// For implicitly-typed lambda parameters, we only care about declaration scopes on by-value parameters.
// For by-value parameters, the declaration scope is the effective scope.
if (parameter.RefKind != RefKind.None)
return DeclarationScope.Unscoped;

Expand Down
2 changes: 0 additions & 2 deletions src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ public LambdaSymbol(
Debug.Assert(syntaxReferenceOpt is not null);
Debug.Assert(containingSymbol.DeclaringCompilation == compilation);
Debug.Assert(parameterDeclarationScopes.IsDefault || parameterTypes.Length == parameterDeclarationScopes.Length);
// We only compute declaration scopes for by-value parameters (see GetByValueParameterDeclarationScopes)
Debug.Assert(parameterDeclarationScopes.IsDefault || parameterDeclarationScopes.All(s => s is DeclarationScope.Unscoped or DeclarationScope.ValueScoped));

_binder = binder;
_containingSymbol = containingSymbol;
Expand Down

0 comments on commit fd3c176

Please sign in to comment.