|
// 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.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
internal BoundExpression CreateConversion(
BoundExpression source,
TypeSymbol destination,
BindingDiagnosticBag diagnostics)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var conversion = Conversions.ClassifyConversionFromExpression(source, destination, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
diagnostics.Add(source.Syntax, useSiteInfo);
return CreateConversion(source.Syntax, source, conversion, isCast: false, conversionGroupOpt: null, destination: destination, diagnostics: diagnostics);
}
internal BoundExpression CreateConversion(
BoundExpression source,
Conversion conversion,
TypeSymbol destination,
BindingDiagnosticBag diagnostics)
{
return CreateConversion(source.Syntax, source, conversion, isCast: false, conversionGroupOpt: null, destination: destination, diagnostics: diagnostics);
}
internal BoundExpression CreateConversion(
SyntaxNode syntax,
BoundExpression source,
Conversion conversion,
bool isCast,
ConversionGroup? conversionGroupOpt,
TypeSymbol destination,
BindingDiagnosticBag diagnostics)
{
return CreateConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, source.WasCompilerGenerated, destination, diagnostics);
}
protected BoundExpression CreateConversion(
SyntaxNode syntax,
BoundExpression source,
Conversion conversion,
bool isCast,
ConversionGroup? conversionGroupOpt,
bool wasCompilerGenerated,
TypeSymbol destination,
BindingDiagnosticBag diagnostics,
bool hasErrors = false)
{
var result = createConversion(syntax, source, conversion, isCast, conversionGroupOpt, wasCompilerGenerated, destination, diagnostics, hasErrors);
Debug.Assert(result is BoundConversion || (conversion.IsIdentity && ((object)result == source) || source.NeedsToBeConverted()) || hasErrors);
#if DEBUG
if (source is BoundValuePlaceholder placeholder1)
{
Debug.Assert(filterConversion(conversion));
Debug.Assert(BoundNode.GetConversion(result, placeholder1) == conversion);
}
else if (source.Type is not null && filterConversion(conversion))
{
var placeholder2 = new BoundValuePlaceholder(source.Syntax, source.Type);
var result2 = createConversion(syntax, placeholder2, conversion, isCast, conversionGroupOpt, wasCompilerGenerated, destination, BindingDiagnosticBag.Discarded, hasErrors);
Debug.Assert(BoundNode.GetConversion(result2, placeholder2) == conversion);
}
static bool filterConversion(Conversion conversion)
{
return !conversion.IsInterpolatedString &&
!conversion.IsInterpolatedStringHandler &&
!conversion.IsSwitchExpression &&
!conversion.IsCollectionExpression &&
!(conversion.IsTupleLiteralConversion || (conversion.IsNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion)) &&
(!conversion.IsUserDefined || filterConversion(conversion.UserDefinedFromConversion));
}
#endif
return result;
BoundExpression createConversion(
SyntaxNode syntax,
BoundExpression source,
Conversion conversion,
bool isCast,
ConversionGroup? conversionGroupOpt,
bool wasCompilerGenerated,
TypeSymbol destination,
BindingDiagnosticBag diagnostics,
bool hasErrors = false)
{
RoslynDebug.Assert(source != null);
RoslynDebug.Assert((object)destination != null);
RoslynDebug.Assert(!isCast || conversionGroupOpt != null || wasCompilerGenerated);
if (conversion.IsIdentity)
{
if (source is BoundTupleLiteral sourceTuple)
{
NamedTypeSymbol.ReportTupleNamesMismatchesIfAny(destination, sourceTuple, diagnostics);
}
// identity tuple and switch conversions result in a converted expression
// to indicate that such conversions are no longer applicable.
source = BindToNaturalType(source, diagnostics);
RoslynDebug.Assert(source.Type is object);
// We need to preserve any conversion that changes the type (even identity conversions, like object->dynamic),
// or that was explicitly written in code (so that GetSemanticInfo can find the syntax in the bound tree).
if (!isCast && source.Type.Equals(destination, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
{
return source;
}
}
if (conversion.IsMethodGroup)
{
return CreateMethodGroupConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
}
// Obsolete diagnostics for method group are reported as part of creating the method group conversion.
reportUseSiteDiagnostics(syntax, conversion, source, destination, diagnostics);
if (conversion.IsAnonymousFunction && source.Kind == BoundKind.UnboundLambda)
{
return CreateAnonymousFunctionConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
}
if (conversion.Kind == ConversionKind.FunctionType)
{
return CreateFunctionTypeConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
}
if (conversion.IsStackAlloc)
{
return CreateStackAllocConversion(syntax, source, conversion, isCast, conversionGroupOpt, destination, diagnostics);
}
if (conversion.IsTupleLiteralConversion ||
(conversion.IsNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion))
{
return CreateTupleLiteralConversion(syntax, (BoundTupleLiteral)source, conversion, isCast: isCast, conversionGroupOpt, destination, diagnostics);
}
if (conversion.Kind == ConversionKind.SwitchExpression)
{
var convertedSwitch = ConvertSwitchExpression((BoundUnconvertedSwitchExpression)source, destination, conversionIfTargetTyped: conversion, diagnostics);
return new BoundConversion(
syntax,
convertedSwitch,
conversion,
CheckOverflowAtRuntime,
explicitCastInCode: isCast && !wasCompilerGenerated,
conversionGroupOpt,
convertedSwitch.ConstantValueOpt,
destination,
hasErrors);
}
if (conversion.Kind == ConversionKind.ConditionalExpression)
{
var convertedConditional = ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, destination, conversionIfTargetTyped: conversion, diagnostics);
return new BoundConversion(
syntax,
convertedConditional,
conversion,
CheckOverflowAtRuntime,
explicitCastInCode: isCast && !wasCompilerGenerated,
conversionGroupOpt,
convertedConditional.ConstantValueOpt,
destination,
hasErrors);
}
if (conversion.Kind == ConversionKind.InterpolatedString)
{
Debug.Assert(destination.SpecialType != SpecialType.System_String);
var unconvertedSource = (BoundUnconvertedInterpolatedString)source;
source = BindUnconvertedInterpolatedExpressionToFormattableStringFactory(unconvertedSource, destination, diagnostics);
}
if (conversion.Kind == ConversionKind.InterpolatedStringHandler)
{
return new BoundConversion(
syntax,
BindUnconvertedInterpolatedExpressionToHandlerType(source, (NamedTypeSymbol)destination, diagnostics),
conversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: isCast && !wasCompilerGenerated,
conversionGroupOpt,
constantValueOpt: null,
destination);
}
if (source.Kind == BoundKind.UnconvertedSwitchExpression)
{
TypeSymbol? type = source.Type;
if (type is null)
{
Debug.Assert(!conversion.Exists);
type = CreateErrorType();
hasErrors = true;
}
source = ConvertSwitchExpression((BoundUnconvertedSwitchExpression)source, type, conversionIfTargetTyped: null, diagnostics, hasErrors);
if (destination.Equals(type, TypeCompareKind.ConsiderEverything) && wasCompilerGenerated)
{
return source;
}
}
if (conversion.IsObjectCreation)
{
return ConvertObjectCreationExpression(syntax, (BoundUnconvertedObjectCreationExpression)source, conversion, isCast, destination, conversionGroupOpt, wasCompilerGenerated, diagnostics);
}
if (source.Kind == BoundKind.UnconvertedCollectionExpression)
{
Debug.Assert(conversion.IsCollectionExpression
|| (conversion.IsNullable && conversion.UnderlyingConversions[0].IsCollectionExpression)
|| !conversion.Exists);
var collectionExpression = ConvertCollectionExpression(
(BoundUnconvertedCollectionExpression)source,
destination,
conversion,
diagnostics);
return new BoundConversion(
syntax,
collectionExpression,
conversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: isCast && !wasCompilerGenerated,
conversionGroupOpt,
constantValueOpt: null,
type: destination);
}
if (source.Kind == BoundKind.UnconvertedConditionalOperator)
{
Debug.Assert(source.Type is null);
Debug.Assert(!conversion.Exists);
hasErrors = true;
source = ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, CreateErrorType(), conversionIfTargetTyped: null, diagnostics, hasErrors);
}
if (conversion.IsUserDefined)
{
// User-defined conversions are likely to be represented as multiple
// BoundConversion instances so a ConversionGroup is necessary.
return CreateUserDefinedConversion(syntax, source, conversion, isCast: isCast, conversionGroupOpt ?? new ConversionGroup(conversion), destination, diagnostics, hasErrors);
}
ConstantValue? constantValue = this.FoldConstantConversion(syntax, source, conversion, destination, diagnostics);
if (conversion.Kind == ConversionKind.DefaultLiteral)
{
source = new BoundDefaultExpression(source.Syntax, targetType: null, constantValue, type: destination)
.WithSuppression(source.IsSuppressed);
}
if (!hasErrors && conversion.Exists)
{
ensureAllUnderlyingConversionsChecked(syntax, source, conversion, wasCompilerGenerated, destination, diagnostics);
if (conversion.Kind is ConversionKind.ImplicitReference or ConversionKind.ExplicitReference &&
source.Type is { } sourceType &&
sourceType.IsWellKnownTypeLock())
{
diagnostics.Add(ErrorCode.WRN_ConvertingLock, source.Syntax);
}
}
return new BoundConversion(
syntax,
BindToNaturalType(source, diagnostics),
conversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: isCast && !wasCompilerGenerated,
conversionGroupOpt,
constantValueOpt: constantValue,
type: destination,
hasErrors: hasErrors)
{ WasCompilerGenerated = wasCompilerGenerated };
void reportUseSiteDiagnostics(SyntaxNode syntax, Conversion conversion, BoundExpression source, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
// Obsolete diagnostics for method group are reported as part of creating the method group conversion.
Debug.Assert(!conversion.IsMethodGroup);
ReportDiagnosticsIfObsolete(diagnostics, conversion, syntax, hasBaseReceiver: false);
if (conversion.Method is not null)
{
ReportUseSite(conversion.Method, diagnostics, syntax.Location);
}
checkConstraintLanguageVersionAndRuntimeSupportForConversion(syntax, conversion, source, destination, diagnostics);
}
}
void ensureAllUnderlyingConversionsChecked(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool wasCompilerGenerated, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
if (conversion.IsNullable)
{
Debug.Assert(conversion.UnderlyingConversions.Length == 1);
if (destination.IsNullableType())
{
switch (source.Type?.IsNullableType())
{
case true:
_ = CreateConversion(
syntax,
new BoundValuePlaceholder(source.Syntax, source.Type.GetNullableUnderlyingType()),
conversion.UnderlyingConversions[0],
isCast: false,
conversionGroupOpt: null,
wasCompilerGenerated,
destination.GetNullableUnderlyingType(),
diagnostics);
break;
case false:
_ = CreateConversion(
syntax,
source,
conversion.UnderlyingConversions[0],
isCast: false,
conversionGroupOpt: null,
wasCompilerGenerated,
destination.GetNullableUnderlyingType(),
diagnostics);
break;
}
conversion.UnderlyingConversions[0].AssertUnderlyingConversionsChecked();
conversion.MarkUnderlyingConversionsChecked();
}
else if (source.Type?.IsNullableType() == true)
{
_ = CreateConversion(
syntax,
new BoundValuePlaceholder(source.Syntax, source.Type.GetNullableUnderlyingType()),
conversion.UnderlyingConversions[0],
isCast: false,
conversionGroupOpt: null,
wasCompilerGenerated,
destination,
diagnostics);
conversion.UnderlyingConversions[0].AssertUnderlyingConversionsChecked();
conversion.MarkUnderlyingConversionsChecked();
}
}
else if (conversion.IsTupleConversion)
{
ImmutableArray<TypeWithAnnotations> sourceTypes;
ImmutableArray<TypeWithAnnotations> destTypes;
if (source.Type?.TryGetElementTypesWithAnnotationsIfTupleType(out sourceTypes) == true &&
destination.TryGetElementTypesWithAnnotationsIfTupleType(out destTypes) &&
sourceTypes.Length == destTypes.Length)
{
var elementConversions = conversion.UnderlyingConversions;
Debug.Assert(elementConversions.Length == sourceTypes.Length);
for (int i = 0; i < sourceTypes.Length; i++)
{
_ = CreateConversion(
syntax,
new BoundValuePlaceholder(source.Syntax, sourceTypes[i].Type),
elementConversions[i],
isCast: false,
conversionGroupOpt: null,
wasCompilerGenerated,
destTypes[i].Type,
diagnostics);
elementConversions[i].AssertUnderlyingConversionsChecked();
}
conversion.MarkUnderlyingConversionsChecked();
}
}
else if (conversion.IsDynamic)
{
Debug.Assert(conversion.UnderlyingConversions.IsDefault);
conversion.MarkUnderlyingConversionsChecked();
}
conversion.AssertUnderlyingConversionsCheckedRecursive();
}
void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syntax, Conversion conversion, BoundExpression source, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
Debug.Assert(syntax.SyntaxTree is object);
if (conversion.IsUserDefined)
{
if (conversion.Method is MethodSymbol method && method.IsStatic)
{
if (method.IsAbstract || method.IsVirtual)
{
Debug.Assert(conversion.ConstrainedToTypeOpt is TypeParameterSymbol);
if (Compilation.SourceModule != method.ContainingModule)
{
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureStaticAbstractMembersInInterfaces, diagnostics);
if (!Compilation.Assembly.RuntimeSupportsStaticAbstractMembersInInterfaces)
{
Error(diagnostics, ErrorCode.ERR_RuntimeDoesNotSupportStaticAbstractMembersInInterfaces, syntax);
}
}
}
if (SyntaxFacts.IsCheckedOperator(method.Name) &&
Compilation.SourceModule != method.ContainingModule)
{
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureCheckedUserDefinedOperators, diagnostics);
}
}
}
else if (conversion.IsInlineArray)
{
if (!Compilation.Assembly.RuntimeSupportsInlineArrayTypes)
{
Error(diagnostics, ErrorCode.ERR_RuntimeDoesNotSupportInlineArrayTypes, syntax);
}
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureInlineArrays, diagnostics);
Debug.Assert(source.Type is { });
FieldSymbol? elementField = source.Type.TryGetInlineArrayElementField();
Debug.Assert(elementField is { });
diagnostics.ReportUseSite(elementField, syntax);
if (destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions))
{
if (CheckValueKind(syntax, source, BindValueKind.RefersToLocation, checkingReceiver: false, BindingDiagnosticBag.Discarded))
{
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_MemoryMarshal__CreateReadOnlySpan, diagnostics, syntax: syntax); // This also takes care of an 'int' type
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_Unsafe__AsRef_T, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_Unsafe__As_T, diagnostics, syntax: syntax);
}
else
{
Error(diagnostics, ErrorCode.ERR_InlineArrayConversionToReadOnlySpanNotSupported, syntax, destination);
}
}
else
{
Debug.Assert(destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions));
if (CheckValueKind(syntax, source, BindValueKind.RefersToLocation | BindValueKind.Assignable, checkingReceiver: false, BindingDiagnosticBag.Discarded))
{
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_MemoryMarshal__CreateSpan, diagnostics, syntax: syntax); // This also takes care of an 'int' type
_ = GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_Unsafe__As_T, diagnostics, syntax: syntax);
}
else
{
Error(diagnostics, ErrorCode.ERR_InlineArrayConversionToSpanNotSupported, syntax, destination);
}
}
CheckInlineArrayTypeIsSupported(syntax, source.Type, elementField.Type, diagnostics);
}
else if (conversion.IsSpan)
{
Debug.Assert(source.Type is not null);
Debug.Assert(destination.IsSpan() || destination.IsReadOnlySpan());
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureFirstClassSpan, diagnostics);
// NOTE: We cannot use well-known members because per the spec
// the Span types involved in the Span conversions can be any that match the type name.
// Span<T>.op_Implicit(T[]) or ReadOnlySpan<T>.op_Implicit(T[])
if (source.Type is ArrayTypeSymbol)
{
reportUseSiteOrMissing(
TryFindImplicitOperatorFromArray(destination.OriginalDefinition),
destination.OriginalDefinition,
WellKnownMemberNames.ImplicitConversionName,
syntax,
diagnostics);
}
// ReadOnlySpan<T> Span<T>.op_Implicit(Span<T>)
if (source.Type.IsSpan())
{
Debug.Assert(destination.IsReadOnlySpan());
reportUseSiteOrMissing(
TryFindImplicitOperatorFromSpan(source.Type.OriginalDefinition, destination.OriginalDefinition),
source.Type.OriginalDefinition,
WellKnownMemberNames.ImplicitConversionName,
syntax,
diagnostics);
}
// ReadOnlySpan<T> ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>)
if (source.Type.IsSpan() || source.Type.IsReadOnlySpan())
{
Debug.Assert(destination.IsReadOnlySpan());
if (NeedsSpanCastUp(source.Type, destination))
{
// If converting Span<TDerived> -> ROS<TDerived> -> ROS<T>,
// the source of the CastUp is the return type of the op_Implicit (i.e., the ROS<TDerived>)
// which has the same original definition as the destination ROS<T>.
TypeSymbol sourceForCastUp = source.Type.IsSpan()
? destination.OriginalDefinition
: source.Type.OriginalDefinition;
MethodSymbol? castUpMethod = TryFindCastUpMethod(sourceForCastUp, destination.OriginalDefinition);
reportUseSiteOrMissing(
castUpMethod,
destination.OriginalDefinition,
WellKnownMemberNames.CastUpMethodName,
syntax,
diagnostics);
castUpMethod?
.AsMember((NamedTypeSymbol)destination)
.Construct([((NamedTypeSymbol)source.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]])
.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(Compilation, Conversions, includeNullability: false, syntax.Location, diagnostics));
}
}
// ReadOnlySpan<char> MemoryExtensions.AsSpan(string)
if (source.Type.IsStringType())
{
reportUseSiteOrMissing(
TryFindAsSpanCharMethod(Compilation, destination),
WellKnownMemberNames.MemoryExtensionsTypeFullName,
WellKnownMemberNames.AsSpanMethodName,
syntax,
diagnostics);
}
}
}
static void reportUseSiteOrMissing(MethodSymbol? method, object containingType, string methodName, SyntaxNode syntax, BindingDiagnosticBag diagnostics)
{
if (method is not null)
{
diagnostics.ReportUseSite(method, syntax);
}
else
{
Error(diagnostics,
ErrorCode.ERR_MissingPredefinedMember,
syntax,
containingType,
methodName);
}
}
}
// {type}.op_Implicit(T[])
internal static MethodSymbol? TryFindImplicitOperatorFromArray(TypeSymbol type)
{
Debug.Assert(type.IsSpan() || type.IsReadOnlySpan());
Debug.Assert(type.IsDefinition);
return TryFindImplicitOperator(type, 0, static (_, method) =>
method.Parameters[0].Type is ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol });
}
// ReadOnlySpan<T> Span<T>.op_Implicit(Span<T>)
internal static MethodSymbol? TryFindImplicitOperatorFromSpan(TypeSymbol spanType, TypeSymbol readonlySpanType)
{
Debug.Assert(spanType.IsSpan() && readonlySpanType.IsReadOnlySpan());
Debug.Assert(spanType.IsDefinition && readonlySpanType.IsDefinition);
return TryFindImplicitOperator(spanType, readonlySpanType,
static (readonlySpanType, method) => method.Parameters[0].Type.IsSpan() &&
readonlySpanType.Equals(method.ReturnType.OriginalDefinition, TypeCompareKind.ConsiderEverything));
}
private static MethodSymbol? TryFindImplicitOperator<TArg>(TypeSymbol type, TArg arg,
Func<TArg, MethodSymbol, bool> predicate)
{
return TryFindSingleMethod(type, WellKnownMemberNames.ImplicitConversionName, (predicate, arg),
static (arg, method) => method is
{
ParameterCount: 1,
Arity: 0,
IsStatic: true,
DeclaredAccessibility: Accessibility.Public,
} && arg.predicate(arg.arg, method));
}
internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert(source.IsSpan() || source.IsReadOnlySpan());
Debug.Assert(destination.IsReadOnlySpan());
Debug.Assert(!source.IsDefinition && !destination.IsDefinition);
var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
var sameElementTypes = sourceElementType.Equals(destinationElementType, TypeCompareKind.AllIgnoreOptions);
Debug.Assert(!source.IsReadOnlySpan() || !sameElementTypes);
return !sameElementTypes;
}
// ReadOnlySpan<T> ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) where TDerived : class
internal static MethodSymbol? TryFindCastUpMethod(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert(source.IsReadOnlySpan() && destination.IsReadOnlySpan());
Debug.Assert(source.IsDefinition && destination.IsDefinition);
return TryFindSingleMethod(destination, WellKnownMemberNames.CastUpMethodName, (source, destination),
static (arg, method) => method is
{
ParameterCount: 1,
Arity: 1,
IsStatic: true,
DeclaredAccessibility: Accessibility.Public,
Parameters: [{ } parameter],
TypeArgumentsWithAnnotations: [{ } typeArgument],
} &&
// parameter type is the source ReadOnlySpan<>
arg.source.Equals(parameter.Type.OriginalDefinition, TypeCompareKind.ConsiderEverything) &&
// return type is the destination ReadOnlySpan<>
arg.destination.Equals(method.ReturnType.OriginalDefinition, TypeCompareKind.ConsiderEverything) &&
// TDerived : class
typeArgument.Type.IsReferenceType &&
// parameter type argument is TDerived
((NamedTypeSymbol)parameter.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type.Equals(typeArgument.Type, TypeCompareKind.ConsiderEverything) &&
// return type argument is T
((NamedTypeSymbol)method.ReturnType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type.Equals(((NamedTypeSymbol)arg.destination).TypeParameters[0], TypeCompareKind.ConsiderEverything));
}
// ReadOnlySpan<char> MemoryExtensions.AsSpan(string)
internal static MethodSymbol? TryFindAsSpanCharMethod(CSharpCompilation compilation, TypeSymbol readOnlySpanType)
{
Debug.Assert(readOnlySpanType.IsReadOnlySpan());
Debug.Assert(!readOnlySpanType.IsDefinition);
Debug.Assert(((NamedTypeSymbol)readOnlySpanType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].SpecialType is SpecialType.System_Char);
MethodSymbol? result = null;
foreach (var memoryExtensionsType in compilation.GetTypesByMetadataName(WellKnownMemberNames.MemoryExtensionsTypeFullName))
{
if (memoryExtensionsType.DeclaredAccessibility == Accessibility.Public &&
TryFindSingleMethod(memoryExtensionsType.GetSymbol<NamedTypeSymbol>(), WellKnownMemberNames.AsSpanMethodName, 0,
static (_, method) => method is
{
ParameterCount: 1,
Arity: 0,
IsStatic: true,
DeclaredAccessibility: Accessibility.Public,
Parameters: [{ Type.SpecialType: SpecialType.System_String }]
}) is { } method &&
method.ReturnType.Equals(readOnlySpanType, TypeCompareKind.ConsiderEverything))
{
if (result is not null)
{
// Ambiguous member found.
return null;
}
result = method;
}
}
return result;
}
private static MethodSymbol? TryFindSingleMethod<TArg>(TypeSymbol type, string name, TArg arg, Func<TArg, MethodSymbol, bool> predicate)
{
var members = type.GetMembers(name);
MethodSymbol? result = null;
foreach (var member in members)
{
if (member is MethodSymbol method && predicate(arg, method))
{
if (result is not null)
{
// Ambiguous member found.
return null;
}
result = method;
}
}
return result;
}
private BoundExpression BindUnconvertedInterpolatedExpressionToFormattableStringFactory(BoundUnconvertedInterpolatedString unconvertedSource, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
Debug.Assert(destination.Equals(Compilation.GetWellKnownType(WellKnownType.System_IFormattable), TypeCompareKind.ConsiderEverything) ||
destination.Equals(Compilation.GetWellKnownType(WellKnownType.System_FormattableString), TypeCompareKind.ConsiderEverything));
ImmutableArray<BoundExpression> parts = BindInterpolatedStringPartsForFactory(unconvertedSource, diagnostics, out bool haveErrors);
var stringFactory = GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory, diagnostics, unconvertedSource.Syntax);
if (stringFactory.IsErrorType() || haveErrors)
{
return new BoundInterpolatedString(
unconvertedSource.Syntax,
interpolationData: null,
BindInterpolatedStringParts(unconvertedSource, diagnostics),
unconvertedSource.ConstantValueOpt,
unconvertedSource.Type,
unconvertedSource.HasErrors);
}
return BindUnconvertedInterpolatedExpressionToFactory(unconvertedSource, parts, stringFactory, factoryMethod: "Create", destination, diagnostics);
}
private static void CheckInlineArrayTypeIsSupported(SyntaxNode syntax, TypeSymbol inlineArrayType, TypeSymbol elementType, BindingDiagnosticBag diagnostics)
{
if (elementType.IsPointerOrFunctionPointer() || elementType.IsRestrictedType())
{
Error(diagnostics, ErrorCode.ERR_BadTypeArgument, syntax, elementType);
}
else if (inlineArrayType.IsRestrictedType())
{
Error(diagnostics, ErrorCode.ERR_BadTypeArgument, syntax, inlineArrayType);
}
}
private static BoundExpression ConvertObjectCreationExpression(
SyntaxNode syntax, BoundUnconvertedObjectCreationExpression node, Conversion conversion, bool isCast, TypeSymbol destination,
ConversionGroup? conversionGroupOpt, bool wasCompilerGenerated, BindingDiagnosticBag diagnostics)
{
var arguments = AnalyzedArguments.GetInstance(node.Arguments, node.ArgumentRefKindsOpt, node.ArgumentNamesOpt);
BoundExpression expr = bindObjectCreationExpression(node.Syntax, node.InitializerOpt, node.Binder, destination.StrippedType(), arguments, diagnostics);
arguments.Free();
Debug.Assert(expr is BoundObjectCreationExpressionBase { WasTargetTyped: true } or
BoundDelegateCreationExpression { WasTargetTyped: true } or
BoundBadExpression);
// Assert that the shape of the BoundBadExpression is sound and is not going to confuse NullableWalker for target-typed 'new'.
Debug.Assert(expr is not BoundBadExpression { ChildBoundNodes: var children } || !children.Any((child, node) => child.Syntax == node.Syntax, node));
if (wasCompilerGenerated)
{
expr.MakeCompilerGenerated();
}
expr = new BoundConversion(
syntax,
expr,
expr is BoundBadExpression ? Conversion.NoConversion : conversion,
node.Binder.CheckOverflowAtRuntime,
explicitCastInCode: isCast && !wasCompilerGenerated,
conversionGroupOpt,
expr.ConstantValueOpt,
destination)
{ WasCompilerGenerated = wasCompilerGenerated };
return expr;
static BoundExpression bindObjectCreationExpression(
SyntaxNode syntax, InitializerExpressionSyntax? initializerOpt, Binder binder,
TypeSymbol type, AnalyzedArguments arguments, BindingDiagnosticBag diagnostics)
{
switch (type.TypeKind)
{
case TypeKind.Enum:
case TypeKind.Struct:
case TypeKind.Class when !type.IsAnonymousType: // We don't want to enable object creation with unspeakable types
return binder.BindClassCreationExpression(syntax, type.Name, typeNode: syntax, (NamedTypeSymbol)type, arguments, diagnostics, initializerOpt, wasTargetTyped: true);
case TypeKind.TypeParameter:
return binder.BindTypeParameterCreationExpression(syntax, (TypeParameterSymbol)type, arguments, initializerOpt, typeSyntax: syntax, wasTargetTyped: true, diagnostics);
case TypeKind.Delegate:
return binder.BindDelegateCreationExpression(syntax, (NamedTypeSymbol)type, arguments, initializerOpt, wasTargetTyped: true, diagnostics);
case TypeKind.Interface:
return binder.BindInterfaceCreationExpression(syntax, (NamedTypeSymbol)type, diagnostics, typeNode: syntax, arguments, initializerOpt, wasTargetTyped: true);
case TypeKind.Array:
case TypeKind.Class:
case TypeKind.Dynamic:
Error(diagnostics, ErrorCode.ERR_ImplicitObjectCreationIllegalTargetType, syntax, type);
goto case TypeKind.Error;
case TypeKind.Pointer:
case TypeKind.FunctionPointer:
Error(diagnostics, ErrorCode.ERR_UnsafeTypeInObjectCreation, syntax, type);
goto case TypeKind.Error;
case TypeKind.Error:
return binder.MakeBadExpressionForObjectCreation(syntax, type, arguments, initializerOpt, typeSyntax: syntax, diagnostics);
case var v:
throw ExceptionUtilities.UnexpectedValue(v);
}
}
}
private BoundCollectionExpression ConvertCollectionExpression(
BoundUnconvertedCollectionExpression node,
TypeSymbol targetType,
Conversion conversion,
BindingDiagnosticBag diagnostics)
{
if (conversion.IsNullable)
{
targetType = targetType.GetNullableUnderlyingType();
conversion = conversion.UnderlyingConversions[0];
_ = GetSpecialTypeMember(SpecialMember.System_Nullable_T__ctor, diagnostics, syntax: node.Syntax);
}
var collectionTypeKind = conversion.GetCollectionExpressionTypeKind(out var elementType, out MethodSymbol? constructor, out bool isExpanded);
if (collectionTypeKind == CollectionExpressionTypeKind.None)
{
Debug.Assert(conversion.Kind is ConversionKind.NoConversion);
return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: false, diagnostics);
}
var syntax = node.Syntax;
if (LocalRewriter.IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType, Compilation))
{
diagnostics.Add(node.HasSpreadElements(out _, out _)
? ErrorCode.WRN_CollectionExpressionRefStructSpreadMayAllocate
: ErrorCode.WRN_CollectionExpressionRefStructMayAllocate,
syntax, targetType);
}
MethodSymbol? collectionBuilderMethod = null;
BoundValuePlaceholder? collectionBuilderInvocationPlaceholder = null;
BoundExpression? collectionBuilderInvocationConversion = null;
switch (collectionTypeKind)
{
case CollectionExpressionTypeKind.Span:
_ = GetWellKnownTypeMember(WellKnownMember.System_Span_T__ctor_Array, diagnostics, syntax: syntax);
break;
case CollectionExpressionTypeKind.ReadOnlySpan:
_ = GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Array, diagnostics, syntax: syntax);
break;
case CollectionExpressionTypeKind.CollectionBuilder:
{
Debug.Assert(elementType is { });
var namedType = (NamedTypeSymbol)targetType;
collectionBuilderMethod = GetAndValidateCollectionBuilderMethod(syntax, namedType, diagnostics, out var updatedElementType);
if (collectionBuilderMethod is null)
{
return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: true, diagnostics);
}
elementType = updatedElementType;
collectionBuilderInvocationPlaceholder = new BoundValuePlaceholder(syntax, collectionBuilderMethod.ReturnType) { WasCompilerGenerated = true };
collectionBuilderInvocationConversion = CreateConversion(collectionBuilderInvocationPlaceholder, targetType, diagnostics);
}
break;
case CollectionExpressionTypeKind.ImplementsIEnumerable:
if (targetType.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Collections_Immutable_ImmutableArray_T), TypeCompareKind.ConsiderEverything))
{
diagnostics.Add(ErrorCode.ERR_CollectionExpressionImmutableArray, syntax, targetType.OriginalDefinition);
return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: true, diagnostics);
}
break;
}
var elements = node.Elements;
var builder = ArrayBuilder<BoundNode>.GetInstance(elements.Length);
BoundExpression? collectionCreation = null;
BoundObjectOrCollectionValuePlaceholder? implicitReceiver = null;
if (collectionTypeKind is CollectionExpressionTypeKind.ImplementsIEnumerable)
{
if (targetType is NamedTypeSymbol namedType &&
HasParamsCollectionTypeInProgress(namedType, out NamedTypeSymbol? inProgress, out MethodSymbol? inProgressConstructor))
{
Debug.Assert(inProgressConstructor is not null);
diagnostics.Add(ErrorCode.ERR_ParamsCollectionInfiniteChainOfConstructorCalls, syntax, inProgress, inProgressConstructor.OriginalDefinition);
return BindCollectionExpressionForErrorRecovery(node, namedType, inConversion: true, diagnostics);
}
implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true };
collectionCreation = BindCollectionExpressionConstructor(syntax, targetType, constructor, diagnostics);
Debug.Assert((collectionCreation is BoundNewT && !isExpanded && constructor is null) ||
(collectionCreation is BoundObjectCreationExpression creation && creation.Expanded == isExpanded && creation.Constructor == constructor));
if (collectionCreation.HasErrors)
{
return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: true, diagnostics);
}
if (!elements.IsDefaultOrEmpty && HasCollectionInitializerTypeInProgress(syntax, targetType))
{
diagnostics.Add(ErrorCode.ERR_CollectionInitializerInfiniteChainOfAddCalls, syntax, targetType);
return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: true, diagnostics);
}
var collectionInitializerAddMethodBinder = new CollectionInitializerAddMethodBinder(syntax, targetType, this);
foreach (var element in elements)
{
BoundNode convertedElement = element is BoundCollectionExpressionSpreadElement spreadElement ?
(BoundNode)BindCollectionExpressionSpreadElementAddMethod(
(SpreadElementSyntax)spreadElement.Syntax,
spreadElement,
collectionInitializerAddMethodBinder,
implicitReceiver,
diagnostics) :
BindCollectionInitializerElementAddMethod(
element.Syntax,
ImmutableArray.Create((BoundExpression)element),
hasEnumerableInitializerType: true,
collectionInitializerAddMethodBinder,
diagnostics,
implicitReceiver);
builder.Add(convertedElement);
}
}
else
{
if ((collectionTypeKind is CollectionExpressionTypeKind.ArrayInterface) ||
node.HasSpreadElements(out _, out _))
{
// Verify the existence of the List<T> members that may be used in lowering, even
// though not all will be used for any particular collection expression. Checking all
// gives a consistent behavior, regardless of collection expression elements.
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctor, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctorInt32, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__Add, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ToArray, diagnostics, syntax: syntax);
}
var elementConversions = conversion.UnderlyingConversions;
Debug.Assert(elementType is { });
Debug.Assert(elements.Length == elementConversions.Length);
Debug.Assert(elementConversions.All(c => c.Exists));
for (int i = 0; i < elements.Length; i++)
{
var element = elements[i];
var elementConversion = elementConversions[i];
var convertedElement = element is BoundCollectionExpressionSpreadElement spreadElement ?
bindSpreadElement(
spreadElement,
elementType,
elementConversion,
diagnostics) :
CreateConversion(
element.Syntax,
(BoundExpression)element,
elementConversion,
isCast: false,
conversionGroupOpt: null,
destination: elementType,
diagnostics);
builder.Add(convertedElement!);
}
conversion.MarkUnderlyingConversionsChecked();
}
return new BoundCollectionExpression(
syntax,
collectionTypeKind,
implicitReceiver,
collectionCreation,
collectionBuilderMethod,
collectionBuilderInvocationPlaceholder,
collectionBuilderInvocationConversion,
wasTargetTyped: true,
node,
builder.ToImmutableAndFree(),
targetType)
{ WasCompilerGenerated = node.IsParamsArrayOrCollection, IsParamsArrayOrCollection = node.IsParamsArrayOrCollection };
BoundNode bindSpreadElement(BoundCollectionExpressionSpreadElement element, TypeSymbol elementType, Conversion elementConversion, BindingDiagnosticBag diagnostics)
{
var enumeratorInfo = element.EnumeratorInfoOpt;
Debug.Assert(enumeratorInfo is { });
Debug.Assert(enumeratorInfo.ElementType is { }); // ElementType is set always, even for IEnumerable.
var expressionSyntax = element.Expression.Syntax;
var elementPlaceholder = new BoundValuePlaceholder(expressionSyntax, enumeratorInfo.ElementType) { WasCompilerGenerated = true };
elementPlaceholder = (BoundValuePlaceholder)elementPlaceholder.WithSuppression(element.Expression.IsSuppressed);
var convertElement = CreateConversion(
expressionSyntax,
elementPlaceholder,
elementConversion,
isCast: false,
conversionGroupOpt: null,
destination: elementType,
diagnostics);
return element.Update(
element.Expression,
expressionPlaceholder: element.ExpressionPlaceholder,
conversion: element.Conversion,
enumeratorInfo,
elementPlaceholder: elementPlaceholder,
iteratorBody: new BoundExpressionStatement(expressionSyntax, convertElement) { WasCompilerGenerated = true },
lengthOrCount: element.LengthOrCount);
}
}
private bool HasCollectionInitializerTypeInProgress(SyntaxNode syntax, TypeSymbol targetType)
{
Binder? current = this;
while (current?.Flags.Includes(BinderFlags.CollectionInitializerAddMethod) == true)
{
if (current is CollectionInitializerAddMethodBinder binder &&
binder.Syntax == syntax &&
binder.CollectionType.OriginalDefinition.Equals(targetType.OriginalDefinition, TypeCompareKind.AllIgnoreOptions))
{
return true;
}
current = current.Next;
}
return false;
}
internal MethodSymbol? GetAndValidateCollectionBuilderMethod(
SyntaxNode syntax,
NamedTypeSymbol namedType,
BindingDiagnosticBag diagnostics,
out TypeSymbol? elementType)
{
MethodSymbol? collectionBuilderMethod;
bool result = namedType.HasCollectionBuilderAttribute(out TypeSymbol? builderType, out string? methodName);
Debug.Assert(result);
var targetTypeOriginalDefinition = namedType.OriginalDefinition;
result = TryGetCollectionIterationType(syntax, targetTypeOriginalDefinition, out TypeWithAnnotations elementTypeOriginalDefinition);
Debug.Assert(result);
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
Conversion collectionBuilderReturnTypeConversion;
collectionBuilderMethod = GetCollectionBuilderMethod(namedType, elementTypeOriginalDefinition.Type, builderType, methodName, ref useSiteInfo, out collectionBuilderReturnTypeConversion);
diagnostics.Add(syntax, useSiteInfo);
if (collectionBuilderMethod is null)
{
diagnostics.Add(ErrorCode.ERR_CollectionBuilderAttributeMethodNotFound, syntax, methodName ?? "", elementTypeOriginalDefinition, targetTypeOriginalDefinition);
elementType = null;
return null;
}
Debug.Assert(collectionBuilderReturnTypeConversion.Exists);
ReportUseSite(collectionBuilderMethod, diagnostics, syntax.Location);
var parameterType = (NamedTypeSymbol)collectionBuilderMethod.Parameters[0].Type;
Debug.Assert(parameterType.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions));
elementType = parameterType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
collectionBuilderMethod.CheckConstraints(
new ConstraintsHelper.CheckConstraintsArgs(Compilation, Conversions, syntax.Location, diagnostics));
ReportDiagnosticsIfObsolete(diagnostics, collectionBuilderMethod.ContainingType, syntax, hasBaseReceiver: false);
ReportDiagnosticsIfObsolete(diagnostics, collectionBuilderMethod, syntax, hasBaseReceiver: false);
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, collectionBuilderMethod, syntax, isDelegateConversion: false);
return collectionBuilderMethod;
}
internal BoundExpression BindCollectionExpressionConstructor(SyntaxNode syntax, TypeSymbol targetType, MethodSymbol? constructor, BindingDiagnosticBag diagnostics)
{
//
// !!! ATTENTION !!!
//
// In terms of errors relevant for HasCollectionExpressionApplicableConstructor check
// this function should be kept in sync with HasCollectionExpressionApplicableConstructor.
//
BoundExpression collectionCreation;
var analyzedArguments = AnalyzedArguments.GetInstance();
if (targetType is NamedTypeSymbol namedType)
{
var binder = new ParamsCollectionTypeInProgressBinder(namedType, this, constructor);
collectionCreation = binder.BindClassCreationExpression(syntax, namedType.Name, syntax, namedType, analyzedArguments, diagnostics);
collectionCreation.WasCompilerGenerated = true;
}
else if (targetType is TypeParameterSymbol typeParameter)
{
collectionCreation = BindTypeParameterCreationExpression(syntax, typeParameter, analyzedArguments, initializerOpt: null, typeSyntax: syntax, wasTargetTyped: true, diagnostics);
collectionCreation.WasCompilerGenerated = true;
}
else
{
throw ExceptionUtilities.UnexpectedValue(targetType);
}
analyzedArguments.Free();
return collectionCreation;
}
internal bool HasCollectionExpressionApplicableConstructor(SyntaxNode syntax, TypeSymbol targetType, out MethodSymbol? constructor, out bool isExpanded, BindingDiagnosticBag diagnostics, bool isParamsModifierValidation = false)
{
Debug.Assert(!isParamsModifierValidation || syntax is ParameterSyntax);
// This is what BindClassCreationExpression is doing in terms of reporting diagnostics
constructor = null;
isExpanded = false;
if (targetType is NamedTypeSymbol namedType)
{
// This is what BindClassCreationExpression called by BindCollectionExpressionConstructor is doing in terms of reporting diagnostics
if (namedType.IsAbstract)
{
// Report error for new of abstract type.
diagnostics.Add(ErrorCode.ERR_NoNewAbstract, syntax.Location, namedType);
return false;
}
if (HasParamsCollectionTypeInProgress(namedType, out _, out _))
{
// We are in a cycle. Optimistically assume we have the right constructor to break the cycle
return true;
}
var analyzedArguments = AnalyzedArguments.GetInstance();
var binder = new ParamsCollectionTypeInProgressBinder(namedType, this);
bool overloadResolutionSucceeded = binder.TryPerformConstructorOverloadResolution(
namedType,
analyzedArguments,
namedType.Name,
syntax.Location,
suppressResultDiagnostics: false,
diagnostics,
out MemberResolutionResult<MethodSymbol> memberResolutionResult,
candidateConstructors: out _,
allowProtectedConstructorsOfBaseType: false,
out CompoundUseSiteInfo<AssemblySymbol> overloadResolutionUseSiteInfo,
isParamsModifierValidation: isParamsModifierValidation);
analyzedArguments.Free();
if (overloadResolutionSucceeded)
{
bindClassCreationExpressionContinued(binder, syntax, memberResolutionResult, in overloadResolutionUseSiteInfo, isParamsModifierValidation, diagnostics);
constructor = memberResolutionResult.Member;
isExpanded = memberResolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm;
}
else
{
reportAdditionalDiagnosticsForOverloadResolutionFailure(syntax, in overloadResolutionUseSiteInfo, diagnostics);
}
return overloadResolutionSucceeded;
}
else if (targetType is TypeParameterSymbol typeParameter)
{
return TypeParameterHasParameterlessConstructor(syntax, typeParameter, diagnostics);
}
else
{
throw ExceptionUtilities.UnexpectedValue(targetType);
}
// This is what BindClassCreationExpressionContinued is doing in terms of reporting diagnostics
static void bindClassCreationExpressionContinued(
Binder binder,
SyntaxNode node,
MemberResolutionResult<MethodSymbol> memberResolutionResult,
in CompoundUseSiteInfo<AssemblySymbol> overloadResolutionUseSiteInfo,
bool isParamsModifierValidation,
BindingDiagnosticBag diagnostics)
{
ReportConstructorUseSiteDiagnostics(node.Location, diagnostics, suppressUnsupportedRequiredMembersError: false, in overloadResolutionUseSiteInfo);
var method = memberResolutionResult.Member;
binder.ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false);
// NOTE: Use-site diagnostics were reported during overload resolution.
ImmutableSegmentedDictionary<string, Symbol> requiredMembers = GetMembersRequiringInitialization(method);
if (requiredMembers.Count != 0)
{
if (isParamsModifierValidation)
{
diagnostics.Add(
ErrorCode.ERR_ParamsCollectionConstructorDoesntInitializeRequiredMember,
((ParameterSyntax)node).Modifiers.First(static m => m.IsKind(SyntaxKind.ParamsKeyword)).GetLocation(),
method, requiredMembers.First().Value);
}
else
{
ReportMembersRequiringInitialization(node, requiredMembers.ToBuilder(), diagnostics);
}
}
}
// This is what CreateBadClassCreationExpression is doing in terms of reporting diagnostics
static void reportAdditionalDiagnosticsForOverloadResolutionFailure(
SyntaxNode typeNode,
in CompoundUseSiteInfo<AssemblySymbol> overloadResolutionUseSiteInfo,
BindingDiagnosticBag diagnostics)
{
ReportConstructorUseSiteDiagnostics(typeNode.Location, diagnostics, suppressUnsupportedRequiredMembersError: false, in overloadResolutionUseSiteInfo);
}
}
private bool HasParamsCollectionTypeInProgress(NamedTypeSymbol toCheck,
[NotNullWhen(returnValue: true)] out NamedTypeSymbol? inProgress,
out MethodSymbol? constructor)
{
Binder? current = this;
while (current?.Flags.Includes(BinderFlags.CollectionExpressionConversionValidation) == true)
{
if (current.ParamsCollectionTypeInProgress?.OriginalDefinition.Equals(toCheck.OriginalDefinition, TypeCompareKind.AllIgnoreOptions) == true)
{
// We are in a cycle.
inProgress = current.ParamsCollectionTypeInProgress;
constructor = current.ParamsCollectionConstructorInProgress;
return true;
}
current = current.Next;
}
inProgress = null;
constructor = null;
return false;
}
internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, TypeSymbol targetType, out ImmutableArray<MethodSymbol> addMethods, BindingDiagnosticBag diagnostics)
{
Debug.Assert(!targetType.IsDynamic());
NamedTypeSymbol? namedType = targetType as NamedTypeSymbol;
if (namedType is not null && HasParamsCollectionTypeInProgress(namedType, out _, out _))
{
// We are in a cycle. Optimistically assume we have the right Add to break the cycle
addMethods = [];
return true;
}
var implicitReceiver = new BoundObjectOrCollectionValuePlaceholder(syntax, isNewInstance: true, targetType) { WasCompilerGenerated = true };
// For the element, we create a dynamic argument and will be forcing overload resolution to convert it to any type.
// This way we are going to do most of the work in terms of determining applicability of 'Add' method candidates
// in overload resolution.
var elementPlaceholder = new BoundValuePlaceholder(syntax, Compilation.DynamicType) { WasCompilerGenerated = true };
var addMethodBinder = WithAdditionalFlags(BinderFlags.CollectionInitializerAddMethod | BinderFlags.CollectionExpressionConversionValidation);
if (namedType is not null)
{
addMethodBinder = new ParamsCollectionTypeInProgressBinder(namedType, addMethodBinder);
}
return bindCollectionInitializerElementAddMethod(
addMethodBinder,
syntax,
elementPlaceholder,
diagnostics,
implicitReceiver,
out addMethods);
// This is what BindCollectionInitializerElementAddMethod is doing in terms of reporting diagnostics and detecting a failure
static bool bindCollectionInitializerElementAddMethod(
Binder addMethodBinder,
SyntaxNode elementInitializer,
BoundValuePlaceholder arg,
BindingDiagnosticBag diagnostics,
BoundObjectOrCollectionValuePlaceholder implicitReceiver,
out ImmutableArray<MethodSymbol> addMethods)
{
return makeInvocationExpression(
addMethodBinder,
elementInitializer,
implicitReceiver,
arg: arg,
diagnostics,
out addMethods);
}
// This is what MakeInvocationExpression is doing in terms of reporting diagnostics and detecting a failure
static bool makeInvocationExpression(
Binder addMethodBinder,
SyntaxNode node,
BoundExpression receiver,
BoundValuePlaceholder arg,
BindingDiagnosticBag diagnostics,
out ImmutableArray<MethodSymbol> addMethods)
{
var boundExpression = addMethodBinder.BindInstanceMemberAccess(
node, node, receiver, WellKnownMemberNames.CollectionInitializerAddMethodName, rightArity: 0,
typeArgumentsSyntax: default(SeparatedSyntaxList<TypeSyntax>),
typeArgumentsWithAnnotations: default(ImmutableArray<TypeWithAnnotations>),
invoked: true, indexed: false, diagnostics, searchExtensionMethodsIfNecessary: true);
// require the target member to be a method.
if (boundExpression.Kind == BoundKind.FieldAccess || boundExpression.Kind == BoundKind.PropertyAccess)
{
ReportMakeInvocationExpressionBadMemberKind(node, WellKnownMemberNames.CollectionInitializerAddMethodName, boundExpression, diagnostics);
addMethods = [];
return false;
}
if (boundExpression.Kind != BoundKind.MethodGroup)
{
Debug.Assert(boundExpression.HasErrors);
addMethods = [];
return false;
}
var analyzedArguments = AnalyzedArguments.GetInstance();
analyzedArguments.Arguments.AddRange(arg);
bool result = bindInvocationExpression(
addMethodBinder, node, node, (BoundMethodGroup)boundExpression, analyzedArguments, diagnostics, out addMethods);
analyzedArguments.Free();
return result;
}
// This is what BindInvocationExpression is doing in terms of reporting diagnostics and detecting a failure
static bool bindInvocationExpression(
Binder addMethodBinder,
SyntaxNode node,
SyntaxNode expression,
BoundMethodGroup boundExpression,
AnalyzedArguments analyzedArguments,
BindingDiagnosticBag diagnostics,
out ImmutableArray<MethodSymbol> addMethods)
{
return bindMethodGroupInvocation(
addMethodBinder, node, expression, boundExpression, analyzedArguments,
diagnostics, out addMethods);
}
// This is what BindMethodGroupInvocation is doing in terms of reporting diagnostics and detecting a failure
static bool bindMethodGroupInvocation(
Binder addMethodBinder,
SyntaxNode syntax,
SyntaxNode expression,
BoundMethodGroup methodGroup,
AnalyzedArguments analyzedArguments,
BindingDiagnosticBag diagnostics,
out ImmutableArray<MethodSymbol> addMethods)
{
Debug.Assert(methodGroup.ReceiverOpt is not null);
Debug.Assert(methodGroup.ReceiverOpt.Type is not null);
bool result;
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = addMethodBinder.GetNewCompoundUseSiteInfo(diagnostics);
var resolution = addMethodBinder.ResolveMethodGroup(
methodGroup, expression, WellKnownMemberNames.CollectionInitializerAddMethodName, analyzedArguments,
useSiteInfo: ref useSiteInfo,
options: OverloadResolution.Options.DynamicResolution | OverloadResolution.Options.DynamicConvertsToAnything);
diagnostics.Add(expression, useSiteInfo);
if (!methodGroup.HasAnyErrors) diagnostics.AddRange(resolution.Diagnostics); // Suppress cascading.
if (resolution.HasAnyErrors)
{
addMethods = [];
result = false;
}
else if (!resolution.IsEmpty)
{
// We're checking resolution.ResultKind, rather than methodGroup.HasErrors
// to better handle the case where there's a problem with the receiver
// (e.g. inaccessible), but the method group resolved correctly (e.g. because
// it's actually an accessible static method on a base type).
// CONSIDER: could check for error types amongst method group type arguments.
if (resolution.ResultKind != LookupResultKind.Viable)
{
addMethods = [];
result = false;
}
else
{
Debug.Assert(resolution.AnalyzedArguments.HasDynamicArgument);
// If overload resolution found one or more applicable methods and at least one argument
// was dynamic then treat this as a dynamic call.
if (resolution.OverloadResolutionResult.HasAnyApplicableMember)
{
// Note that the runtime binder may consider candidates that haven't passed compile-time final validation
// and an ambiguity error may be reported. Also additional checks are performed in runtime final validation
// that are not performed at compile-time.
// Only if the set of final applicable candidates is empty we know for sure the call will fail at runtime.
var finalApplicableCandidates = addMethodBinder.GetCandidatesPassingFinalValidation(syntax, resolution.OverloadResolutionResult,
methodGroup.ReceiverOpt,
methodGroup.TypeArgumentsOpt,
invokedAsExtensionMethod: resolution.IsExtensionMethodGroup,
diagnostics);
Debug.Assert(finalApplicableCandidates.Length != 1 || finalApplicableCandidates[0].IsApplicable);
if (finalApplicableCandidates.Length == 0)
{
addMethods = [];
result = false;
}
else
{
addMethods = filterOutBadGenericMethods(addMethodBinder, syntax, methodGroup, analyzedArguments, resolution, finalApplicableCandidates, ref useSiteInfo);
result = !addMethods.IsEmpty;
if (!result)
{
diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, syntax, methodGroup.ReceiverOpt.Type);
}
else if (addMethods.Length == 1)
{
addMethodBinder.ReportDiagnosticsIfObsolete(diagnostics, addMethods[0], syntax, hasBaseReceiver: false);
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, addMethods[0], syntax, isDelegateConversion: false);
}
}
}
else
{
result = bindInvocationExpressionContinued(
addMethodBinder, syntax, expression, resolution.OverloadResolutionResult, resolution.AnalyzedArguments,
resolution.MethodGroup, diagnostics: diagnostics, out var addMethod);
addMethods = addMethod is null ? [] : [addMethod];
}
}
}
else
{
addMethods = [];
result = false;
}
resolution.Free();
return result;
}
static ImmutableArray<MethodSymbol> filterOutBadGenericMethods(
Binder addMethodBinder, SyntaxNode syntax, BoundMethodGroup methodGroup, AnalyzedArguments analyzedArguments, MethodGroupResolution resolution,
ImmutableArray<MemberResolutionResult<MethodSymbol>> finalApplicableCandidates, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(methodGroup.ReceiverOpt is not null);
var resultBuilder = ArrayBuilder<MethodSymbol>.GetInstance(finalApplicableCandidates.Length);
foreach (var candidate in finalApplicableCandidates)
{
// If the method is generic, skip it if the type arguments cannot be inferred.
var member = candidate.Member;
var typeParameters = member.TypeParameters;
if (!typeParameters.IsEmpty)
{
if (resolution.IsExtensionMethodGroup)
{
// We need to validate an ability to infer type arguments as well as check conversion to 'this' parameter.
// Overload resolution doesn't check the conversion when 'this' type refers to a type parameter
TypeSymbol? receiverType = methodGroup.ReceiverOpt.Type;
Debug.Assert(receiverType is not null);
bool thisTypeIsOpen = typeParameters.Any((typeParameter, parameter) => parameter.Type.ContainsTypeParameter(typeParameter), member.Parameters[0]);
MethodSymbol? constructed = null;
bool wasFullyInferred = false;
if (thisTypeIsOpen)
{
constructed = ReducedExtensionMethodSymbol.InferExtensionMethodTypeArguments(
member, receiverType, addMethodBinder.Compilation, ref useSiteInfo, out wasFullyInferred);
}
if (constructed is null || !wasFullyInferred)
{
// It is quite possible that inference failed because we didn't supply type from the second argument
if (!typeParameters.Any((typeParameter, parameter) => parameter.Type.ContainsTypeParameter(typeParameter), member.Parameters[1]))
{
continue;
}
// Let's attempt inference with type for the second parameter
// We are going to use the second parameter's type for that
OverloadResolution.GetEffectiveParameterTypes(
member,
argumentCount: 2,
argToParamMap: default,
argumentRefKinds: analyzedArguments.RefKinds,
isMethodGroupConversion: false,
allowRefOmittedArguments: methodGroup.ReceiverOpt.IsExpressionOfComImportType(),
binder: addMethodBinder,
expanded: candidate.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm,
parameterTypes: out ImmutableArray<TypeWithAnnotations> parameterTypes,
parameterRefKinds: out ImmutableArray<RefKind> parameterRefKinds);
// If we were able to infer something just from the first parameter,
// use partially substituted second type, otherwise inference might fail
// for type parameters "shared" between the parameters.
TypeSymbol secondArgumentType = (constructed ?? member).Parameters[1].Type;
MethodTypeInferenceResult inferenceResult = MethodTypeInferrer.Infer(
addMethodBinder,
addMethodBinder.Conversions,
member.TypeParameters,
member.ContainingType,
parameterTypes,
parameterRefKinds,
ImmutableArray.Create<BoundExpression>(methodGroup.ReceiverOpt, new BoundValuePlaceholder(syntax, secondArgumentType) { WasCompilerGenerated = true }),
ref useSiteInfo);
if (!inferenceResult.Success)
{
continue;
}
if (thisTypeIsOpen)
{
constructed = member.Construct(inferenceResult.InferredTypeArguments);
}
}
if (thisTypeIsOpen)
{
Debug.Assert(constructed is not null);
var conversions = constructed.ContainingAssembly.CorLibrary.TypeConversions;
var conversion = conversions.ConvertExtensionMethodThisArg(constructed.Parameters[0].Type, receiverType, ref useSiteInfo, isMethodGroupConversion: false);
if (!conversion.Exists)
{
continue; // Conversion to 'this' parameter failed
}
}
}
else if (typeParameters.Any((typeParameter, parameter) => !parameter.Type.ContainsTypeParameter(typeParameter), member.Parameters[0]))
{
// A type parameter does not appear in the parameter type.
continue;
}
}
resultBuilder.Add(member);
}
return resultBuilder.ToImmutableAndFree();
}
// This is what BindInvocationExpressionContinued is doing in terms of reporting diagnostics and detecting a failure
static bool bindInvocationExpressionContinued(
Binder addMethodBinder,
SyntaxNode node,
SyntaxNode expression,
OverloadResolutionResult<MethodSymbol> result,
AnalyzedArguments analyzedArguments,
MethodGroup methodGroup,
BindingDiagnosticBag diagnostics,
out MethodSymbol? addMethod)
{
Debug.Assert(node != null);
Debug.Assert(methodGroup != null);
Debug.Assert(methodGroup.Error == null);
Debug.Assert(methodGroup.Methods.Count > 0);
var invokedAsExtensionMethod = methodGroup.IsExtensionMethodGroup;
// We have already determined that we are not in a situation where we can successfully do
// a dynamic binding. We might be in one of the following situations:
//
// * There were dynamic arguments but overload resolution still found zero applicable candidates.
// * There were no dynamic arguments and overload resolution found zero applicable candidates.
// * There were no dynamic arguments and overload resolution found multiple applicable candidates
// without being able to find the best one.
//
// In those three situations we might give an additional error.
if (!result.Succeeded)
{
// Since there were no argument errors to report, we report an error on the invocation itself.
result.ReportDiagnostics(
binder: addMethodBinder, location: GetLocationForOverloadResolutionDiagnostic(node, expression), nodeOpt: node, diagnostics: diagnostics, name: WellKnownMemberNames.CollectionInitializerAddMethodName,
receiver: methodGroup.Receiver, invokedExpression: expression, arguments: analyzedArguments, memberGroup: methodGroup.Methods.ToImmutable(),
typeContainingConstructor: null, delegateTypeBeingInvoked: null, queryClause: null);
addMethod = null;
return false;
}
// Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate.
// We still have to determine if it passes final validation.
var methodResult = result.ValidResult;
var method = methodResult.Member;
// It is possible that overload resolution succeeded, but we have chosen an
// instance method and we're in a static method. A careful reading of the
// overload resolution spec shows that the "final validation" stage allows an
// "implicit this" on any method call, not just method calls from inside
// instance methods. Therefore we must detect this scenario here, rather than in
// overload resolution.
var receiver = methodGroup.Receiver;
// Note: we specifically want to do final validation (7.6.5.1) without checking delegate compatibility (15.2),
// so we're calling MethodGroupFinalValidation directly, rather than via MethodGroupConversionHasErrors.
// Note: final validation wants the receiver that corresponds to the source representation
// (i.e. the first argument, if invokedAsExtensionMethod).
var gotError = addMethodBinder.MemberGroupFinalValidation(receiver, method, expression, diagnostics, invokedAsExtensionMethod);
addMethodBinder.ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false);
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false);
// No use site errors, but there could be use site warnings.
// If there are any use site warnings, they have already been reported by overload resolution.
Debug.Assert(!method.HasUseSiteError, "Shouldn't have reached this point if there were use site errors.");
Debug.Assert(!method.IsRuntimeFinalizer());
addMethod = method;
return !gotError;
}
}
/// <summary>
/// If the element is from a collection type where elements are added with collection initializers,
/// return the argument to the collection initializer Add method or null if the element is not a
/// collection initializer node. Otherwise, return the element as is.
/// </summary>
internal static BoundExpression GetUnderlyingCollectionExpressionElement(BoundCollectionExpression expr, BoundExpression element, bool throwOnErrors)
{
if (expr.CollectionTypeKind is CollectionExpressionTypeKind.ImplementsIEnumerable)
{
switch (element)
{
case BoundCollectionElementInitializer collectionInitializer:
return getCollectionInitializerElement(collectionInitializer);
case BoundDynamicCollectionElementInitializer dynamicInitializer:
return dynamicInitializer.Arguments[0];
}
if (throwOnErrors)
{
throw ExceptionUtilities.UnexpectedValue(element);
}
// Handle error cases from bindCollectionInitializerElementAddMethod.
switch (element)
{
case BoundCall call:
// Overload resolution failed with one or more applicable or ambiguous
// Add methods. This case can be hit for spreads and non-spread elements.
Debug.Assert(call.HasErrors);
Debug.Assert(call.Method.Name == "Add");
return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0];
case BoundBadExpression badExpression:
Debug.Assert(false); // Add test if we hit this assert.
return badExpression;
default:
throw ExceptionUtilities.UnexpectedValue(element);
}
}
return element;
static BoundExpression getCollectionInitializerElement(BoundCollectionElementInitializer collectionInitializer)
{
int argIndex = collectionInitializer.InvokedAsExtensionMethod ? 1 : 0;
var arg = collectionInitializer.Arguments[argIndex];
Debug.Assert(!collectionInitializer.DefaultArguments[argIndex]);
if (collectionInitializer.Expanded && argIndex == collectionInitializer.AddMethod.ParameterCount - 1)
{
if (arg.IsParamsArrayOrCollection)
{
if (arg is BoundArrayCreation { InitializerOpt.Initializers: [var arrayElement] })
{
return arrayElement;
}
else if (arg is BoundConversion { Operand: BoundCollectionExpression { Elements: [BoundExpression collectionElement] } })
{
return collectionElement;
}
}
Debug.Assert(false);
}
return arg;
}
}
internal bool TryGetCollectionIterationType(SyntaxNode syntax, TypeSymbol collectionType, out TypeWithAnnotations iterationType)
{
BoundExpression collectionExpr = new BoundValuePlaceholder(syntax, collectionType);
bool result = GetEnumeratorInfoAndInferCollectionElementType(
syntax,
syntax,
ref collectionExpr,
isAsync: false,
isSpread: false,
BindingDiagnosticBag.Discarded,
out iterationType,
builder: out var builder);
// Collection expression target types require instance method GetEnumerator.
if (result && builder.ViaExtensionMethod)
{
iterationType = default;
return false;
}
return result;
}
private BoundCollectionExpression BindCollectionExpressionForErrorRecovery(
BoundUnconvertedCollectionExpression node,
TypeSymbol targetType,
bool inConversion,
BindingDiagnosticBag diagnostics)
{
var syntax = node.Syntax;
var builder = ArrayBuilder<BoundNode>.GetInstance(node.Elements.Length);
foreach (var element in node.Elements)
{
var result = element is BoundExpression expression ?
BindToNaturalType(expression, diagnostics, reportNoTargetType: !targetType.IsErrorType()) :
element;
builder.Add(result);
}
return new BoundCollectionExpression(
syntax,
collectionTypeKind: CollectionExpressionTypeKind.None,
placeholder: null,
collectionCreation: null,
collectionBuilderMethod: null,
collectionBuilderInvocationPlaceholder: null,
collectionBuilderInvocationConversion: null,
wasTargetTyped: inConversion,
node,
elements: builder.ToImmutableAndFree(),
targetType,
hasErrors: true)
{ WasCompilerGenerated = node.IsParamsArrayOrCollection, IsParamsArrayOrCollection = node.IsParamsArrayOrCollection };
}
internal void GenerateImplicitConversionErrorForCollectionExpression(
BoundUnconvertedCollectionExpression node,
TypeSymbol targetType,
BindingDiagnosticBag diagnostics)
{
var collectionTypeKind = ConversionsBase.GetCollectionExpressionTypeKind(Compilation, targetType, out TypeWithAnnotations elementTypeWithAnnotations);
switch (collectionTypeKind)
{
case CollectionExpressionTypeKind.ImplementsIEnumerable:
case CollectionExpressionTypeKind.CollectionBuilder:
Debug.Assert(elementTypeWithAnnotations.Type is null); // GetCollectionExpressionTypeKind() does not set elementType for these cases.
if (!TryGetCollectionIterationType(node.Syntax, targetType, out elementTypeWithAnnotations))
{
Error(
diagnostics,
collectionTypeKind == CollectionExpressionTypeKind.CollectionBuilder ?
ErrorCode.ERR_CollectionBuilderNoElementType :
ErrorCode.ERR_CollectionExpressionTargetNoElementType,
node.Syntax,
targetType);
return;
}
Debug.Assert(elementTypeWithAnnotations.HasType);
break;
}
bool reportedErrors = false;
if (collectionTypeKind != CollectionExpressionTypeKind.None)
{
var elements = node.Elements;
var elementType = elementTypeWithAnnotations.Type;
Debug.Assert(elementType is { });
if (collectionTypeKind == CollectionExpressionTypeKind.ImplementsIEnumerable)
{
if (!HasCollectionExpressionApplicableConstructor(node.Syntax, targetType, constructor: out _, isExpanded: out _, diagnostics))
{
reportedErrors = true;
}
if (elements.Length > 0 &&
!HasCollectionExpressionApplicableAddMethod(node.Syntax, targetType, addMethods: out _, diagnostics))
{
reportedErrors = true;
}
}
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
foreach (var element in elements)
{
if (element is BoundCollectionExpressionSpreadElement spreadElement)
{
var enumeratorInfo = spreadElement.EnumeratorInfoOpt;
if (enumeratorInfo is null)
{
Error(diagnostics, ErrorCode.ERR_NoImplicitConv, spreadElement.Expression.Syntax, spreadElement.Expression.Display, elementType);
reportedErrors = true;
}
else
{
Conversion elementConversion = Conversions.GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo);
if (!elementConversion.Exists)
{
GenerateImplicitConversionError(diagnostics, this.Compilation, spreadElement.Expression.Syntax, elementConversion, enumeratorInfo.ElementType, elementType);
reportedErrors = true;
}
}
}
else
{
Conversion elementConversion = Conversions.ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo);
if (!elementConversion.Exists)
{
GenerateImplicitConversionError(diagnostics, element.Syntax, elementConversion, (BoundExpression)element, elementType);
reportedErrors = true;
}
}
}
Debug.Assert(reportedErrors);
}
if (!reportedErrors)
{
Error(diagnostics, ErrorCode.ERR_CollectionExpressionTargetTypeNotConstructible, node.Syntax, targetType);
}
return;
}
private MethodSymbol? GetCollectionBuilderMethod(
NamedTypeSymbol targetType,
TypeSymbol elementTypeOriginalDefinition,
TypeSymbol? builderType,
string? methodName,
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
out Conversion returnTypeConversion)
{
returnTypeConversion = default;
if (!SourceNamedTypeSymbol.IsValidCollectionBuilderType(builderType))
{
return null;
}
if (string.IsNullOrEmpty(methodName))
{
return null;
}
var readOnlySpanType = Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T);
foreach (var candidate in builderType.GetMembers(methodName))
{
if (candidate is not MethodSymbol { IsStatic: true } method)
{
continue;
}
var candidateUseSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(useSiteInfo);
if (!IsAccessible(method, ref candidateUseSiteInfo))
{
continue;
}
var builder = ArrayBuilder<TypeWithAnnotations>.GetInstance();
targetType.GetAllTypeArgumentsNoUseSiteDiagnostics(builder);
var allTypeArguments = builder.ToImmutableAndFree();
if (method.Arity != allTypeArguments.Length)
{
continue;
}
if (method.Parameters is not [{ RefKind: RefKind.None, Type: var parameterType }]
|| !readOnlySpanType.Equals(parameterType.OriginalDefinition, TypeCompareKind.AllIgnoreOptions))
{
continue;
}
MethodSymbol methodWithTargetTypeParameters; // builder method substituted with type parameters from target type
if (allTypeArguments.Length > 0)
{
var allTypeParameters = TypeMap.TypeParametersAsTypeSymbolsWithAnnotations(targetType.OriginalDefinition.GetAllTypeParameters());
methodWithTargetTypeParameters = method.OriginalDefinition.Construct(allTypeParameters);
method = method.Construct(allTypeArguments);
}
else
{
methodWithTargetTypeParameters = method;
}
var spanTypeArg = ((NamedTypeSymbol)methodWithTargetTypeParameters.Parameters[0].Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
var conversion = Conversions.ClassifyImplicitConversionFromType(elementTypeOriginalDefinition, spanTypeArg, ref candidateUseSiteInfo);
if (!conversion.IsIdentity)
{
continue;
}
conversion = Conversions.ClassifyImplicitConversionFromType(methodWithTargetTypeParameters.ReturnType, targetType.OriginalDefinition, ref candidateUseSiteInfo);
switch (conversion.Kind)
{
case ConversionKind.Identity:
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
break;
default:
continue;
}
useSiteInfo.AddDiagnostics(candidateUseSiteInfo.Diagnostics);
returnTypeConversion = conversion;
return method;
}
return null;
}
/// <summary>
/// Rewrite the subexpressions in a conditional expression to convert the whole thing to the destination type.
/// </summary>
private BoundExpression ConvertConditionalExpression(
BoundUnconvertedConditionalOperator source,
TypeSymbol destination,
Conversion? conversionIfTargetTyped,
BindingDiagnosticBag diagnostics,
bool hasErrors = false)
{
bool targetTyped = conversionIfTargetTyped is { };
Debug.Assert(targetTyped || destination.IsErrorType() || destination.Equals(source.Type, TypeCompareKind.ConsiderEverything));
var conversion = conversionIfTargetTyped.GetValueOrDefault();
ImmutableArray<Conversion> underlyingConversions = conversion.UnderlyingConversions;
var condition = source.Condition;
hasErrors |= source.HasErrors || destination.IsErrorType();
var trueExpr =
targetTyped
? CreateConversion(source.Consequence.Syntax, source.Consequence, underlyingConversions[0], isCast: false, conversionGroupOpt: null, destination, diagnostics)
: GenerateConversionForAssignment(destination, source.Consequence, diagnostics);
var falseExpr =
targetTyped
? CreateConversion(source.Alternative.Syntax, source.Alternative, underlyingConversions[1], isCast: false, conversionGroupOpt: null, destination, diagnostics)
: GenerateConversionForAssignment(destination, source.Alternative, diagnostics);
conversion.MarkUnderlyingConversionsChecked();
var constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr);
hasErrors |= constantValue?.IsBad == true;
if (targetTyped && !destination.IsErrorType() && !Compilation.IsFeatureEnabled(MessageID.IDS_FeatureTargetTypedConditional))
{
diagnostics.Add(
ErrorCode.ERR_NoImplicitConvTargetTypedConditional,
source.Syntax.Location,
Compilation.LanguageVersion.ToDisplayString(),
source.Consequence.Display,
source.Alternative.Display,
new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureTargetTypedConditional.RequiredVersion()));
}
return new BoundConditionalOperator(source.Syntax, isRef: false, condition, trueExpr, falseExpr, constantValue, source.Type, wasTargetTyped: targetTyped, destination, hasErrors)
.WithSuppression(source.IsSuppressed);
}
/// <summary>
/// Rewrite the expressions in the switch expression arms to add a conversion to the destination type.
/// </summary>
private BoundExpression ConvertSwitchExpression(BoundUnconvertedSwitchExpression source, TypeSymbol destination, Conversion? conversionIfTargetTyped, BindingDiagnosticBag diagnostics, bool hasErrors = false)
{
bool targetTyped = conversionIfTargetTyped is { };
Conversion conversion = conversionIfTargetTyped ?? Conversion.Identity;
Debug.Assert(targetTyped || destination.IsErrorType() || destination.Equals(source.Type, TypeCompareKind.ConsiderEverything));
ImmutableArray<Conversion> underlyingConversions = conversion.UnderlyingConversions;
var builder = ArrayBuilder<BoundSwitchExpressionArm>.GetInstance(source.SwitchArms.Length);
for (int i = 0, n = source.SwitchArms.Length; i < n; i++)
{
var oldCase = source.SwitchArms[i];
var oldValue = oldCase.Value;
var newValue =
targetTyped
? CreateConversion(oldValue.Syntax, oldValue, underlyingConversions[i], isCast: false, conversionGroupOpt: null, destination, diagnostics)
: GenerateConversionForAssignment(destination, oldValue, diagnostics);
var newCase = (oldValue == newValue) ? oldCase :
new BoundSwitchExpressionArm(oldCase.Syntax, oldCase.Locals, oldCase.Pattern, oldCase.WhenClause, newValue, oldCase.Label, oldCase.HasErrors);
builder.Add(newCase);
}
conversion.MarkUnderlyingConversionsChecked();
var newSwitchArms = builder.ToImmutableAndFree();
return new BoundConvertedSwitchExpression(
source.Syntax, source.Type, targetTyped, source.Expression, newSwitchArms, source.ReachabilityDecisionDag,
source.DefaultLabel, source.ReportedNotExhaustive, destination, hasErrors || source.HasErrors).WithSuppression(source.IsSuppressed);
}
private BoundExpression CreateUserDefinedConversion(
SyntaxNode syntax,
BoundExpression source,
Conversion conversion,
bool isCast,
ConversionGroup conversionGroup,
TypeSymbol destination,
BindingDiagnosticBag diagnostics,
bool hasErrors)
{
Debug.Assert(conversionGroup != null);
Debug.Assert(conversion.IsUserDefined);
conversion.MarkUnderlyingConversionsChecked();
if (!conversion.IsValid)
{
if (!hasErrors)
GenerateImplicitConversionError(diagnostics, syntax, conversion, source, destination);
return new BoundConversion(
syntax,
BindToNaturalType(source, diagnostics),
conversion,
CheckOverflowAtRuntime,
explicitCastInCode: isCast,
conversionGroup,
constantValueOpt: ConstantValue.NotAvailable,
type: destination,
hasErrors: true)
{ WasCompilerGenerated = source.WasCompilerGenerated };
}
// Due to an oddity in the way we create a non-lifted user-defined conversion from A to D?
// (required backwards compatibility with the native compiler) we can end up in a situation
// where we have:
// a standard conversion from A to B?
// then a standard conversion from B? to B
// then a user-defined conversion from B to C
// then a standard conversion from C to C?
// then a standard conversion from C? to D?
//
// In that scenario, the "from type" of the conversion will be B? and the "from conversion" will be
// from A to B?. Similarly the "to type" of the conversion will be C? and the "to conversion"
// of the conversion will be from C? to D?.
//
// Therefore, we might need to introduce an extra conversion on the source side, from B? to B.
// Now, you might think we should also introduce an extra conversion on the destination side,
// from C to C?. But that then gives us the following bad situation: If we in fact bind this as
//
// (D?)(C?)(C)(B)(B?)(A)x
//
// then what we are in effect doing is saying "convert C? to D? by checking for null, unwrapping,
// converting C to D, and then wrapping". But we know that the C? will never be null. In this case
// we should actually generate
//
// (D?)(C)(B)(B?)(A)x
//
// And thereby skip the unnecessary nullable conversion.
Debug.Assert(conversion.BestUserDefinedConversionAnalysis is object); // All valid user-defined conversions have this populated
// Original expression --> conversion's "from" type
BoundExpression convertedOperand = CreateConversion(
syntax: source.Syntax,
source: source,
conversion: conversion.UserDefinedFromConversion,
isCast: false,
conversionGroupOpt: conversionGroup,
wasCompilerGenerated: false,
destination: conversion.BestUserDefinedConversionAnalysis.FromType,
diagnostics: diagnostics);
TypeSymbol conversionParameterType = conversion.BestUserDefinedConversionAnalysis.Operator.GetParameterType(0);
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
if (conversion.BestUserDefinedConversionAnalysis.Kind == UserDefinedConversionAnalysisKind.ApplicableInNormalForm &&
!TypeSymbol.Equals(conversion.BestUserDefinedConversionAnalysis.FromType, conversionParameterType, TypeCompareKind.ConsiderEverything2))
{
// Conversion's "from" type --> conversion method's parameter type.
convertedOperand = CreateConversion(
syntax: syntax,
source: convertedOperand,
conversion: Conversions.ClassifyStandardConversion(convertedOperand.Type, conversionParameterType, ref useSiteInfo),
isCast: false,
conversionGroupOpt: conversionGroup,
wasCompilerGenerated: true,
destination: conversionParameterType,
diagnostics: diagnostics);
}
BoundExpression userDefinedConversion;
TypeSymbol conversionReturnType = conversion.BestUserDefinedConversionAnalysis.Operator.ReturnType;
TypeSymbol conversionToType = conversion.BestUserDefinedConversionAnalysis.ToType;
Conversion toConversion = conversion.UserDefinedToConversion;
if (conversion.BestUserDefinedConversionAnalysis.Kind == UserDefinedConversionAnalysisKind.ApplicableInNormalForm &&
!TypeSymbol.Equals(conversionToType, conversionReturnType, TypeCompareKind.ConsiderEverything2))
{
// Conversion method's parameter type --> conversion method's return type
// NB: not calling CreateConversion here because this is the recursive base case.
userDefinedConversion = new BoundConversion(
syntax,
convertedOperand,
conversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: isCast,
conversionGroup,
constantValueOpt: ConstantValue.NotAvailable,
type: conversionReturnType)
{ WasCompilerGenerated = true };
if (conversionToType.IsNullableType() && TypeSymbol.Equals(conversionToType.GetNullableUnderlyingType(), conversionReturnType, TypeCompareKind.ConsiderEverything2))
{
// Skip introducing the conversion from C to C?. The "to" conversion is now wrong though,
// because it will still assume converting C? to D?.
toConversion.MarkUnderlyingConversionsCheckedRecursive();
toConversion = Conversions.ClassifyConversionFromType(conversionReturnType, destination, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
Debug.Assert(toConversion.Exists);
}
else
{
// Conversion method's return type --> conversion's "to" type
userDefinedConversion = CreateConversion(
syntax: syntax,
source: userDefinedConversion,
conversion: Conversions.ClassifyStandardConversion(conversionReturnType, conversionToType, ref useSiteInfo),
isCast: false,
conversionGroupOpt: conversionGroup,
wasCompilerGenerated: true,
destination: conversionToType,
diagnostics: diagnostics);
}
}
else
{
// Conversion method's parameter type --> conversion method's "to" type
// NB: not calling CreateConversion here because this is the recursive base case.
userDefinedConversion = new BoundConversion(
syntax,
convertedOperand,
conversion,
@checked: CheckOverflowAtRuntime,
explicitCastInCode: isCast,
conversionGroup,
constantValueOpt: ConstantValue.NotAvailable,
type: conversionToType)
{ WasCompilerGenerated = true };
}
diagnostics.Add(syntax, useSiteInfo);
// Conversion's "to" type --> final type
BoundExpression finalConversion = CreateConversion(
syntax: syntax,
source: userDefinedConversion,
conversion: toConversion,
isCast: false,
conversionGroupOpt: conversionGroup,
wasCompilerGenerated: true, // NOTE: doesn't necessarily set flag on resulting bound expression.
destination: destination,
diagnostics: diagnostics);
conversion.AssertUnderlyingConversionsCheckedRecursive();
finalConversion.ResetCompilerGenerated(source.WasCompilerGenerated);
return finalConversion;
}
private BoundExpression CreateFunctionTypeConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
Debug.Assert(conversion.Kind == ConversionKind.FunctionType);
Debug.Assert(source.Kind is BoundKind.MethodGroup or BoundKind.UnboundLambda);
Debug.Assert(syntax.IsFeatureEnabled(MessageID.IDS_FeatureInferredDelegateType));
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var delegateType = source.GetInferredDelegateType(ref useSiteInfo);
Debug.Assert(delegateType is { });
if (source.Kind == BoundKind.UnboundLambda &&
destination.IsNonGenericExpressionType())
{
delegateType = Compilation.GetWellKnownType(WellKnownType.System_Linq_Expressions_Expression_T).Construct(delegateType);
delegateType.AddUseSiteInfo(ref useSiteInfo);
}
conversion = Conversions.ClassifyConversionFromExpression(source, delegateType, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
bool warnOnMethodGroupConversion =
source.Kind == BoundKind.MethodGroup &&
!isCast &&
conversion.Exists &&
destination.SpecialType == SpecialType.System_Object;
BoundExpression expr;
if (!conversion.Exists)
{
GenerateImplicitConversionError(diagnostics, syntax, conversion, source, delegateType);
expr = new BoundConversion(syntax, source, conversion, @checked: false, explicitCastInCode: isCast, conversionGroup, constantValueOpt: ConstantValue.NotAvailable, type: delegateType, hasErrors: true) { WasCompilerGenerated = source.WasCompilerGenerated };
}
else
{
expr = CreateConversion(syntax, source, conversion, isCast, conversionGroup, delegateType, diagnostics);
}
conversion = Conversions.ClassifyConversionFromExpression(expr, destination, isChecked: CheckOverflowAtRuntime, ref useSiteInfo);
if (!conversion.Exists)
{
GenerateImplicitConversionError(diagnostics, syntax, conversion, source, destination);
}
else if (warnOnMethodGroupConversion)
{
Error(diagnostics, ErrorCode.WRN_MethGrpToNonDel, syntax, ((BoundMethodGroup)source).Name, destination);
}
diagnostics.Add(syntax, useSiteInfo);
return CreateConversion(syntax, expr, conversion, isCast, conversionGroup, destination, diagnostics);
}
private BoundExpression CreateAnonymousFunctionConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
// We have a successful anonymous function conversion; rather than producing a node
// which is a conversion on top of an unbound lambda, replace it with the bound
// lambda.
// UNDONE: Figure out what to do about the error case, where a lambda
// UNDONE: is converted to a delegate that does not match. What to surface then?
var unboundLambda = (UnboundLambda)source;
var boundLambda = unboundLambda.Bind((NamedTypeSymbol)destination, isExpressionTree: destination.IsGenericOrNonGenericExpressionType(out _)).WithInAnonymousFunctionConversion();
diagnostics.AddRange(boundLambda.Diagnostics);
CheckParameterModifierMismatchMethodConversion(syntax, boundLambda.Symbol, destination, invokedAsExtensionMethod: false, diagnostics);
CheckLambdaConversion(boundLambda.Symbol, destination, diagnostics);
return new BoundConversion(
syntax,
boundLambda,
conversion,
@checked: false,
explicitCastInCode: isCast,
conversionGroup,
constantValueOpt: ConstantValue.NotAvailable,
type: destination)
{ WasCompilerGenerated = source.WasCompilerGenerated };
}
private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
var (originalGroup, isAddressOf) = source switch
{
BoundMethodGroup m => (m, false),
BoundUnconvertedAddressOfOperator { Operand: { } m } => (m, true),
_ => throw ExceptionUtilities.UnexpectedValue(source),
};
BoundMethodGroup group = FixMethodGroupWithTypeOrValue(originalGroup, conversion, diagnostics);
bool hasErrors = false;
if (MethodGroupConversionHasErrors(syntax, conversion, group.ReceiverOpt, conversion.IsExtensionMethod, isAddressOf, destination, diagnostics))
{
hasErrors = true;
}
Debug.Assert(conversion.UnderlyingConversions.IsDefault);
conversion.MarkUnderlyingConversionsChecked();
return new BoundConversion(syntax, group, conversion, @checked: false, explicitCastInCode: isCast, conversionGroup, constantValueOpt: ConstantValue.NotAvailable, type: destination, hasErrors: hasErrors) { WasCompilerGenerated = group.WasCompilerGenerated };
}
private static void CheckParameterModifierMismatchMethodConversion(SyntaxNode syntax, MethodSymbol lambdaOrMethod, TypeSymbol targetType, bool invokedAsExtensionMethod, BindingDiagnosticBag diagnostics)
{
MethodSymbol? delegateMethod;
if (targetType.GetDelegateType() is { } delegateType)
{
delegateMethod = delegateType.DelegateInvokeMethod;
}
else if (targetType is FunctionPointerTypeSymbol functionPointerType)
{
delegateMethod = functionPointerType.Signature;
}
else
{
return;
}
if (SourceMemberContainerTypeSymbol.RequiresValidScopedOverrideForRefSafety(delegateMethod))
{
SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
delegateMethod,
lambdaOrMethod,
diagnostics,
static (diagnostics, delegateMethod, lambdaOrMethod, parameter, _, typeAndSyntax) =>
{
diagnostics.Add(
SourceMemberContainerTypeSymbol.ReportInvalidScopedOverrideAsError(delegateMethod, lambdaOrMethod) ?
ErrorCode.ERR_ScopedMismatchInParameterOfTarget :
ErrorCode.WRN_ScopedMismatchInParameterOfTarget,
typeAndSyntax.Syntax.Location,
new FormattedSymbol(parameter, SymbolDisplayFormat.ShortFormat),
typeAndSyntax.Type);
},
(Type: targetType, Syntax: syntax),
allowVariance: true,
invokedAsExtensionMethod: invokedAsExtensionMethod);
}
SourceMemberContainerTypeSymbol.CheckRefReadonlyInMismatch(
delegateMethod, lambdaOrMethod, diagnostics,
static (diagnostics, delegateMethod, lambdaOrMethod, lambdaOrMethodParameter, _, arg) =>
{
var (delegateParameter, location) = arg;
diagnostics.Add(ErrorCode.WRN_TargetDifferentRefness, location, lambdaOrMethodParameter, delegateParameter);
},
syntax.Location,
invokedAsExtensionMethod: invokedAsExtensionMethod);
}
private static void CheckLambdaConversion(LambdaSymbol lambdaSymbol, TypeSymbol targetType, BindingDiagnosticBag diagnostics)
{
var delegateType = targetType.GetDelegateType();
Debug.Assert(delegateType is not null);
var isSynthesized = delegateType.DelegateInvokeMethod?.OriginalDefinition is SynthesizedDelegateInvokeMethod;
var delegateParameters = delegateType.DelegateParameters();
Debug.Assert(lambdaSymbol.ParameterCount == delegateParameters.Length);
for (int p = 0; p < lambdaSymbol.ParameterCount; p++)
{
var lambdaParameter = lambdaSymbol.Parameters[p];
var delegateParameter = delegateParameters[p];
if (isSynthesized)
{
// If synthesizing a delegate with `decimal`/`DateTime` default value,
// check that the corresponding `*ConstantAttribute` is available.
if (delegateParameter.ExplicitDefaultConstantValue is { } defaultValue &&
// Skip reporting this diagnostic if already reported in `SourceComplexParameterSymbolBase.DefaultSyntaxValue`.
lambdaParameter is not SourceComplexParameterSymbolBase
{
ExplicitDefaultConstantValue.IsDecimal: true,
DefaultValueFromAttributes: ConstantValue.NotAvailable
})
{
WellKnownMember? member = defaultValue.SpecialType switch
{
SpecialType.System_Decimal => WellKnownMember.System_Runtime_CompilerServices_DecimalConstantAttribute__ctor,
SpecialType.System_DateTime => WellKnownMember.System_Runtime_CompilerServices_DateTimeConstantAttribute__ctor,
_ => null
};
if (member != null)
{
reportUseSiteDiagnosticForSynthesizedAttribute(
lambdaSymbol,
lambdaParameter,
member.GetValueOrDefault(),
diagnostics);
}
}
// If synthesizing a delegate with an [UnscopedRef] parameter, check the attribute is available.
if (delegateParameter.HasUnscopedRefAttribute)
{
reportUseSiteDiagnosticForSynthesizedAttribute(
lambdaSymbol,
lambdaParameter,
WellKnownMember.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor,
diagnostics);
}
}
// Warn for defaults/`params` mismatch.
if (!lambdaSymbol.SyntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression))
{
if (lambdaParameter.HasExplicitDefaultValue &&
lambdaParameter.ExplicitDefaultConstantValue is { IsBad: false } lambdaParamDefault)
{
var delegateParamDefault = delegateParameter.HasExplicitDefaultValue ? delegateParameter.ExplicitDefaultConstantValue : null;
if (delegateParamDefault?.IsBad != true && lambdaParamDefault != delegateParamDefault)
{
// Parameter {0} has default value '{1}' in lambda but '{2}' in target delegate type.
Error(diagnostics, ErrorCode.WRN_OptionalParamValueMismatch, lambdaParameter.GetFirstLocation(), p + 1, lambdaParamDefault, delegateParamDefault ?? ((object)MessageID.IDS_Missing.Localize()));
}
}
if (lambdaParameter.IsParams && !delegateParameter.IsParams && p == lambdaSymbol.ParameterCount - 1)
{
// Parameter {0} has params modifier in lambda but not in target delegate type.
Error(diagnostics, ErrorCode.WRN_ParamsArrayInLambdaOnly, lambdaParameter.GetFirstLocation(), p + 1);
}
}
}
static void reportUseSiteDiagnosticForSynthesizedAttribute(
LambdaSymbol lambdaSymbol,
ParameterSymbol lambdaParameter,
WellKnownMember member,
BindingDiagnosticBag diagnostics)
{
ReportUseSiteDiagnosticForSynthesizedAttribute(
lambdaSymbol.DeclaringCompilation,
member,
diagnostics,
lambdaParameter.TryGetFirstLocation() ?? lambdaSymbol.SyntaxNode.Location);
}
}
private BoundExpression CreateStackAllocConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
Debug.Assert(conversion.IsStackAlloc);
var boundStackAlloc = (BoundStackAllocArrayCreation)source;
var elementType = boundStackAlloc.ElementType;
TypeSymbol stackAllocType;
switch (conversion.Kind)
{
case ConversionKind.StackAllocToPointerType:
ReportUnsafeIfNotAllowed(syntax.Location, diagnostics);
stackAllocType = new PointerTypeSymbol(TypeWithAnnotations.Create(elementType));
break;
case ConversionKind.StackAllocToSpanType:
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureRefStructs, diagnostics);
stackAllocType = Compilation.GetWellKnownType(WellKnownType.System_Span_T).Construct(elementType);
break;
default:
throw ExceptionUtilities.UnexpectedValue(conversion.Kind);
}
var convertedNode = new BoundConvertedStackAllocExpression(syntax, elementType, boundStackAlloc.Count, boundStackAlloc.InitializerOpt, stackAllocType, boundStackAlloc.HasErrors);
var underlyingConversion = conversion.UnderlyingConversions.Single();
return CreateConversion(syntax, convertedNode, underlyingConversion, isCast: isCast, conversionGroup, destination, diagnostics);
}
private BoundExpression CreateTupleLiteralConversion(SyntaxNode syntax, BoundTupleLiteral sourceTuple, Conversion conversion, bool isCast, ConversionGroup? conversionGroup, TypeSymbol destination, BindingDiagnosticBag diagnostics)
{
// We have a successful tuple conversion; rather than producing a separate conversion node
// which is a conversion on top of a tuple literal, tuple conversion is an element-wise conversion of arguments.
Debug.Assert(conversion.IsNullable == destination.IsNullableType());
var destinationWithoutNullable = destination;
var conversionWithoutNullable = conversion;
if (conversion.IsNullable)
{
destinationWithoutNullable = destination.GetNullableUnderlyingType();
conversionWithoutNullable = conversion.UnderlyingConversions[0];
conversion.MarkUnderlyingConversionsChecked();
}
Debug.Assert(conversionWithoutNullable.IsTupleLiteralConversion);
NamedTypeSymbol targetType = (NamedTypeSymbol)destinationWithoutNullable;
if (targetType.IsTupleType)
{
NamedTypeSymbol.ReportTupleNamesMismatchesIfAny(targetType, sourceTuple, diagnostics);
// do not lose the original element names and locations in the literal if different from names in the target
//
// the tuple has changed the type of elements due to target-typing,
// but element names has not changed and locations of their declarations
// should not be confused with element locations on the target type.
if (sourceTuple.Type is NamedTypeSymbol { IsTupleType: true } sourceType)
{
targetType = targetType.WithTupleDataFrom(sourceType);
}
else
{
var tupleSyntax = (TupleExpressionSyntax)sourceTuple.Syntax;
var locationBuilder = ArrayBuilder<Location?>.GetInstance();
foreach (var argument in tupleSyntax.Arguments)
{
locationBuilder.Add(argument.NameColon?.Name.Location);
}
targetType = targetType.WithElementNames(sourceTuple.ArgumentNamesOpt!,
locationBuilder.ToImmutableAndFree(),
errorPositions: default,
ImmutableArray.Create(tupleSyntax.Location));
}
}
var arguments = sourceTuple.Arguments;
var convertedArguments = ArrayBuilder<BoundExpression>.GetInstance(arguments.Length);
var targetElementTypes = targetType.TupleElementTypesWithAnnotations;
Debug.Assert(targetElementTypes.Length == arguments.Length, "converting a tuple literal to incompatible type?");
var underlyingConversions = conversionWithoutNullable.UnderlyingConversions;
conversionWithoutNullable.MarkUnderlyingConversionsChecked();
for (int i = 0; i < arguments.Length; i++)
{
var argument = arguments[i];
var destType = targetElementTypes[i];
var elementConversion = underlyingConversions[i];
var elementConversionGroup = isCast ? new ConversionGroup(elementConversion, destType) : null;
convertedArguments.Add(CreateConversion(argument.Syntax, argument, elementConversion, isCast: isCast, elementConversionGroup, destType.Type, diagnostics));
}
BoundExpression result = new BoundConvertedTupleLiteral(
sourceTuple.Syntax,
sourceTuple,
wasTargetTyped: true,
convertedArguments.ToImmutableAndFree(),
sourceTuple.ArgumentNamesOpt,
sourceTuple.InferredNamesOpt,
targetType).WithSuppression(sourceTuple.IsSuppressed);
if (!TypeSymbol.Equals(sourceTuple.Type, destination, TypeCompareKind.ConsiderEverything2))
{
// literal cast is applied to the literal
result = new BoundConversion(
sourceTuple.Syntax,
result,
conversion,
@checked: false,
explicitCastInCode: isCast,
conversionGroup,
constantValueOpt: ConstantValue.NotAvailable,
type: destination);
}
// If we had a cast in the code, keep conversion in the tree.
// even though the literal is already converted to the target type.
if (isCast)
{
result = new BoundConversion(
syntax,
result,
Conversion.Identity,
@checked: false,
explicitCastInCode: isCast,
conversionGroup,
constantValueOpt: ConstantValue.NotAvailable,
type: destination);
}
return result;
}
private static bool IsMethodGroupWithTypeOrValueReceiver(BoundNode node)
{
if (node.Kind != BoundKind.MethodGroup)
{
return false;
}
return Binder.IsTypeOrValueExpression(((BoundMethodGroup)node).ReceiverOpt);
}
private BoundMethodGroup FixMethodGroupWithTypeOrValue(BoundMethodGroup group, Conversion conversion, BindingDiagnosticBag diagnostics)
{
if (!IsMethodGroupWithTypeOrValueReceiver(group))
{
return group;
}
BoundExpression? receiverOpt = group.ReceiverOpt;
RoslynDebug.Assert(receiverOpt != null);
receiverOpt = ReplaceTypeOrValueReceiver(receiverOpt, useType: conversion.Method?.RequiresInstanceReceiver == false && !conversion.IsExtensionMethod, diagnostics);
return group.Update(
group.TypeArgumentsOpt,
group.Name,
group.Methods,
group.LookupSymbolOpt,
group.LookupError,
group.Flags,
group.FunctionType,
receiverOpt, //only change
group.ResultKind);
}
/// <summary>
/// This method implements the algorithm in spec section 7.6.5.1.
///
/// For method group conversions, there are situations in which the conversion is
/// considered to exist ("Otherwise the algorithm produces a single best method M having
/// the same number of parameters as D and the conversion is considered to exist"), but
/// application of the conversion fails. These are the "final validation" steps of
/// overload resolution.
/// </summary>
/// <returns>
/// True if there is any error, except lack of runtime support errors.
/// </returns>
private bool MemberGroupFinalValidation(BoundExpression? receiverOpt, MethodSymbol methodSymbol, SyntaxNode node, BindingDiagnosticBag diagnostics, bool invokedAsExtensionMethod)
{
if (!IsBadBaseAccess(node, receiverOpt, methodSymbol, diagnostics))
{
CheckReceiverAndRuntimeSupportForSymbolAccess(node, receiverOpt, methodSymbol, diagnostics);
}
if (MemberGroupFinalValidationAccessibilityChecks(receiverOpt, methodSymbol, node, diagnostics, invokedAsExtensionMethod))
{
return true;
}
// SPEC: If the best method is a generic method, the type arguments (supplied or inferred) are checked against the constraints
// SPEC: declared on the generic method. If any type argument does not satisfy the corresponding constraint(s) on
// SPEC: the type parameter, a binding-time error occurs.
// The portion of the overload resolution spec quoted above is subtle and somewhat
// controversial. The upshot of this is that overload resolution does not consider
// constraints to be a part of the signature. Overload resolution matches arguments to
// parameter lists; it does not consider things which are outside of the parameter list.
// If the best match from the arguments to the formal parameters is not viable then we
// give an error rather than falling back to a worse match.
//
// Consider the following:
//
// void M<T>(T t) where T : Reptile {}
// void M(object x) {}
// ...
// M(new Giraffe());
//
// The correct analysis is to determine that the applicable candidates are
// M<Giraffe>(Giraffe) and M(object). Overload resolution then chooses the former
// because it is an exact match, over the latter which is an inexact match. Only after
// the best method is determined do we check the constraints and discover that the
// constraint on T has been violated.
//
// Note that this is different from the rule that says that during type inference, if an
// inference violates a constraint then inference fails. For example:
//
// class C<T> where T : struct {}
// ...
// void M<U>(U u, C<U> c){}
// void M(object x, object y) {}
// ...
// M("hello", null);
//
// Type inference determines that U is string, but since C<string> is not a valid type
// because of the constraint, type inference fails. M<string> is never added to the
// applicable candidate set, so the applicable candidate set consists solely of
// M(object, object) and is therefore the best match.
return !methodSymbol.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability: false, node.Location, diagnostics));
}
/// <summary>
/// Performs the following checks:
///
/// Spec 7.6.5: Invocation expressions (definition of Final Validation)
/// The method is validated in the context of the method group: If the best method is a static method,
/// the method group must have resulted from a simple-name or a member-access through a type. If the best
/// method is an instance method, the method group must have resulted from a simple-name, a member-access
/// through a variable or value, or a base-access. If neither of these requirements is true, a binding-time
/// error occurs.
/// (Note that the spec omits to mention, in the case of an instance method invoked through a simple name, that
/// the invocation must appear within the body of an instance method)
///
/// Spec 7.5.4: Compile-time checking of dynamic overload resolution
/// If F is a static method, the method group must have resulted from a simple-name, a member-access through a type,
/// or a member-access whose receiver can't be classified as a type or value until after overload resolution (see §7.6.4.1).
/// If F is an instance method, the method group must have resulted from a simple-name, a member-access through a variable or value,
/// or a member-access whose receiver can't be classified as a type or value until after overload resolution (see §7.6.4.1).
/// </summary>
/// <returns>
/// True if there is any error.
/// </returns>
private bool MemberGroupFinalValidationAccessibilityChecks(BoundExpression? receiverOpt, Symbol memberSymbol, SyntaxNode node, BindingDiagnosticBag diagnostics, bool invokedAsExtensionMethod)
{
// Perform final validation of the method to be invoked.
Debug.Assert(memberSymbol is not MethodSymbol { MethodKind: not MethodKind.Constructor } ||
memberSymbol.CanBeReferencedByName);
//note that the same assert does not hold for all properties. Some properties and (all indexers) are not referenceable by name, yet
//their binding brings them through here, perhaps needlessly.
if (receiverOpt != null || memberSymbol is not MethodSymbol { MethodKind: MethodKind.Constructor })
{
if (IsTypeOrValueExpression(receiverOpt))
{
// TypeOrValue expression isn't replaced only if the invocation is late bound, in which case it can't be extension method.
// None of the checks below apply if the receiver can't be classified as a type or value.
Debug.Assert(!invokedAsExtensionMethod);
}
else if (!memberSymbol.RequiresInstanceReceiver())
{
Debug.Assert(!invokedAsExtensionMethod || (receiverOpt != null));
if (invokedAsExtensionMethod)
{
if (IsMemberAccessedThroughType(receiverOpt))
{
if (receiverOpt.Kind == BoundKind.QueryClause)
{
RoslynDebug.Assert(receiverOpt.Type is object);
// Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found.
diagnostics.Add(ErrorCode.ERR_QueryNoProvider, node.Location, receiverOpt.Type, memberSymbol.Name);
}
else
{
// An object reference is required for the non-static field, method, or property '{0}'
diagnostics.Add(ErrorCode.ERR_ObjectRequired, node.Location, memberSymbol);
}
return true;
}
}
else if (!WasImplicitReceiver(receiverOpt) && IsMemberAccessedThroughVariableOrValue(receiverOpt))
{
if (this.Flags.Includes(BinderFlags.CollectionInitializerAddMethod))
{
diagnostics.Add(ErrorCode.ERR_InitializerAddHasWrongSignature, node.Location, memberSymbol);
}
else if (node.Kind() == SyntaxKind.AwaitExpression && memberSymbol.Name == WellKnownMemberNames.GetAwaiter)
{
RoslynDebug.Assert(receiverOpt.Type is object);
diagnostics.Add(ErrorCode.ERR_BadAwaitArg, node.Location, receiverOpt.Type);
}
else
{
diagnostics.Add(ErrorCode.ERR_ObjectProhibited, node.Location, memberSymbol);
}
return true;
}
}
else if (IsMemberAccessedThroughType(receiverOpt))
{
diagnostics.Add(ErrorCode.ERR_ObjectRequired, node.Location, memberSymbol);
return true;
}
else if (WasImplicitReceiver(receiverOpt))
{
if (InFieldInitializer && !ContainingType!.IsScriptClass || InConstructorInitializer || InAttributeArgument)
{
SyntaxNode errorNode = node;
if (node.Parent != null && node.Parent.Kind() == SyntaxKind.InvocationExpression)
{
errorNode = node.Parent;
}
ErrorCode code = InFieldInitializer ? ErrorCode.ERR_FieldInitRefNonstatic : ErrorCode.ERR_ObjectRequired;
diagnostics.Add(code, errorNode.Location, memberSymbol);
return true;
}
// If we could access the member through implicit "this" the receiver would be a BoundThisReference.
// If it is null it means that the instance member is inaccessible.
if (receiverOpt == null || ContainingMember().IsStatic)
{
Error(diagnostics, ErrorCode.ERR_ObjectRequired, node, memberSymbol);
return true;
}
}
}
var containingType = this.ContainingType;
if (containingType is object)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
bool isAccessible = this.IsSymbolAccessibleConditional(memberSymbol.GetTypeOrReturnType().Type, containingType, ref useSiteInfo);
diagnostics.Add(node, useSiteInfo);
if (!isAccessible)
{
// In the presence of non-transitive [InternalsVisibleTo] in source, or obnoxious symbols from metadata, it is possible
// to select a method through overload resolution in which the type is not accessible. In this case a method cannot
// be called through normal IL, so we give an error. Neither [InternalsVisibleTo] nor the need for this diagnostic is
// described by the language specification.
//
// Dev11 perform different access checks. See bug #530360 and tests AccessCheckTests.InaccessibleReturnType.
Error(diagnostics, ErrorCode.ERR_BadAccess, node, memberSymbol);
return true;
}
}
return false;
}
private static bool IsMemberAccessedThroughVariableOrValue(BoundExpression? receiverOpt)
{
if (receiverOpt == null)
{
return false;
}
return !IsMemberAccessedThroughType(receiverOpt);
}
internal static bool IsMemberAccessedThroughType([NotNullWhen(true)] BoundExpression? receiverOpt)
{
if (receiverOpt == null)
{
return false;
}
while (receiverOpt.Kind == BoundKind.QueryClause)
{
receiverOpt = ((BoundQueryClause)receiverOpt).Value;
}
return receiverOpt.Kind == BoundKind.TypeExpression;
}
/// <summary>
/// Was the receiver expression compiler-generated?
/// </summary>
internal static bool WasImplicitReceiver([NotNullWhen(false)] BoundExpression? receiverOpt)
{
if (receiverOpt == null) return true;
if (!receiverOpt.WasCompilerGenerated) return false;
switch (receiverOpt.Kind)
{
case BoundKind.ThisReference:
case BoundKind.HostObjectMemberReference:
case BoundKind.PreviousSubmissionReference:
return true;
default:
return false;
}
}
/// <summary>
/// This method implements the checks in spec section 15.2.
/// </summary>
internal bool MethodIsCompatibleWithDelegateOrFunctionPointer(BoundExpression? receiverOpt, bool isExtensionMethod, MethodSymbol method, TypeSymbol delegateType, Location errorLocation, BindingDiagnosticBag diagnostics)
{
Debug.Assert(delegateType is NamedTypeSymbol { TypeKind: TypeKind.Delegate, DelegateInvokeMethod: { HasUseSiteError: false } }
|| delegateType.TypeKind == TypeKind.FunctionPointer,
"This method should only be called for valid delegate or function pointer types.");
MethodSymbol delegateOrFuncPtrMethod = delegateType switch
{
NamedTypeSymbol { DelegateInvokeMethod: { } invokeMethod } => invokeMethod,
FunctionPointerTypeSymbol { Signature: { } signature } => signature,
_ => throw ExceptionUtilities.UnexpectedValue(delegateType),
};
Debug.Assert(!isExtensionMethod || (receiverOpt != null));
// - Argument types "match", and
var delegateOrFuncPtrParameters = delegateOrFuncPtrMethod.Parameters;
var methodParameters = method.Parameters;
int numParams = delegateOrFuncPtrParameters.Length;
if (methodParameters.Length != numParams + (isExtensionMethod ? 1 : 0))
{
// This can happen if "method" has optional parameters.
Debug.Assert(methodParameters.Length > numParams + (isExtensionMethod ? 1 : 0));
Error(diagnostics, getMethodMismatchErrorCode(delegateType.TypeKind), errorLocation, method, delegateType);
return false;
}
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
// If this is an extension method delegate, the caller should have verified the
// receiver is compatible with the "this" parameter of the extension method.
Debug.Assert(!isExtensionMethod ||
(Conversions.ConvertExtensionMethodThisArg(methodParameters[0].Type, receiverOpt!.Type, ref useSiteInfo, isMethodGroupConversion: true).Exists && useSiteInfo.Diagnostics.IsNullOrEmpty()));
useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(useSiteInfo);
for (int i = 0; i < numParams; i++)
{
var delegateParameter = delegateOrFuncPtrParameters[i];
var methodParameter = methodParameters[isExtensionMethod ? i + 1 : i];
// The delegate compatibility checks are stricter than the checks on applicable functions: it's possible
// to get here with a method that, while all the parameters are applicable, is not actually delegate
// compatible. This is because the Applicable function member spec requires that:
// * Every value parameter (non-ref or similar) from the delegate type has an implicit conversion to the corresponding
// target parameter
// * Every ref or similar parameter has an identity conversion to the corresponding target parameter
// However, the delegate compatibility requirements are stricter:
// * Every value parameter (non-ref or similar) from the delegate type has an implicit _reference_ conversion to the
// corresponding target parameter.
// * Every ref or similar parameter has an identity conversion to the corresponding target parameter
// Note the addition of the reference requirement: it means that for delegate type void D(int i), void M(long l) is
// _applicable_, but not _compatible_.
if (!hasConversion(
delegateType.TypeKind,
Conversions,
source: delegateParameter.Type,
destination: methodParameter.Type,
sourceRefKind: delegateParameter.RefKind,
destinationRefKind: methodParameter.RefKind,
checkingReturns: false,
ref useSiteInfo))
{
// No overload for '{0}' matches delegate '{1}'
Error(diagnostics, getMethodMismatchErrorCode(delegateType.TypeKind), errorLocation, method, delegateType);
diagnostics.Add(errorLocation, useSiteInfo);
return false;
}
}
if (delegateOrFuncPtrMethod.RefKind != method.RefKind)
{
Error(diagnostics, getRefMismatchErrorCode(delegateType.TypeKind), errorLocation, method, delegateType);
diagnostics.Add(errorLocation, useSiteInfo);
return false;
}
var methodReturnType = method.ReturnType;
var delegateReturnType = delegateOrFuncPtrMethod.ReturnType;
bool returnsMatch = delegateOrFuncPtrMethod switch
{
{ RefKind: RefKind.None, ReturnsVoid: true } => method.ReturnsVoid,
{ RefKind: var destinationRefKind } => hasConversion(
delegateType.TypeKind,
Conversions,
source: methodReturnType,
destination: delegateReturnType,
sourceRefKind: method.RefKind,
destinationRefKind: destinationRefKind,
checkingReturns: true,
ref useSiteInfo),
};
if (!returnsMatch)
{
Error(diagnostics, ErrorCode.ERR_BadRetType, errorLocation, method, method.ReturnType);
diagnostics.Add(errorLocation, useSiteInfo);
return false;
}
if (delegateType.IsFunctionPointer())
{
if (isExtensionMethod)
{
Error(diagnostics, ErrorCode.ERR_CannotUseReducedExtensionMethodInAddressOf, errorLocation);
diagnostics.Add(errorLocation, useSiteInfo);
return false;
}
if (!method.IsStatic)
{
// This check is here purely for completeness of implementing the spec. It should
// never be hit, as static methods should be eliminated as candidates in overload
// resolution and should never make it to this point.
Debug.Fail("This method should have been eliminated in overload resolution!");
Error(diagnostics, ErrorCode.ERR_FuncPtrMethMustBeStatic, errorLocation, method);
diagnostics.Add(errorLocation, useSiteInfo);
return false;
}
}
diagnostics.Add(errorLocation, useSiteInfo);
return true;
static bool hasConversion(TypeKind targetKind, Conversions conversions, TypeSymbol source, TypeSymbol destination,
RefKind sourceRefKind, RefKind destinationRefKind, bool checkingReturns, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// Allowed ref kind mismatches between parameters have already been checked by overload resolution.
if (checkingReturns
? sourceRefKind != destinationRefKind
: (sourceRefKind == RefKind.None) != (destinationRefKind == RefKind.None))
{
return false;
}
if (sourceRefKind != RefKind.None)
{
return ConversionsBase.HasIdentityConversion(source, destination);
}
if (conversions.HasIdentityOrImplicitReferenceConversion(source, destination, ref useSiteInfo))
{
return true;
}
return targetKind == TypeKind.FunctionPointer
&& (ConversionsBase.HasImplicitPointerToVoidConversion(source, destination)
|| conversions.HasImplicitPointerConversion(source, destination, ref useSiteInfo));
}
static ErrorCode getMethodMismatchErrorCode(TypeKind type)
=> type switch
{
TypeKind.Delegate => ErrorCode.ERR_MethDelegateMismatch,
TypeKind.FunctionPointer => ErrorCode.ERR_MethFuncPtrMismatch,
_ => throw ExceptionUtilities.UnexpectedValue(type)
};
static ErrorCode getRefMismatchErrorCode(TypeKind type)
=> type switch
{
TypeKind.Delegate => ErrorCode.ERR_DelegateRefMismatch,
TypeKind.FunctionPointer => ErrorCode.ERR_FuncPtrRefMismatch,
_ => throw ExceptionUtilities.UnexpectedValue(type)
};
}
/// <summary>
/// This method combines final validation (section 7.6.5.1) and delegate compatibility (section 15.2).
/// </summary>
/// <param name="syntax">CSharpSyntaxNode of the expression requiring method group conversion.</param>
/// <param name="conversion">Conversion to be performed.</param>
/// <param name="receiverOpt">Optional receiver.</param>
/// <param name="isExtensionMethod">Method invoked as extension method.</param>
/// <param name="delegateOrFuncPtrType">Target delegate type.</param>
/// <param name="diagnostics">Where diagnostics should be added.</param>
/// <returns>True if a diagnostic has been added.</returns>
private bool MethodGroupConversionHasErrors(
SyntaxNode syntax,
Conversion conversion,
BoundExpression? receiverOpt,
bool isExtensionMethod,
bool isAddressOf,
TypeSymbol delegateOrFuncPtrType,
BindingDiagnosticBag diagnostics)
{
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
Debug.Assert(Conversions.IsAssignableFromMulticastDelegate(delegateOrFuncPtrType, ref discardedUseSiteInfo) || delegateOrFuncPtrType.TypeKind == TypeKind.Delegate || delegateOrFuncPtrType.TypeKind == TypeKind.FunctionPointer);
Debug.Assert(conversion.Method is object);
MethodSymbol selectedMethod = conversion.Method;
if (!Conversions.IsAssignableFromMulticastDelegate(delegateOrFuncPtrType, ref discardedUseSiteInfo))
{
if (!MethodIsCompatibleWithDelegateOrFunctionPointer(receiverOpt, isExtensionMethod, selectedMethod, delegateOrFuncPtrType, syntax.Location, diagnostics) ||
MemberGroupFinalValidation(receiverOpt, selectedMethod, syntax, diagnostics, isExtensionMethod))
{
return true;
}
}
if (selectedMethod.IsConditional)
{
// CS1618: Cannot create delegate with '{0}' because it has a Conditional attribute
Error(diagnostics, ErrorCode.ERR_DelegateOnConditional, syntax.Location, selectedMethod);
return true;
}
var sourceMethod = selectedMethod.OriginalDefinition as SourceOrdinaryMethodSymbol;
if (sourceMethod is object && sourceMethod.IsPartialWithoutImplementation)
{
// CS0762: Cannot create delegate from method '{0}' because it is a partial method without an implementing declaration
Error(diagnostics, ErrorCode.ERR_PartialMethodToDelegate, syntax.Location, selectedMethod);
return true;
}
if ((selectedMethod.HasParameterContainingPointerType() || selectedMethod.ReturnType.ContainsPointer())
&& ReportUnsafeIfNotAllowed(syntax, diagnostics))
{
return true;
}
CheckParameterModifierMismatchMethodConversion(syntax, selectedMethod, delegateOrFuncPtrType, isExtensionMethod, diagnostics);
if (!isAddressOf)
{
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, selectedMethod, syntax, isDelegateConversion: true);
}
ReportDiagnosticsIfObsolete(diagnostics, selectedMethod, syntax, hasBaseReceiver: false);
// No use site errors, but there could be use site warnings.
// If there are use site warnings, they were reported during the overload resolution process
// that chose selectedMethod.
Debug.Assert(!selectedMethod.HasUseSiteError, "Shouldn't have reached this point if there were use site errors.");
return false;
}
/// <summary>
/// This method is a wrapper around MethodGroupConversionHasErrors. As a preliminary step,
/// it checks whether a conversion exists.
/// </summary>
private bool MethodGroupConversionDoesNotExistOrHasErrors(
BoundMethodGroup boundMethodGroup,
NamedTypeSymbol delegateType,
Location delegateMismatchLocation,
BindingDiagnosticBag diagnostics,
out Conversion conversion)
{
if (ReportDelegateInvokeUseSiteDiagnostic(diagnostics, delegateType, delegateMismatchLocation))
{
conversion = Conversion.NoConversion;
return true;
}
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
conversion = Conversions.GetMethodGroupDelegateConversion(boundMethodGroup, delegateType, ref useSiteInfo);
diagnostics.Add(delegateMismatchLocation, useSiteInfo);
if (!conversion.Exists)
{
if (!Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(this, boundMethodGroup, delegateType, diagnostics))
{
// No overload for '{0}' matches delegate '{1}'
diagnostics.Add(ErrorCode.ERR_MethDelegateMismatch, delegateMismatchLocation, boundMethodGroup.Name, delegateType);
}
return true;
}
else
{
Debug.Assert(conversion.IsValid); // i.e. if it exists, then it is valid.
// Only cares about nullness and type of receiver, so no need to worry about BoundTypeOrValueExpression.
return this.MethodGroupConversionHasErrors(boundMethodGroup.Syntax, conversion, boundMethodGroup.ReceiverOpt, conversion.IsExtensionMethod, isAddressOf: false, delegateType, diagnostics);
}
}
public ConstantValue? FoldConstantConversion(
SyntaxNode syntax,
BoundExpression source,
Conversion conversion,
TypeSymbol destination,
BindingDiagnosticBag diagnostics)
{
RoslynDebug.Assert(source != null);
RoslynDebug.Assert((object)destination != null);
// The diagnostics bag can be null in cases where we know ahead of time that the
// conversion will succeed without error or warning. (For example, if we have a valid
// implicit numeric conversion on a constant of numeric type.)
// SPEC: A constant expression must be the null literal or a value with one of
// SPEC: the following types: sbyte, byte, short, ushort, int, uint, long,
// SPEC: ulong, char, float, double, decimal, bool, string, or any enumeration type.
// SPEC: The following conversions are permitted in constant expressions:
// SPEC: Identity conversions
// SPEC: Numeric conversions
// SPEC: Enumeration conversions
// SPEC: Constant expression conversions
// SPEC: Implicit and explicit reference conversions, provided that the source of the conversions
// SPEC: is a constant expression that evaluates to the null value.
// SPEC VIOLATION: C# has always allowed the following, even though this does violate the rule that
// SPEC VIOLATION: a constant expression must be either the null literal, or an expression of one
// SPEC VIOLATION: of the given types.
// SPEC VIOLATION: const C c = (C)null;
// TODO: Some conversions can produce errors or warnings depending on checked/unchecked.
// TODO: Fold conversions on enums and strings too.
var sourceConstantValue = source.ConstantValueOpt;
if (sourceConstantValue == null)
{
if (conversion.Kind == ConversionKind.DefaultLiteral)
{
return destination.GetDefaultValue();
}
else
{
return sourceConstantValue;
}
}
else if (sourceConstantValue.IsBad)
{
return sourceConstantValue;
}
if (source.HasAnyErrors)
{
return null;
}
switch (conversion.Kind)
{
case ConversionKind.Identity:
// An identity conversion to a floating-point type (for example from a cast in
// source code) changes the internal representation of the constant value
// to precisely the required precision.
switch (destination.SpecialType)
{
case SpecialType.System_Single:
return ConstantValue.Create(sourceConstantValue.SingleValue);
case SpecialType.System_Double:
return ConstantValue.Create(sourceConstantValue.DoubleValue);
default:
return sourceConstantValue;
}
case ConversionKind.NullLiteral:
return sourceConstantValue;
case ConversionKind.ImplicitConstant:
return FoldConstantNumericConversion(syntax, sourceConstantValue, destination, diagnostics);
case ConversionKind.ExplicitNumeric:
case ConversionKind.ImplicitNumeric:
case ConversionKind.ExplicitEnumeration:
case ConversionKind.ImplicitEnumeration:
// The C# specification categorizes conversion from literal zero to nullable enum as
// an Implicit Enumeration Conversion. Such a thing should not be constant folded
// because nullable enums are never constants.
if (destination.IsNullableType())
{
return null;
}
return FoldConstantNumericConversion(syntax, sourceConstantValue, destination, diagnostics);
case ConversionKind.ExplicitReference:
case ConversionKind.ImplicitReference:
return sourceConstantValue.IsNull ? sourceConstantValue : null;
}
return null;
}
private ConstantValue? FoldConstantNumericConversion(
SyntaxNode syntax,
ConstantValue sourceValue,
TypeSymbol destination,
BindingDiagnosticBag diagnostics)
{
RoslynDebug.Assert(sourceValue != null);
Debug.Assert(!sourceValue.IsBad);
SpecialType destinationType;
if ((object)destination != null && destination.IsEnumType())
{
var underlyingType = ((NamedTypeSymbol)destination).EnumUnderlyingType;
RoslynDebug.Assert((object)underlyingType != null);
Debug.Assert(underlyingType.SpecialType != SpecialType.None);
destinationType = underlyingType.SpecialType;
}
else
{
destinationType = destination.GetSpecialTypeSafe();
}
// In an unchecked context we ignore overflowing conversions on conversions from any
// integral type, float and double to any integral type. "unchecked" actually does not
// affect conversions from decimal to any integral type; if those are out of bounds then
// we always give an error regardless.
if (sourceValue.IsDecimal)
{
if (!CheckConstantBounds(destinationType, sourceValue, out _))
{
// NOTE: Dev10 puts a suffix, "M", on the constant value.
Error(diagnostics, ErrorCode.ERR_ConstOutOfRange, syntax, sourceValue.Value + "M", destination!);
return ConstantValue.Bad;
}
}
else if (destinationType == SpecialType.System_Decimal)
{
if (!CheckConstantBounds(destinationType, sourceValue, out _))
{
Error(diagnostics, ErrorCode.ERR_ConstOutOfRange, syntax, sourceValue.Value!, destination!);
return ConstantValue.Bad;
}
}
else if (CheckOverflowAtCompileTime)
{
if (!CheckConstantBounds(destinationType, sourceValue, out bool maySucceedAtRuntime))
{
if (maySucceedAtRuntime)
{
// Can be calculated at runtime, but is not a compile-time constant.
Error(diagnostics, ErrorCode.WRN_ConstOutOfRangeChecked, syntax, sourceValue.Value!, destination!);
return null;
}
else
{
Error(diagnostics, ErrorCode.ERR_ConstOutOfRangeChecked, syntax, sourceValue.Value!, destination!);
return ConstantValue.Bad;
}
}
}
else if (destinationType == SpecialType.System_IntPtr || destinationType == SpecialType.System_UIntPtr)
{
if (!CheckConstantBounds(destinationType, sourceValue, out _))
{
// Can be calculated at runtime, but is not a compile-time constant.
return null;
}
}
return ConstantValue.Create(DoUncheckedConversion(destinationType, sourceValue), destinationType);
}
private static object DoUncheckedConversion(SpecialType destinationType, ConstantValue value)
{
// Note that we keep "single" floats as doubles internally to maintain higher precision. However,
// we do not do so in an entirely "lossless" manner. When *converting* to a float, we do lose
// the precision lost due to the conversion. But when doing arithmetic, we do the arithmetic on
// the double values.
//
// An example will help. Suppose we have:
//
// const float cf1 = 1.0f;
// const float cf2 = 1.0e-15f;
// const double cd3 = cf1 - cf2;
//
// We first take the double-precision values for 1.0 and 1.0e-15 and round them to floats,
// and then turn them back into doubles. Then when we do the subtraction, we do the subtraction
// in doubles, not in floats. Had we done the subtraction in floats, we'd get 1.0; but instead we
// do it in doubles and get 0.99999999999999.
//
// Similarly, if we have
//
// const int i4 = int.MaxValue; // 2147483647
// const float cf5 = int.MaxValue; // 2147483648.0
// const double cd6 = cf5; // 2147483648.0
//
// The int is converted to float and stored internally as the double 214783648, even though the
// fully precise int would fit into a double.
unchecked
{
switch (value.Discriminator)
{
case ConstantValueTypeDiscriminator.Byte:
byte byteValue = value.ByteValue;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)byteValue;
case SpecialType.System_Char: return (char)byteValue;
case SpecialType.System_UInt16: return (ushort)byteValue;
case SpecialType.System_UInt32: return (uint)byteValue;
case SpecialType.System_UInt64: return (ulong)byteValue;
case SpecialType.System_SByte: return (sbyte)byteValue;
case SpecialType.System_Int16: return (short)byteValue;
case SpecialType.System_Int32: return (int)byteValue;
case SpecialType.System_Int64: return (long)byteValue;
case SpecialType.System_IntPtr: return (int)byteValue;
case SpecialType.System_UIntPtr: return (uint)byteValue;
case SpecialType.System_Single:
case SpecialType.System_Double: return (double)byteValue;
case SpecialType.System_Decimal: return (decimal)byteValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.Char:
char charValue = value.CharValue;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)charValue;
case SpecialType.System_Char: return (char)charValue;
case SpecialType.System_UInt16: return (ushort)charValue;
case SpecialType.System_UInt32: return (uint)charValue;
case SpecialType.System_UInt64: return (ulong)charValue;
case SpecialType.System_SByte: return (sbyte)charValue;
case SpecialType.System_Int16: return (short)charValue;
case SpecialType.System_Int32: return (int)charValue;
case SpecialType.System_Int64: return (long)charValue;
case SpecialType.System_IntPtr: return (int)charValue;
case SpecialType.System_UIntPtr: return (uint)charValue;
case SpecialType.System_Single:
case SpecialType.System_Double: return (double)charValue;
case SpecialType.System_Decimal: return (decimal)charValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.UInt16:
ushort uint16Value = value.UInt16Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)uint16Value;
case SpecialType.System_Char: return (char)uint16Value;
case SpecialType.System_UInt16: return (ushort)uint16Value;
case SpecialType.System_UInt32: return (uint)uint16Value;
case SpecialType.System_UInt64: return (ulong)uint16Value;
case SpecialType.System_SByte: return (sbyte)uint16Value;
case SpecialType.System_Int16: return (short)uint16Value;
case SpecialType.System_Int32: return (int)uint16Value;
case SpecialType.System_Int64: return (long)uint16Value;
case SpecialType.System_IntPtr: return (int)uint16Value;
case SpecialType.System_UIntPtr: return (uint)uint16Value;
case SpecialType.System_Single:
case SpecialType.System_Double: return (double)uint16Value;
case SpecialType.System_Decimal: return (decimal)uint16Value;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.UInt32:
uint uint32Value = value.UInt32Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)uint32Value;
case SpecialType.System_Char: return (char)uint32Value;
case SpecialType.System_UInt16: return (ushort)uint32Value;
case SpecialType.System_UInt32: return (uint)uint32Value;
case SpecialType.System_UInt64: return (ulong)uint32Value;
case SpecialType.System_SByte: return (sbyte)uint32Value;
case SpecialType.System_Int16: return (short)uint32Value;
case SpecialType.System_Int32: return (int)uint32Value;
case SpecialType.System_Int64: return (long)uint32Value;
case SpecialType.System_IntPtr: return (int)uint32Value;
case SpecialType.System_UIntPtr: return (uint)uint32Value;
case SpecialType.System_Single: return (double)(float)uint32Value;
case SpecialType.System_Double: return (double)uint32Value;
case SpecialType.System_Decimal: return (decimal)uint32Value;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.UInt64:
ulong uint64Value = value.UInt64Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)uint64Value;
case SpecialType.System_Char: return (char)uint64Value;
case SpecialType.System_UInt16: return (ushort)uint64Value;
case SpecialType.System_UInt32: return (uint)uint64Value;
case SpecialType.System_UInt64: return (ulong)uint64Value;
case SpecialType.System_SByte: return (sbyte)uint64Value;
case SpecialType.System_Int16: return (short)uint64Value;
case SpecialType.System_Int32: return (int)uint64Value;
case SpecialType.System_Int64: return (long)uint64Value;
case SpecialType.System_IntPtr: return (int)uint64Value;
case SpecialType.System_UIntPtr: return (uint)uint64Value;
case SpecialType.System_Single: return (double)(float)uint64Value;
case SpecialType.System_Double: return (double)uint64Value;
case SpecialType.System_Decimal: return (decimal)uint64Value;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.NUInt:
uint nuintValue = value.UInt32Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)nuintValue;
case SpecialType.System_Char: return (char)nuintValue;
case SpecialType.System_UInt16: return (ushort)nuintValue;
case SpecialType.System_UInt32: return (uint)nuintValue;
case SpecialType.System_UInt64: return (ulong)nuintValue;
case SpecialType.System_SByte: return (sbyte)nuintValue;
case SpecialType.System_Int16: return (short)nuintValue;
case SpecialType.System_Int32: return (int)nuintValue;
case SpecialType.System_Int64: return (long)nuintValue;
case SpecialType.System_IntPtr: return (int)nuintValue;
case SpecialType.System_Single: return (double)(float)nuintValue;
case SpecialType.System_Double: return (double)nuintValue;
case SpecialType.System_Decimal: return (decimal)nuintValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.SByte:
sbyte sbyteValue = value.SByteValue;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)sbyteValue;
case SpecialType.System_Char: return (char)sbyteValue;
case SpecialType.System_UInt16: return (ushort)sbyteValue;
case SpecialType.System_UInt32: return (uint)sbyteValue;
case SpecialType.System_UInt64: return (ulong)sbyteValue;
case SpecialType.System_SByte: return (sbyte)sbyteValue;
case SpecialType.System_Int16: return (short)sbyteValue;
case SpecialType.System_Int32: return (int)sbyteValue;
case SpecialType.System_Int64: return (long)sbyteValue;
case SpecialType.System_IntPtr: return (int)sbyteValue;
case SpecialType.System_UIntPtr: return (uint)sbyteValue;
case SpecialType.System_Single:
case SpecialType.System_Double: return (double)sbyteValue;
case SpecialType.System_Decimal: return (decimal)sbyteValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.Int16:
short int16Value = value.Int16Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)int16Value;
case SpecialType.System_Char: return (char)int16Value;
case SpecialType.System_UInt16: return (ushort)int16Value;
case SpecialType.System_UInt32: return (uint)int16Value;
case SpecialType.System_UInt64: return (ulong)int16Value;
case SpecialType.System_SByte: return (sbyte)int16Value;
case SpecialType.System_Int16: return (short)int16Value;
case SpecialType.System_Int32: return (int)int16Value;
case SpecialType.System_Int64: return (long)int16Value;
case SpecialType.System_IntPtr: return (int)int16Value;
case SpecialType.System_UIntPtr: return (uint)int16Value;
case SpecialType.System_Single:
case SpecialType.System_Double: return (double)int16Value;
case SpecialType.System_Decimal: return (decimal)int16Value;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.Int32:
int int32Value = value.Int32Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)int32Value;
case SpecialType.System_Char: return (char)int32Value;
case SpecialType.System_UInt16: return (ushort)int32Value;
case SpecialType.System_UInt32: return (uint)int32Value;
case SpecialType.System_UInt64: return (ulong)int32Value;
case SpecialType.System_SByte: return (sbyte)int32Value;
case SpecialType.System_Int16: return (short)int32Value;
case SpecialType.System_Int32: return (int)int32Value;
case SpecialType.System_Int64: return (long)int32Value;
case SpecialType.System_IntPtr: return (int)int32Value;
case SpecialType.System_UIntPtr: return (uint)int32Value;
case SpecialType.System_Single: return (double)(float)int32Value;
case SpecialType.System_Double: return (double)int32Value;
case SpecialType.System_Decimal: return (decimal)int32Value;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.Int64:
long int64Value = value.Int64Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)int64Value;
case SpecialType.System_Char: return (char)int64Value;
case SpecialType.System_UInt16: return (ushort)int64Value;
case SpecialType.System_UInt32: return (uint)int64Value;
case SpecialType.System_UInt64: return (ulong)int64Value;
case SpecialType.System_SByte: return (sbyte)int64Value;
case SpecialType.System_Int16: return (short)int64Value;
case SpecialType.System_Int32: return (int)int64Value;
case SpecialType.System_Int64: return (long)int64Value;
case SpecialType.System_IntPtr: return (int)int64Value;
case SpecialType.System_UIntPtr: return (uint)int64Value;
case SpecialType.System_Single: return (double)(float)int64Value;
case SpecialType.System_Double: return (double)int64Value;
case SpecialType.System_Decimal: return (decimal)int64Value;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.NInt:
int nintValue = value.Int32Value;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)nintValue;
case SpecialType.System_Char: return (char)nintValue;
case SpecialType.System_UInt16: return (ushort)nintValue;
case SpecialType.System_UInt32: return (uint)nintValue;
case SpecialType.System_UInt64: return (ulong)nintValue;
case SpecialType.System_SByte: return (sbyte)nintValue;
case SpecialType.System_Int16: return (short)nintValue;
case SpecialType.System_Int32: return (int)nintValue;
case SpecialType.System_Int64: return (long)nintValue;
case SpecialType.System_IntPtr: return (int)nintValue;
case SpecialType.System_UIntPtr: return (uint)nintValue;
case SpecialType.System_Single: return (double)(float)nintValue;
case SpecialType.System_Double: return (double)nintValue;
case SpecialType.System_Decimal: return (decimal)nintValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.Single:
case ConstantValueTypeDiscriminator.Double:
// When converting from a floating-point type to an integral type, if the checked conversion would
// throw an overflow exception, then the unchecked conversion is undefined. So that we have
// identical behavior on every host platform, we yield a result of zero in that case.
double doubleValue = CheckConstantBounds(destinationType, value.DoubleValue, out _) ? value.DoubleValue : 0D;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)doubleValue;
case SpecialType.System_Char: return (char)doubleValue;
case SpecialType.System_UInt16: return (ushort)doubleValue;
case SpecialType.System_UInt32: return (uint)doubleValue;
case SpecialType.System_UInt64: return (ulong)doubleValue;
case SpecialType.System_SByte: return (sbyte)doubleValue;
case SpecialType.System_Int16: return (short)doubleValue;
case SpecialType.System_Int32: return (int)doubleValue;
case SpecialType.System_Int64: return (long)doubleValue;
case SpecialType.System_IntPtr: return (int)doubleValue;
case SpecialType.System_UIntPtr: return (uint)doubleValue;
case SpecialType.System_Single: return (double)(float)doubleValue;
case SpecialType.System_Double: return (double)doubleValue;
case SpecialType.System_Decimal: return (value.Discriminator == ConstantValueTypeDiscriminator.Single) ? (decimal)(float)doubleValue : (decimal)doubleValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
case ConstantValueTypeDiscriminator.Decimal:
decimal decimalValue = CheckConstantBounds(destinationType, value.DecimalValue, out _) ? value.DecimalValue : 0m;
switch (destinationType)
{
case SpecialType.System_Byte: return (byte)decimalValue;
case SpecialType.System_Char: return (char)decimalValue;
case SpecialType.System_UInt16: return (ushort)decimalValue;
case SpecialType.System_UInt32: return (uint)decimalValue;
case SpecialType.System_UInt64: return (ulong)decimalValue;
case SpecialType.System_SByte: return (sbyte)decimalValue;
case SpecialType.System_Int16: return (short)decimalValue;
case SpecialType.System_Int32: return (int)decimalValue;
case SpecialType.System_Int64: return (long)decimalValue;
case SpecialType.System_IntPtr: return (int)decimalValue;
case SpecialType.System_UIntPtr: return (uint)decimalValue;
case SpecialType.System_Single: return (double)(float)decimalValue;
case SpecialType.System_Double: return (double)decimalValue;
case SpecialType.System_Decimal: return (decimal)decimalValue;
default: throw ExceptionUtilities.UnexpectedValue(destinationType);
}
default:
throw ExceptionUtilities.UnexpectedValue(value.Discriminator);
}
}
// all cases should have been handled in the switch above.
// return value.Value;
}
public static bool CheckConstantBounds(SpecialType destinationType, ConstantValue value, out bool maySucceedAtRuntime)
{
if (value.IsBad)
{
//assume that the constant was intended to be in bounds
maySucceedAtRuntime = false;
return true;
}
// Compute whether the value fits into the bounds of the given destination type without
// error. We know that the constant will fit into either a double or a decimal, so
// convert it to one of those and then check the bounds on that.
var canonicalValue = CanonicalizeConstant(value);
return canonicalValue is decimal ?
CheckConstantBounds(destinationType, (decimal)canonicalValue, out maySucceedAtRuntime) :
CheckConstantBounds(destinationType, (double)canonicalValue, out maySucceedAtRuntime);
}
private static bool CheckConstantBounds(SpecialType destinationType, double value, out bool maySucceedAtRuntime)
{
maySucceedAtRuntime = false;
// Dev10 checks (minValue - 1) < value < (maxValue + 1).
// See ExpressionBinder::isConstantInRange.
switch (destinationType)
{
case SpecialType.System_Byte: return (byte.MinValue - 1D) < value && value < (byte.MaxValue + 1D);
case SpecialType.System_Char: return (char.MinValue - 1D) < value && value < (char.MaxValue + 1D);
case SpecialType.System_UInt16: return (ushort.MinValue - 1D) < value && value < (ushort.MaxValue + 1D);
case SpecialType.System_UInt32: return (uint.MinValue - 1D) < value && value < (uint.MaxValue + 1D);
case SpecialType.System_UInt64: return (ulong.MinValue - 1D) < value && value < (ulong.MaxValue + 1D);
case SpecialType.System_SByte: return (sbyte.MinValue - 1D) < value && value < (sbyte.MaxValue + 1D);
case SpecialType.System_Int16: return (short.MinValue - 1D) < value && value < (short.MaxValue + 1D);
case SpecialType.System_Int32: return (int.MinValue - 1D) < value && value < (int.MaxValue + 1D);
// Note: Using <= to compare the min value matches the native compiler.
case SpecialType.System_Int64: return (long.MinValue - 1D) <= value && value < (long.MaxValue + 1D);
case SpecialType.System_Decimal: return ((double)decimal.MinValue - 1D) < value && value < ((double)decimal.MaxValue + 1D);
case SpecialType.System_IntPtr:
maySucceedAtRuntime = (long.MinValue - 1D) < value && value < (long.MaxValue + 1D);
return (int.MinValue - 1D) < value && value < (int.MaxValue + 1D);
case SpecialType.System_UIntPtr:
maySucceedAtRuntime = (ulong.MinValue - 1D) < value && value < (ulong.MaxValue + 1D);
return (uint.MinValue - 1D) < value && value < (uint.MaxValue + 1D);
}
return true;
}
private static bool CheckConstantBounds(SpecialType destinationType, decimal value, out bool maySucceedAtRuntime)
{
maySucceedAtRuntime = false;
// Dev10 checks (minValue - 1) < value < (maxValue + 1).
// See ExpressionBinder::isConstantInRange.
switch (destinationType)
{
case SpecialType.System_Byte: return (byte.MinValue - 1M) < value && value < (byte.MaxValue + 1M);
case SpecialType.System_Char: return (char.MinValue - 1M) < value && value < (char.MaxValue + 1M);
case SpecialType.System_UInt16: return (ushort.MinValue - 1M) < value && value < (ushort.MaxValue + 1M);
case SpecialType.System_UInt32: return (uint.MinValue - 1M) < value && value < (uint.MaxValue + 1M);
case SpecialType.System_UInt64: return (ulong.MinValue - 1M) < value && value < (ulong.MaxValue + 1M);
case SpecialType.System_SByte: return (sbyte.MinValue - 1M) < value && value < (sbyte.MaxValue + 1M);
case SpecialType.System_Int16: return (short.MinValue - 1M) < value && value < (short.MaxValue + 1M);
case SpecialType.System_Int32: return (int.MinValue - 1M) < value && value < (int.MaxValue + 1M);
case SpecialType.System_Int64: return (long.MinValue - 1M) < value && value < (long.MaxValue + 1M);
case SpecialType.System_IntPtr:
maySucceedAtRuntime = (long.MinValue - 1M) < value && value < (long.MaxValue + 1M);
return (int.MinValue - 1M) < value && value < (int.MaxValue + 1M);
case SpecialType.System_UIntPtr:
maySucceedAtRuntime = (ulong.MinValue - 1M) < value && value < (ulong.MaxValue + 1M);
return (uint.MinValue - 1M) < value && value < (uint.MaxValue + 1M);
}
return true;
}
// Takes in a constant of any kind and returns the constant as either a double or decimal
private static object CanonicalizeConstant(ConstantValue value)
{
switch (value.Discriminator)
{
case ConstantValueTypeDiscriminator.SByte: return (decimal)value.SByteValue;
case ConstantValueTypeDiscriminator.Int16: return (decimal)value.Int16Value;
case ConstantValueTypeDiscriminator.Int32: return (decimal)value.Int32Value;
case ConstantValueTypeDiscriminator.Int64: return (decimal)value.Int64Value;
case ConstantValueTypeDiscriminator.NInt: return (decimal)value.Int32Value;
case ConstantValueTypeDiscriminator.Byte: return (decimal)value.ByteValue;
case ConstantValueTypeDiscriminator.Char: return (decimal)value.CharValue;
case ConstantValueTypeDiscriminator.UInt16: return (decimal)value.UInt16Value;
case ConstantValueTypeDiscriminator.UInt32: return (decimal)value.UInt32Value;
case ConstantValueTypeDiscriminator.UInt64: return (decimal)value.UInt64Value;
case ConstantValueTypeDiscriminator.NUInt: return (decimal)value.UInt32Value;
case ConstantValueTypeDiscriminator.Single:
case ConstantValueTypeDiscriminator.Double: return value.DoubleValue;
case ConstantValueTypeDiscriminator.Decimal: return value.DecimalValue;
default: throw ExceptionUtilities.UnexpectedValue(value.Discriminator);
}
// all cases handled in the switch, above.
}
}
}
|