|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal static class ParameterHelpers
{
public static ImmutableArray<SourceParameterSymbol> MakeParameters(
Binder withTypeParametersBinder,
Symbol owner,
BaseParameterListSyntax syntax,
out SyntaxToken arglistToken,
BindingDiagnosticBag diagnostics,
bool allowRefOrOut,
bool allowThis,
bool addRefReadOnlyModifier)
{
return MakeParameters<ParameterSyntax, SourceParameterSymbol, Symbol>(
withTypeParametersBinder,
owner,
syntax.Parameters,
out arglistToken,
diagnostics,
allowRefOrOut,
allowThis,
addRefReadOnlyModifier,
suppressUseSiteDiagnostics: false,
lastIndex: syntax.Parameters.Count - 1,
parameterCreationFunc: (Binder context, Symbol owner, TypeWithAnnotations parameterType,
ParameterSyntax syntax, RefKind refKind, int ordinal,
SyntaxToken paramsKeyword, SyntaxToken thisKeyword, bool addRefReadOnlyModifier,
ScopedKind scope,
BindingDiagnosticBag declarationDiagnostics) =>
{
return SourceParameterSymbol.Create(
context,
owner,
parameterType,
syntax,
refKind,
syntax.Identifier,
ordinal,
hasParamsModifier: paramsKeyword.Kind() != SyntaxKind.None,
isExtensionMethodThis: ordinal == 0 && thisKeyword.Kind() != SyntaxKind.None,
addRefReadOnlyModifier,
scope,
declarationDiagnostics);
});
}
public static ImmutableArray<FunctionPointerParameterSymbol> MakeFunctionPointerParameters(
Binder binder,
FunctionPointerMethodSymbol owner,
SeparatedSyntaxList<FunctionPointerParameterSyntax> parametersList,
BindingDiagnosticBag diagnostics,
bool suppressUseSiteDiagnostics)
{
return MakeParameters<FunctionPointerParameterSyntax, FunctionPointerParameterSymbol, FunctionPointerMethodSymbol>(
binder,
owner,
parametersList,
out _,
diagnostics,
allowRefOrOut: true,
allowThis: false,
addRefReadOnlyModifier: true,
suppressUseSiteDiagnostics,
parametersList.Count - 2,
parameterCreationFunc: (Binder binder, FunctionPointerMethodSymbol owner, TypeWithAnnotations parameterType,
FunctionPointerParameterSyntax syntax, RefKind refKind, int ordinal,
SyntaxToken paramsKeyword, SyntaxToken thisKeyword, bool addRefReadOnlyModifier,
ScopedKind scope,
BindingDiagnosticBag diagnostics) =>
{
// Non-function pointer locations have other locations to encode in/ref readonly/outness. For function pointers,
// these modreqs are the only locations where this can be encoded. If that changes, we should update this.
Debug.Assert(addRefReadOnlyModifier, "If addReadonlyRef isn't true, we must have found a different location to encode the readonlyness of a function pointer");
ImmutableArray<CustomModifier> customModifiers = refKind switch
{
RefKind.In => CreateInModifiers(binder, diagnostics, syntax),
RefKind.RefReadOnlyParameter => CreateRefReadonlyParameterModifiers(binder, diagnostics, syntax),
RefKind.Out => CreateOutModifiers(binder, diagnostics, syntax),
_ => ImmutableArray<CustomModifier>.Empty
};
if (parameterType.IsVoidType())
{
diagnostics.Add(ErrorCode.ERR_NoVoidParameter, syntax.Type.Location);
}
return new FunctionPointerParameterSymbol(
parameterType,
refKind,
ordinal,
owner,
customModifiers);
},
parsingFunctionPointer: true);
}
private static ImmutableArray<TParameterSymbol> MakeParameters<TParameterSyntax, TParameterSymbol, TOwningSymbol>(
Binder withTypeParametersBinder,
TOwningSymbol owner,
SeparatedSyntaxList<TParameterSyntax> parametersList,
out SyntaxToken arglistToken,
BindingDiagnosticBag diagnostics,
bool allowRefOrOut,
bool allowThis,
bool addRefReadOnlyModifier,
bool suppressUseSiteDiagnostics,
int lastIndex,
Func<Binder, TOwningSymbol, TypeWithAnnotations, TParameterSyntax, RefKind, int, SyntaxToken, SyntaxToken, bool, ScopedKind, BindingDiagnosticBag, TParameterSymbol> parameterCreationFunc,
bool parsingFunctionPointer = false)
where TParameterSyntax : BaseParameterSyntax
where TParameterSymbol : ParameterSymbol
where TOwningSymbol : Symbol
{
Debug.Assert(!parsingFunctionPointer || owner is FunctionPointerMethodSymbol);
arglistToken = default(SyntaxToken);
int parameterIndex = 0;
int firstDefault = -1;
var builder = ArrayBuilder<TParameterSymbol>.GetInstance();
foreach (var parameterSyntax in parametersList)
{
if (parameterIndex > lastIndex) break;
CheckParameterModifiers(parameterSyntax, diagnostics, parsingFunctionPointer, parsingLambdaParams: false, parsingAnonymousMethodParams: false);
var refKind = GetModifiers(parameterSyntax.Modifiers, out SyntaxToken refnessKeyword, out SyntaxToken paramsKeyword, out SyntaxToken thisKeyword, out ScopedKind scope);
if (thisKeyword.Kind() != SyntaxKind.None && !allowThis)
{
diagnostics.Add(ErrorCode.ERR_ThisInBadContext, thisKeyword.GetLocation());
}
if (parameterSyntax is ParameterSyntax concreteParam)
{
if (concreteParam.IsArgList)
{
arglistToken = concreteParam.Identifier;
// The native compiler produces "Expected type" here, in the parser. Roslyn produces
// the somewhat more informative "arglist not valid" error.
if (paramsKeyword.Kind() != SyntaxKind.None
|| refnessKeyword.Kind() != SyntaxKind.None
|| thisKeyword.Kind() != SyntaxKind.None)
{
// CS1669: __arglist is not valid in this context
diagnostics.Add(ErrorCode.ERR_IllegalVarArgs, arglistToken.GetLocation());
}
if (parameterIndex != lastIndex)
{
// CS0257: An __arglist parameter must be the last parameter in a parameter list
diagnostics.Add(ErrorCode.ERR_VarargsLast, concreteParam.GetLocation());
}
continue;
}
if (concreteParam.Default != null && firstDefault == -1)
{
firstDefault = parameterIndex;
}
}
Debug.Assert(parameterSyntax.Type != null);
var parameterType = withTypeParametersBinder.BindType(parameterSyntax.Type, diagnostics, suppressUseSiteDiagnostics: suppressUseSiteDiagnostics);
if (!allowRefOrOut && (refKind == RefKind.Ref || refKind == RefKind.Out))
{
Debug.Assert(refnessKeyword.Kind() != SyntaxKind.None);
// error CS0631: ref and out are not valid in this context
diagnostics.Add(ErrorCode.ERR_IllegalRefParam, refnessKeyword.GetLocation());
}
TParameterSymbol parameter = parameterCreationFunc(withTypeParametersBinder, owner, parameterType, parameterSyntax, refKind, parameterIndex, paramsKeyword, thisKeyword, addRefReadOnlyModifier, scope, diagnostics);
Debug.Assert(parameter is SourceComplexParameterSymbolBase || !parameter.IsParams); // Only SourceComplexParameterSymbolBase validates 'params' type.
Debug.Assert(parameter is SourceComplexParameterSymbolBase || parameter is not SourceParameterSymbol s || s.DeclaredScope == ScopedKind.None); // Only SourceComplexParameterSymbolBase validates 'scope'.
ReportParameterErrors(owner, parameterSyntax, parameter.Ordinal, lastParameterIndex: lastIndex, parameter.IsParams, parameter.TypeWithAnnotations,
parameter.RefKind, parameter.ContainingSymbol, thisKeyword, paramsKeyword, firstDefault, diagnostics);
builder.Add(parameter);
++parameterIndex;
}
ImmutableArray<TParameterSymbol> parameters = builder.ToImmutableAndFree();
if (!parsingFunctionPointer)
{
var methodOwner = owner as MethodSymbol;
var typeParameters = (object)methodOwner != null ?
methodOwner.TypeParameters :
default(ImmutableArray<TypeParameterSymbol>);
Debug.Assert(methodOwner?.MethodKind != MethodKind.LambdaMethod);
bool allowShadowingNames = withTypeParametersBinder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureNameShadowingInNestedFunctions) &&
methodOwner?.MethodKind == MethodKind.LocalFunction;
withTypeParametersBinder.ValidateParameterNameConflicts(typeParameters, parameters.Cast<TParameterSymbol, ParameterSymbol>(), allowShadowingNames, diagnostics);
}
return parameters;
}
#nullable enable
internal static void EnsureRefKindAttributesExist(PEModuleBuilder moduleBuilder, ImmutableArray<ParameterSymbol> parameters)
{
EnsureRefKindAttributesExist(moduleBuilder.Compilation, parameters, diagnostics: null, modifyCompilation: false, moduleBuilder);
}
internal static void EnsureRefKindAttributesExist(CSharpCompilation? compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics, bool modifyCompilation)
{
// These parameters might not come from a compilation (example: lambdas evaluated in EE).
// During rewriting, lowering will take care of flagging the appropriate PEModuleBuilder instead.
if (compilation == null)
{
return;
}
EnsureRefKindAttributesExist(compilation, parameters, diagnostics, modifyCompilation, moduleBuilder: null);
}
private static void EnsureRefKindAttributesExist(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag? diagnostics, bool modifyCompilation, PEModuleBuilder? moduleBuilder)
{
foreach (var parameter in parameters)
{
if (parameter.RefKind == RefKind.In)
{
if (moduleBuilder is { })
{
moduleBuilder.EnsureIsReadOnlyAttributeExists();
}
else
{
compilation.EnsureIsReadOnlyAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation);
}
}
else if (parameter.RefKind == RefKind.RefReadOnlyParameter)
{
if (moduleBuilder is { })
{
moduleBuilder.EnsureRequiresLocationAttributeExists();
}
else
{
compilation.EnsureRequiresLocationAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation);
}
}
}
}
internal static void EnsureParamCollectionAttributeExistsAndModifyCompilation(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics)
{
if (parameters.LastOrDefault(static (p) => p.IsParamsCollection) is { } parameter)
{
compilation.EnsureParamCollectionAttributeExistsAndModifyCompilation(diagnostics, GetParameterLocation(parameter));
}
}
internal static void EnsureNativeIntegerAttributeExists(PEModuleBuilder moduleBuilder, ImmutableArray<ParameterSymbol> parameters)
{
Debug.Assert(moduleBuilder.Compilation.ShouldEmitNativeIntegerAttributes());
EnsureNativeIntegerAttributeExists(moduleBuilder.Compilation, parameters, diagnostics: null, modifyCompilation: false, moduleBuilder);
}
internal static void EnsureNativeIntegerAttributeExists(CSharpCompilation? compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics, bool modifyCompilation)
{
// These parameters might not come from a compilation (example: lambdas evaluated in EE).
// During rewriting, lowering will take care of flagging the appropriate PEModuleBuilder instead.
if (compilation == null)
{
return;
}
if (!compilation.ShouldEmitNativeIntegerAttributes())
{
return;
}
EnsureNativeIntegerAttributeExists(compilation, parameters, diagnostics, modifyCompilation, moduleBuilder: null);
}
private static void EnsureNativeIntegerAttributeExists(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag? diagnostics, bool modifyCompilation, PEModuleBuilder? moduleBuilder)
{
Debug.Assert(compilation.ShouldEmitNativeIntegerAttributes());
foreach (var parameter in parameters)
{
if (parameter.TypeWithAnnotations.ContainsNativeIntegerWrapperType())
{
if (moduleBuilder is { })
{
moduleBuilder.EnsureNativeIntegerAttributeExists();
}
else
{
compilation.EnsureNativeIntegerAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation);
}
}
}
}
internal static bool RequiresScopedRefAttribute(ParameterSymbol parameter)
{
Debug.Assert(!parameter.IsThis);
var scope = parameter.EffectiveScope;
if (scope == ScopedKind.None)
{
return false;
}
if (IsRefScopedByDefault(parameter))
{
return scope == ScopedKind.ScopedValue;
}
return true;
}
internal static bool IsRefScopedByDefault(ParameterSymbol parameter)
{
return IsRefScopedByDefault(parameter.UseUpdatedEscapeRules, parameter.RefKind);
}
internal static bool IsRefScopedByDefault(bool useUpdatedEscapeRules, RefKind refKind)
{
return useUpdatedEscapeRules && refKind == RefKind.Out;
}
internal static void EnsureScopedRefAttributeExists(PEModuleBuilder moduleBuilder, ImmutableArray<ParameterSymbol> parameters)
{
EnsureScopedRefAttributeExists(moduleBuilder.Compilation, parameters, diagnostics: null, modifyCompilation: false, moduleBuilder);
}
internal static void EnsureScopedRefAttributeExists(CSharpCompilation? compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics, bool modifyCompilation)
{
// These parameters might not come from a compilation (example: lambdas evaluated in EE).
// During rewriting, lowering will take care of flagging the appropriate PEModuleBuilder instead.
if (compilation == null)
{
return;
}
EnsureScopedRefAttributeExists(compilation, parameters, diagnostics, modifyCompilation, moduleBuilder: null);
}
private static void EnsureScopedRefAttributeExists(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag? diagnostics, bool modifyCompilation, PEModuleBuilder? moduleBuilder)
{
foreach (var parameter in parameters)
{
if (RequiresScopedRefAttribute(parameter))
{
if (moduleBuilder is { })
{
moduleBuilder.EnsureScopedRefAttributeExists();
}
else
{
compilation.EnsureScopedRefAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation);
}
}
}
}
internal static void EnsureNullableAttributeExists(PEModuleBuilder moduleBuilder, Symbol container, ImmutableArray<ParameterSymbol> parameters)
{
EnsureNullableAttributeExists(moduleBuilder.Compilation, container, parameters, diagnostics: null, modifyCompilation: false, moduleBuilder);
}
internal static void EnsureNullableAttributeExists(CSharpCompilation? compilation, Symbol container, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag? diagnostics, bool modifyCompilation)
{
// These parameters might not come from a compilation (example: lambdas evaluated in EE).
// During rewriting, lowering will take care of flagging the appropriate PEModuleBuilder instead.
if (compilation == null)
{
return;
}
EnsureNullableAttributeExists(compilation, container, parameters, diagnostics, modifyCompilation, moduleBuilder: null);
}
private static void EnsureNullableAttributeExists(CSharpCompilation compilation, Symbol container, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag? diagnostics, bool modifyCompilation, PEModuleBuilder? moduleBuilder)
{
if (parameters.Length > 0 && compilation.ShouldEmitNullableAttributes(container))
{
foreach (var parameter in parameters)
{
if (parameter.TypeWithAnnotations.NeedsNullableAttribute())
{
if (moduleBuilder is { })
{
moduleBuilder.EnsureNullableAttributeExists();
}
else
{
compilation.EnsureNullableAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation);
}
}
}
}
}
internal static Location GetParameterLocation(ParameterSymbol parameter) => parameter.GetNonNullSyntaxNode().Location;
internal static void CheckParameterModifiers(
BaseParameterSyntax parameter,
BindingDiagnosticBag diagnostics,
bool parsingFunctionPointerParams,
bool parsingLambdaParams,
bool parsingAnonymousMethodParams)
{
Debug.Assert(!parsingLambdaParams || !parsingAnonymousMethodParams);
var seenThis = false;
var seenRef = false;
var seenOut = false;
var seenParams = false;
var seenIn = false;
bool seenScoped = false;
bool seenReadonly = false;
SyntaxToken? previousModifier = null;
foreach (var modifier in parameter.Modifiers)
{
switch (modifier.Kind())
{
case SyntaxKind.ThisKeyword:
Binder.CheckFeatureAvailability(modifier, MessageID.IDS_FeatureExtensionMethod, diagnostics);
if (seenRef || seenIn)
{
Binder.CheckFeatureAvailability(modifier, MessageID.IDS_FeatureRefExtensionMethods, diagnostics);
}
if (parsingLambdaParams || parsingAnonymousMethodParams)
{
diagnostics.Add(ErrorCode.ERR_ThisInBadContext, modifier.GetLocation());
}
else if (seenThis)
{
addERR_DupParamMod(diagnostics, modifier);
}
else if (seenOut)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.OutKeyword);
}
else if (seenParams)
{
diagnostics.Add(ErrorCode.ERR_BadParamModThis, modifier.GetLocation());
}
else
{
seenThis = true;
}
break;
case SyntaxKind.RefKeyword:
if (seenThis)
{
Binder.CheckFeatureAvailability(modifier, MessageID.IDS_FeatureRefExtensionMethods, diagnostics);
}
if (seenRef)
{
addERR_DupParamMod(diagnostics, modifier);
}
else if (seenParams)
{
addERR_ParamsCantBeWithModifier(diagnostics, modifier, SyntaxKind.RefKeyword);
}
else if (seenOut)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.OutKeyword);
}
else if (seenIn)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.InKeyword);
}
else
{
seenRef = true;
}
break;
case SyntaxKind.OutKeyword:
if (seenOut)
{
addERR_DupParamMod(diagnostics, modifier);
}
else if (seenThis)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.ThisKeyword);
}
else if (seenParams)
{
addERR_ParamsCantBeWithModifier(diagnostics, modifier, SyntaxKind.OutKeyword);
}
else if (seenRef)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.RefKeyword);
}
else if (seenIn)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.InKeyword);
}
else
{
seenOut = true;
}
break;
case SyntaxKind.ParamsKeyword when !parsingFunctionPointerParams:
if (parsingAnonymousMethodParams)
{
diagnostics.Add(ErrorCode.ERR_IllegalParams, modifier.GetLocation());
}
else if (seenParams)
{
addERR_DupParamMod(diagnostics, modifier);
}
else if (seenThis)
{
diagnostics.Add(ErrorCode.ERR_BadParamModThis, modifier.GetLocation());
}
else if (seenRef)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.RefKeyword);
}
else if (seenIn)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.InKeyword);
}
else if (seenOut)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.OutKeyword);
}
else
{
seenParams = true;
}
if (parsingLambdaParams)
{
MessageID.IDS_FeatureLambdaParamsArray.CheckFeatureAvailability(diagnostics, modifier);
}
break;
case SyntaxKind.InKeyword:
Binder.CheckFeatureAvailability(modifier, MessageID.IDS_FeatureReadOnlyReferences, diagnostics);
if (seenThis)
{
Binder.CheckFeatureAvailability(modifier, MessageID.IDS_FeatureRefExtensionMethods, diagnostics);
}
if (seenIn)
{
addERR_DupParamMod(diagnostics, modifier);
}
else if (seenOut)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.OutKeyword);
}
else if (seenRef)
{
addERR_BadParameterModifiers(diagnostics, modifier, SyntaxKind.RefKeyword);
}
else if (seenParams)
{
addERR_ParamsCantBeWithModifier(diagnostics, modifier, SyntaxKind.InKeyword);
}
else
{
seenIn = true;
}
break;
case SyntaxKind.ScopedKeyword when !parsingFunctionPointerParams:
ModifierUtils.CheckScopedModifierAvailability(parameter, modifier, diagnostics);
Debug.Assert(!seenIn);
Debug.Assert(!seenOut);
Debug.Assert(!seenRef);
Debug.Assert(!seenScoped);
seenScoped = true;
break;
case SyntaxKind.ReadOnlyKeyword:
if (seenReadonly)
{
addERR_DupParamMod(diagnostics, modifier);
}
else if (previousModifier?.Kind() != SyntaxKind.RefKeyword)
{
diagnostics.Add(ErrorCode.ERR_RefReadOnlyWrongOrdering, modifier);
}
else if (seenRef)
{
Binder.CheckFeatureAvailability(modifier, MessageID.IDS_FeatureRefReadonlyParameters, diagnostics);
seenReadonly = true;
}
break;
case SyntaxKind.ParamsKeyword when parsingFunctionPointerParams:
case SyntaxKind.ScopedKeyword when parsingFunctionPointerParams:
diagnostics.Add(ErrorCode.ERR_BadFuncPointerParamModifier, modifier.GetLocation(), SyntaxFacts.GetText(modifier.Kind()));
break;
default:
throw ExceptionUtilities.UnexpectedValue(modifier.Kind());
}
previousModifier = modifier;
}
static void addERR_DupParamMod(BindingDiagnosticBag diagnostics, SyntaxToken modifier)
{
diagnostics.Add(ErrorCode.ERR_DupParamMod, modifier.GetLocation(), SyntaxFacts.GetText(modifier.Kind()));
}
static void addERR_BadParameterModifiers(BindingDiagnosticBag diagnostics, SyntaxToken modifier, SyntaxKind otherModifierKind)
{
diagnostics.Add(ErrorCode.ERR_BadParameterModifiers, modifier.GetLocation(), SyntaxFacts.GetText(modifier.Kind()), SyntaxFacts.GetText(otherModifierKind));
}
static void addERR_ParamsCantBeWithModifier(BindingDiagnosticBag diagnostics, SyntaxToken modifier, SyntaxKind otherModifierKind)
{
diagnostics.Add(ErrorCode.ERR_ParamsCantBeWithModifier, modifier.GetLocation(), SyntaxFacts.GetText(otherModifierKind));
}
}
public static void ReportParameterErrors(
Symbol? owner,
BaseParameterSyntax syntax,
int ordinal,
int lastParameterIndex,
bool isParams,
TypeWithAnnotations typeWithAnnotations,
RefKind refKind,
Symbol? containingSymbol,
SyntaxToken thisKeyword,
SyntaxToken paramsKeyword,
int firstDefault,
BindingDiagnosticBag diagnostics)
{
int parameterIndex = ordinal;
bool isDefault = syntax is ParameterSyntax { Default: { } };
if (thisKeyword.Kind() == SyntaxKind.ThisKeyword && parameterIndex != 0)
{
// Report CS1100 on "this". Note that is a change from Dev10
// which reports the error on the type following "this".
// error CS1100: Method '{0}' has a parameter modifier 'this' which is not on the first parameter
diagnostics.Add(ErrorCode.ERR_BadThisParam, thisKeyword.GetLocation(), owner?.Name ?? "");
}
else if (isParams && owner is { } && owner.IsOperator())
{
// error CS1670: params is not valid in this context
diagnostics.Add(ErrorCode.ERR_IllegalParams, paramsKeyword.GetLocation());
}
else if (typeWithAnnotations.IsStatic)
{
Debug.Assert(containingSymbol is null || (containingSymbol is FunctionPointerMethodSymbol or { ContainingType: not null }));
// error CS0721: '{0}': static types cannot be used as parameters
diagnostics.Add(
ErrorFacts.GetStaticClassParameterCode(containingSymbol?.ContainingType?.IsInterfaceType() ?? false),
syntax.Type?.Location ?? syntax.GetLocation(),
typeWithAnnotations.Type);
}
else if (firstDefault != -1 && parameterIndex > firstDefault && !isDefault && !isParams)
{
// error CS1737: Optional parameters must appear after all required parameters
Location loc = ((ParameterSyntax)syntax).Identifier.GetNextToken(includeZeroWidth: true).GetLocation(); //could be missing
diagnostics.Add(ErrorCode.ERR_DefaultValueBeforeRequiredValue, loc);
}
else if (refKind != RefKind.None &&
typeWithAnnotations.IsRestrictedType(ignoreSpanLikeTypes: true))
{
// CS1601: Cannot make reference to variable of type 'System.TypedReference'
diagnostics.Add(ErrorCode.ERR_MethodArgCantBeRefAny, syntax.Location, typeWithAnnotations.Type);
}
if (isParams && ordinal != lastParameterIndex)
{
// error CS0231: A params parameter must be the last parameter in a parameter list
diagnostics.Add(ErrorCode.ERR_ParamsLast, syntax.GetLocation());
}
}
#nullable disable
internal static bool ReportDefaultParameterErrors(
Binder binder,
Symbol owner,
ParameterSyntax parameterSyntax,
SourceParameterSymbol parameter,
BoundExpression defaultExpression,
BoundExpression convertedExpression,
BindingDiagnosticBag diagnostics)
{
bool hasErrors = false;
// SPEC VIOLATION: The spec says that the conversion from the initializer to the
// parameter type is required to be either an identity or a nullable conversion, but
// that is not right:
//
// void M(short myShort = 10) {}
// * not an identity or nullable conversion but should be legal
//
// void M(object obj = (dynamic)null) {}
// * an identity conversion, but should be illegal
//
// void M(MyStruct? myStruct = default(MyStruct)) {}
// * a nullable conversion, but must be illegal because we cannot generate metadata for it
//
// Even if the expression is thoroughly illegal, we still want to bind it and
// stick it in the parameter because we want to be able to analyze it for
// IntelliSense purposes.
TypeSymbol parameterType = parameter.Type;
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics);
Conversion conversion = binder.Conversions.ClassifyImplicitConversionFromExpression(defaultExpression, parameterType, ref useSiteInfo);
diagnostics.Add(defaultExpression.Syntax, useSiteInfo);
var refKind = GetModifiers(parameterSyntax.Modifiers, out SyntaxToken refnessKeyword, out SyntaxToken paramsKeyword, out SyntaxToken thisKeyword, out _);
// CONSIDER: We are inconsistent here regarding where the error is reported; is it
// CONSIDER: reported on the parameter name, or on the value of the initializer?
// CONSIDER: Consider making this consistent.
if (refKind == RefKind.Ref || refKind == RefKind.Out)
{
// error CS1741: A ref or out parameter cannot have a default value
diagnostics.Add(ErrorCode.ERR_RefOutDefaultValue, refnessKeyword.GetLocation());
hasErrors = true;
}
else if (paramsKeyword.Kind() == SyntaxKind.ParamsKeyword)
{
// error CS1751: Cannot specify a default value for a parameter collection
diagnostics.Add(ErrorCode.ERR_DefaultValueForParamsParameter, paramsKeyword.GetLocation());
hasErrors = true;
}
else if (thisKeyword.Kind() == SyntaxKind.ThisKeyword)
{
// Only need to report CS1743 for the first parameter. The caller will
// have reported CS1100 if 'this' appeared on another parameter.
if (parameter.Ordinal == 0)
{
// error CS1743: Cannot specify a default value for the 'this' parameter
diagnostics.Add(ErrorCode.ERR_DefaultValueForExtensionParameter, thisKeyword.GetLocation());
hasErrors = true;
}
}
else if (!defaultExpression.HasAnyErrors &&
!IsValidDefaultValue(defaultExpression.IsImplicitObjectCreation() ?
convertedExpression : defaultExpression))
{
// error CS1736: Default parameter value for '{0}' must be a compile-time constant
diagnostics.Add(ErrorCode.ERR_DefaultValueMustBeConstant, parameterSyntax.Default.Value.Location, parameterSyntax.Identifier.ValueText);
hasErrors = true;
}
else if (!conversion.Exists ||
conversion.IsUserDefined ||
conversion.IsIdentity && parameterType.SpecialType == SpecialType.System_Object && defaultExpression.Type.IsDynamic())
{
// If we had no implicit conversion, or a user-defined conversion, report an error.
//
// Even though "object x = (dynamic)null" is a legal identity conversion, we do not allow it.
// CONSIDER: We could. Doesn't hurt anything.
// error CS1750: A value of type '{0}' cannot be used as a default parameter because there are no standard conversions to type '{1}'
diagnostics.Add(ErrorCode.ERR_NoConversionForDefaultParam, parameterSyntax.Identifier.GetLocation(),
defaultExpression.Display, parameterType);
hasErrors = true;
}
else if (conversion.IsReference &&
(object)defaultExpression.Type != null &&
defaultExpression.Type.SpecialType == SpecialType.System_String ||
conversion.IsBoxing)
{
// We don't allow object x = "hello", object x = 123, dynamic x = "hello", IEnumerable<char> x = "hello", etc.
// error CS1763: '{0}' is of type '{1}'. A default parameter value of a reference type other than string can only be initialized with null
diagnostics.Add(ErrorCode.ERR_NotNullRefDefaultParameter, parameterSyntax.Identifier.GetLocation(),
parameterSyntax.Identifier.ValueText, parameterType);
hasErrors = true;
}
else if (((conversion.IsNullable && !defaultExpression.Type.IsNullableType()) ||
(conversion.IsObjectCreation && convertedExpression.Type.IsNullableType())) &&
!(parameterType.GetNullableUnderlyingType().IsEnumType() || parameterType.GetNullableUnderlyingType().IsIntrinsicType()))
{
// We can do:
// M(int? x = default(int))
// M(int? x = default(int?))
// M(MyEnum? e = default(enum))
// M(MyEnum? e = default(enum?))
// M(MyStruct? s = default(MyStruct?))
//
// but we cannot do:
//
// M(MyStruct? s = default(MyStruct))
// error CS1770:
// A value of type '{0}' cannot be used as default parameter for nullable parameter '{1}' because '{0}' is not a simple type
diagnostics.Add(ErrorCode.ERR_NoConversionForNubDefaultParam, parameterSyntax.Identifier.GetLocation(),
(defaultExpression.IsImplicitObjectCreation() ? convertedExpression.Type.StrippedType() : defaultExpression.Type), parameterSyntax.Identifier.ValueText);
hasErrors = true;
}
ConstantValueUtils.CheckLangVersionForConstantValue(convertedExpression, diagnostics);
// Certain contexts allow default parameter values syntactically but they are ignored during
// semantic analysis. They are:
// 1. Explicitly implemented interface methods; since the method will always be called
// via the interface, the defaults declared on the implementation will not
// be seen at the call site.
//
// UNDONE: 2. The "actual" side of a partial method; the default values are taken from the
// UNDONE: "declaring" side of the method.
//
// UNDONE: 3. An indexer with only one formal parameter; it is illegal to omit every argument
// UNDONE: to an indexer.
//
// 4. A user-defined operator; it is syntactically impossible to omit the argument.
if (owner.IsExplicitInterfaceImplementation() ||
owner.IsPartialImplementation() ||
owner.IsOperator())
{
// CS1066: The default value specified for parameter '{0}' will have no effect because it applies to a
// member that is used in contexts that do not allow optional arguments
diagnostics.Add(ErrorCode.WRN_DefaultValueForUnconsumedLocation,
parameterSyntax.Identifier.GetLocation(),
parameterSyntax.Identifier.ValueText);
}
if (refKind == RefKind.RefReadOnlyParameter)
{
// A default value is specified for 'ref readonly' parameter '{0}', but 'ref readonly' should be used only for references. Consider declaring the parameter as 'in'.
diagnostics.Add(ErrorCode.WRN_RefReadonlyParameterDefaultValue, parameterSyntax.Default.Value, parameterSyntax.Identifier.ValueText);
}
return hasErrors;
}
private static bool IsValidDefaultValue(BoundExpression expression)
{
// SPEC VIOLATION:
// By the spec an optional parameter initializer is required to be either:
// * a constant,
// * new S() where S is a value type
// * default(S) where S is a value type.
//
// The native compiler considers default(T) to be a valid
// initializer regardless of whether T is a value type
// reference type, type parameter type, and so on.
// We should consider simply allowing this in the spec.
//
// Also when valuetype S has a parameterless constructor,
// new S() is clearly not a constant expression and should produce an error
if (expression.ConstantValueOpt != null)
{
return true;
}
switch (expression.Kind)
{
case BoundKind.DefaultLiteral:
case BoundKind.DefaultExpression:
return true;
case BoundKind.ObjectCreationExpression:
return IsValidDefaultValue((BoundObjectCreationExpression)expression);
case BoundKind.Conversion:
var conversion = (BoundConversion)expression;
return conversion is { Conversion.IsObjectCreation: true, Operand: BoundObjectCreationExpression { WasTargetTyped: true } operand } &&
IsValidDefaultValue(operand);
default:
return false;
}
}
private static bool IsValidDefaultValue(BoundObjectCreationExpression expression)
{
return expression.Constructor.IsDefaultValueTypeConstructor() && expression.InitializerExpressionOpt == null;
}
internal static MethodSymbol FindContainingGenericMethod(Symbol symbol)
{
for (Symbol current = symbol; (object)current != null; current = current.ContainingSymbol)
{
if (current.Kind == SymbolKind.Method)
{
MethodSymbol method = (MethodSymbol)current;
if (method.MethodKind != MethodKind.AnonymousFunction)
{
return method.IsGenericMethod ? method : null;
}
}
}
return null;
}
internal static RefKind GetModifiers(SyntaxTokenList modifiers, out SyntaxToken refnessKeyword, out SyntaxToken paramsKeyword, out SyntaxToken thisKeyword, out ScopedKind scope)
{
var refKind = RefKind.None;
bool isScoped = false;
refnessKeyword = default(SyntaxToken);
paramsKeyword = default(SyntaxToken);
thisKeyword = default(SyntaxToken);
foreach (var modifier in modifiers)
{
switch (modifier.Kind())
{
case SyntaxKind.OutKeyword:
if (refKind == RefKind.None)
{
refnessKeyword = modifier;
refKind = RefKind.Out;
}
break;
case SyntaxKind.RefKeyword:
if (refKind == RefKind.None)
{
refnessKeyword = modifier;
refKind = RefKind.Ref;
}
break;
case SyntaxKind.InKeyword:
if (refKind == RefKind.None)
{
refnessKeyword = modifier;
refKind = RefKind.In;
}
break;
case SyntaxKind.ParamsKeyword:
paramsKeyword = modifier;
break;
case SyntaxKind.ThisKeyword:
thisKeyword = modifier;
break;
case SyntaxKind.ScopedKeyword:
Debug.Assert(refKind == RefKind.None);
isScoped = true;
break;
case SyntaxKind.ReadOnlyKeyword:
if (refKind == RefKind.Ref && refnessKeyword.GetNextToken() == modifier)
{
refKind = RefKind.RefReadOnlyParameter;
}
break;
}
}
if (isScoped)
{
scope = (refKind == RefKind.None) ? ScopedKind.ScopedValue : ScopedKind.ScopedRef;
}
else
{
scope = ScopedKind.None;
}
return refKind;
}
internal static ImmutableArray<CustomModifier> ConditionallyCreateInModifiers(RefKind refKind, bool addRefReadOnlyModifier, Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
if (addRefReadOnlyModifier && refKind is RefKind.In or RefKind.RefReadOnlyParameter)
{
return CreateInModifiers(binder, diagnostics, syntax);
}
else
{
return ImmutableArray<CustomModifier>.Empty;
}
}
internal static ImmutableArray<CustomModifier> CreateInModifiers(Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
return CreateModifiers(WellKnownType.System_Runtime_InteropServices_InAttribute, binder, diagnostics, syntax);
}
// only for function pointer parameters
private static ImmutableArray<CustomModifier> CreateRefReadonlyParameterModifiers(Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
var requiresLocationType = binder.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_RequiresLocationAttribute, diagnostics, syntax);
return ImmutableArray.Create(CSharpCustomModifier.CreateOptional(requiresLocationType));
}
internal static ImmutableArray<CustomModifier> CreateOutModifiers(Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
return CreateModifiers(WellKnownType.System_Runtime_InteropServices_OutAttribute, binder, diagnostics, syntax);
}
private static ImmutableArray<CustomModifier> CreateModifiers(WellKnownType modifier, Binder binder, BindingDiagnosticBag diagnostics, SyntaxNode syntax)
{
var modifierType = binder.GetWellKnownType(modifier, diagnostics, syntax);
return ImmutableArray.Create(CSharpCustomModifier.CreateRequired(modifierType));
}
}
}
|