|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
// A binder that finds query variables (BoundRangeVariableSymbol) and can bind them
// to the appropriate rewriting involving lambda parameters when transparent identifiers are involved.
private sealed class WithQueryLambdaParametersBinder : WithLambdaParametersBinder
{
private readonly RangeVariableMap _rangeVariableMap;
private readonly MultiDictionary<string, RangeVariableSymbol> _parameterMap;
public WithQueryLambdaParametersBinder(LambdaSymbol lambdaSymbol, RangeVariableMap rangeVariableMap, Binder next)
: base(lambdaSymbol, next)
{
_rangeVariableMap = rangeVariableMap;
_parameterMap = new MultiDictionary<string, RangeVariableSymbol>();
foreach (var qv in rangeVariableMap.Keys)
{
_parameterMap.Add(qv.Name, qv);
}
}
protected override BoundExpression BindRangeVariable(SimpleNameSyntax node, RangeVariableSymbol qv, BindingDiagnosticBag diagnostics)
{
Debug.Assert(!qv.IsTransparent);
BoundExpression translation;
ImmutableArray<string> path;
if (_rangeVariableMap.TryGetValue(qv, out path))
{
if (path.IsEmpty)
{
// the range variable maps directly to a use of the parameter of that name
var value = base.parameterMap[qv.Name];
Debug.Assert(value.Count == 1);
translation = new BoundParameter(node, value.Single());
}
else
{
// if the query variable map for this variable is non empty, we always start with the current
// lambda's first parameter, which is a transparent identifier.
Debug.Assert(base.lambdaSymbol.Parameters[0].Name.StartsWith(transparentIdentifierPrefix, StringComparison.Ordinal));
translation = new BoundParameter(node, base.lambdaSymbol.Parameters[0]);
for (int i = path.Length - 1; i >= 0; i--)
{
translation.WasCompilerGenerated = true;
var nextField = path[i];
translation = SelectField(node, translation, nextField, diagnostics);
}
}
return new BoundRangeVariable(node, qv, translation, translation.Type);
}
return base.BindRangeVariable(node, qv, diagnostics);
}
private BoundExpression SelectField(SimpleNameSyntax node, BoundExpression receiver, string name, BindingDiagnosticBag diagnostics)
{
var receiverType = receiver.Type as NamedTypeSymbol;
if ((object)receiverType == null || !receiverType.IsAnonymousType)
{
// We only construct transparent query variables using anonymous types, so if we're trying to navigate through
// some other type, we must have some query API where the types don't match up as expected.
var info = new CSDiagnosticInfo(ErrorCode.ERR_UnsupportedTransparentIdentifierAccess, name, new FormattedSymbol(receiver.ExpressionSymbol ?? receiverType, SymbolDisplayFormat.CSharpErrorMessageNoParameterNamesFormat));
if (receiver.Type?.IsErrorType() != true)
{
Error(diagnostics, info, node);
}
return new BoundBadExpression(
node,
LookupResultKind.Empty,
ImmutableArray.Create<Symbol>(receiver.ExpressionSymbol),
ImmutableArray.Create(BindToTypeForErrorRecovery(receiver)),
new ExtendedErrorTypeSymbol(this.Compilation, "", 0, info));
}
LookupResult lookupResult = LookupResult.GetInstance();
LookupOptions options = LookupOptions.MustBeInstance;
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
LookupMembersWithFallback(lookupResult, receiver.Type, name, 0, ref useSiteInfo, basesBeingResolved: null, options: options);
diagnostics.Add(node, useSiteInfo);
var result = BindMemberOfType(node, node, name, 0, indexed: false, receiver, default(SeparatedSyntaxList<TypeSyntax>), default(ImmutableArray<TypeWithAnnotations>), lookupResult, BoundMethodGroupFlags.None, diagnostics);
lookupResult.Free();
return result;
}
internal override void LookupSymbolsInSingleBinder(
LookupResult result, string name, int arity, ConsList<TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(result.IsClear);
if ((options & LookupOptions.NamespaceAliasesOnly) != 0)
{
return;
}
foreach (var rangeVariable in _parameterMap[name])
{
result.MergeEqual(originalBinder.CheckViability(rangeVariable, arity, options, null, diagnose, ref useSiteInfo));
}
}
internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder)
{
if (options.CanConsiderMembers())
{
foreach (var kvp in _parameterMap)
{
result.AddSymbol(null, kvp.Key, 0);
}
}
}
}
}
}
|