|
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal abstract class SourceUserDefinedOperatorSymbolBase : SourceOrdinaryMethodOrUserDefinedOperatorSymbol
{
// tomat: ignoreDynamic should be true, but we don't want to introduce breaking change. See bug 605326.
private const TypeCompareKind ComparisonForUserDefinedOperators = TypeCompareKind.IgnoreTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes;
private readonly string _name;
#nullable enable
private readonly TypeSymbol? _explicitInterfaceType;
#nullable disable
protected SourceUserDefinedOperatorSymbolBase(
MethodKind methodKind,
TypeSymbol explicitInterfaceType,
string name,
SourceMemberContainerTypeSymbol containingType,
Location location,
CSharpSyntaxNode syntax,
DeclarationModifiers declarationModifiers,
bool hasAnyBody,
bool isExpressionBodied,
bool isIterator,
bool isNullableAnalysisEnabled,
BindingDiagnosticBag diagnostics) :
base(containingType, syntax.GetReference(), location, isIterator: isIterator,
(declarationModifiers, MakeFlags(
methodKind, RefKind.None, declarationModifiers,
// We will bind the formal parameters and the return type lazily. For now,
// assume that the return type is non-void; when we do the lazy initialization
// of the parameters and return type we will update the flag if necessary.
returnsVoid: false,
returnsVoidIsSet: false,
isExpressionBodied: isExpressionBodied,
isExtensionMethod: false, isVarArg: false, isNullableAnalysisEnabled: isNullableAnalysisEnabled,
isExplicitInterfaceImplementation: methodKind == MethodKind.ExplicitInterfaceImplementation,
hasThisInitializer: false)))
{
_explicitInterfaceType = explicitInterfaceType;
_name = name;
this.CheckUnsafeModifier(declarationModifiers, diagnostics);
if (this.ContainingType.IsInterface &&
!(IsAbstract || IsVirtual) && !IsExplicitInterfaceImplementation &&
!(syntax is OperatorDeclarationSyntax { OperatorToken: var opToken } && opToken.Kind() is not (SyntaxKind.EqualsEqualsToken or SyntaxKind.ExclamationEqualsToken)))
{
diagnostics.Add(ErrorCode.ERR_InterfacesCantContainConversionOrEqualityOperators, this.GetFirstLocation());
// No need to cascade the error further.
return;
}
if (this.ContainingType.IsStatic)
{
// Similarly if we're in a static class, though we have not reported it yet.
// CS0715: '{0}': static classes cannot contain user-defined operators
diagnostics.Add(ErrorCode.ERR_OperatorInStaticClass, location, this);
return;
}
// SPEC: An operator declaration must include both a public and a
// SPEC: static modifier
if (this.IsExplicitInterfaceImplementation)
{
if (!this.IsStatic)
{
diagnostics.Add(ErrorCode.ERR_ExplicitImplementationOfOperatorsMustBeStatic, this.GetFirstLocation(), this);
}
}
else if (this.DeclaredAccessibility != Accessibility.Public || !this.IsStatic)
{
// CS0558: User-defined operator '...' must be declared static and public
diagnostics.Add(ErrorCode.ERR_OperatorsMustBeStatic, this.GetFirstLocation(), this);
}
// SPEC: Because an external operator provides no actual implementation,
// SPEC: its operator body consists of a semicolon. For expression-bodied
// SPEC: operators, the body is an expression. For all other operators,
// SPEC: the operator body consists of a block...
if (IsAbstract && IsExtern)
{
diagnostics.Add(ErrorCode.ERR_AbstractAndExtern, location, this);
}
else if (IsAbstract && IsVirtual)
{
diagnostics.Add(ErrorCode.ERR_AbstractNotVirtual, location, this.Kind.Localize(), this);
}
else if (hasAnyBody && (IsExtern || IsAbstract))
{
Debug.Assert(!(IsAbstract && IsExtern));
if (IsExtern)
{
diagnostics.Add(ErrorCode.ERR_ExternHasBody, location, this);
}
else
{
diagnostics.Add(ErrorCode.ERR_AbstractHasBody, location, this);
}
}
else if (!hasAnyBody && !IsExtern && !IsAbstract && !IsPartial)
{
// Do not report that the body is missing if the operator is marked as
// partial or abstract; we will already have given an error for that so
// there's no need to "cascade" the error.
diagnostics.Add(ErrorCode.ERR_ConcreteMissingBody, location, this);
}
// SPEC: It is an error for the same modifier to appear multiple times in an
// SPEC: operator declaration.
ModifierUtils.CheckAccessibility(this.DeclarationModifiers, this, isExplicitInterfaceImplementation: false, diagnostics, location);
}
protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind methodKind, bool inInterface, BaseMethodDeclarationSyntax syntax, Location location, BindingDiagnosticBag diagnostics)
{
bool isExplicitInterfaceImplementation = methodKind == MethodKind.ExplicitInterfaceImplementation;
var defaultAccess = inInterface && !isExplicitInterfaceImplementation ? DeclarationModifiers.Public : DeclarationModifiers.Private;
var allowedModifiers =
DeclarationModifiers.Static |
DeclarationModifiers.Extern |
DeclarationModifiers.Unsafe;
if (!isExplicitInterfaceImplementation)
{
allowedModifiers |= DeclarationModifiers.AccessibilityMask;
if (inInterface)
{
allowedModifiers |= DeclarationModifiers.Abstract | DeclarationModifiers.Virtual;
if (syntax is OperatorDeclarationSyntax { OperatorToken: var opToken } && opToken.Kind() is not (SyntaxKind.EqualsEqualsToken or SyntaxKind.ExclamationEqualsToken))
{
allowedModifiers |= DeclarationModifiers.Sealed;
}
}
}
else if (inInterface)
{
Debug.Assert(isExplicitInterfaceImplementation);
allowedModifiers |= DeclarationModifiers.Abstract;
}
var result = ModifierUtils.MakeAndCheckNonTypeMemberModifiers(
isOrdinaryMethod: false, isForInterfaceMember: inInterface,
syntax.Modifiers, defaultAccess, allowedModifiers, location, diagnostics, modifierErrors: out _);
if (inInterface)
{
if ((result & (DeclarationModifiers.Abstract | DeclarationModifiers.Virtual | DeclarationModifiers.Sealed)) != 0)
{
if ((result & DeclarationModifiers.Sealed) != 0 &&
(result & (DeclarationModifiers.Abstract | DeclarationModifiers.Virtual)) != 0)
{
diagnostics.Add(ErrorCode.ERR_BadMemberFlag, location, ModifierUtils.ConvertSingleModifierToSyntaxText(DeclarationModifiers.Sealed));
result &= ~DeclarationModifiers.Sealed;
}
LanguageVersion availableVersion = ((CSharpParseOptions)location.SourceTree.Options).LanguageVersion;
LanguageVersion requiredVersion = MessageID.IDS_FeatureStaticAbstractMembersInInterfaces.RequiredVersion();
if (availableVersion < requiredVersion)
{
var requiredVersionArgument = new CSharpRequiredLanguageVersion(requiredVersion);
var availableVersionArgument = availableVersion.ToDisplayString();
if ((result & DeclarationModifiers.Abstract) != 0)
{
reportModifierIfPresent(result, DeclarationModifiers.Abstract, location, diagnostics, requiredVersionArgument, availableVersionArgument);
}
else
{
reportModifierIfPresent(result, DeclarationModifiers.Virtual, location, diagnostics, requiredVersionArgument, availableVersionArgument);
}
reportModifierIfPresent(result, DeclarationModifiers.Sealed, location, diagnostics, requiredVersionArgument, availableVersionArgument);
}
result &= ~DeclarationModifiers.Sealed;
}
else if ((result & DeclarationModifiers.Static) != 0 && syntax is OperatorDeclarationSyntax { OperatorToken: var opToken } && opToken.Kind() is not (SyntaxKind.EqualsEqualsToken or SyntaxKind.ExclamationEqualsToken))
{
Binder.CheckFeatureAvailability(location.SourceTree, MessageID.IDS_DefaultInterfaceImplementation, diagnostics, location);
}
}
if (isExplicitInterfaceImplementation)
{
if ((result & DeclarationModifiers.Abstract) != 0)
{
result |= DeclarationModifiers.Sealed;
}
}
return result;
static void reportModifierIfPresent(DeclarationModifiers result, DeclarationModifiers errorModifier, Location location, BindingDiagnosticBag diagnostics, CSharpRequiredLanguageVersion requiredVersionArgument, string availableVersionArgument)
{
if ((result & errorModifier) != 0)
{
diagnostics.Add(ErrorCode.ERR_InvalidModifierForLanguageVersion, location,
ModifierUtils.ConvertSingleModifierToSyntaxText(errorModifier),
availableVersionArgument,
requiredVersionArgument);
}
}
}
protected (TypeWithAnnotations ReturnType, ImmutableArray<ParameterSymbol> Parameters) MakeParametersAndBindReturnType(BaseMethodDeclarationSyntax declarationSyntax, TypeSyntax returnTypeSyntax, BindingDiagnosticBag diagnostics)
{
TypeWithAnnotations returnType;
ImmutableArray<ParameterSymbol> parameters;
var binder = this.DeclaringCompilation.
GetBinderFactory(declarationSyntax.SyntaxTree).GetBinder(returnTypeSyntax, declarationSyntax, this);
SyntaxToken arglistToken;
var signatureBinder = binder.WithAdditionalFlags(BinderFlags.SuppressConstraintChecks);
parameters = ParameterHelpers.MakeParameters(
signatureBinder,
this,
declarationSyntax.ParameterList,
out arglistToken,
allowRefOrOut: true,
allowThis: false,
addRefReadOnlyModifier: IsVirtual || IsAbstract,
diagnostics: diagnostics).Cast<SourceParameterSymbol, ParameterSymbol>();
if (arglistToken.Kind() == SyntaxKind.ArgListKeyword)
{
// This is a parse-time error in the native compiler; it is a semantic analysis error in Roslyn.
// error CS1669: __arglist is not valid in this context
diagnostics.Add(ErrorCode.ERR_IllegalVarArgs, new SourceLocation(arglistToken));
// Regardless of whether __arglist appears in the source code, we do not mark
// the operator method as being a varargs method.
}
returnType = signatureBinder.BindType(returnTypeSyntax, diagnostics);
// restricted types cannot be returned.
// NOTE: Span-like types can be returned (if expression is returnable).
if (returnType.IsRestrictedType(ignoreSpanLikeTypes: true))
{
// The return type of a method, delegate, or function pointer cannot be '{0}'
diagnostics.Add(ErrorCode.ERR_MethodReturnCantBeRefAny, returnTypeSyntax.Location, returnType.Type);
}
if (returnType.Type.IsStatic)
{
// Operators in interfaces was introduced in C# 8, so there's no need to be specially concerned about
// maintaining backcompat with the native compiler bug around interfaces.
// '{0}': static types cannot be used as return types
diagnostics.Add(ErrorFacts.GetStaticClassReturnCode(useWarning: false), returnTypeSyntax.Location, returnType.Type);
}
return (returnType, parameters);
}
protected override void MethodChecks(BindingDiagnosticBag diagnostics)
{
var (returnType, parameters) = MakeParametersAndBindReturnType(diagnostics);
MethodChecks(returnType, parameters, diagnostics);
// If we have a static class then we already
// have reported that fact as an error. No need to cascade the error further.
if (this.ContainingType.IsStatic)
{
return;
}
CheckValueParameters(diagnostics);
CheckOperatorSignatures(diagnostics);
}
protected abstract (TypeWithAnnotations ReturnType, ImmutableArray<ParameterSymbol> Parameters) MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics);
protected sealed override void ExtensionMethodChecks(BindingDiagnosticBag diagnostics)
{
}
protected sealed override MethodSymbol FindExplicitlyImplementedMethod(BindingDiagnosticBag diagnostics)
{
if (_explicitInterfaceType is object)
{
string interfaceMethodName;
ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier;
switch (syntaxReferenceOpt.GetSyntax())
{
case OperatorDeclarationSyntax operatorDeclaration:
interfaceMethodName = OperatorFacts.OperatorNameFromDeclaration(operatorDeclaration);
explicitInterfaceSpecifier = operatorDeclaration.ExplicitInterfaceSpecifier;
break;
case ConversionOperatorDeclarationSyntax conversionDeclaration:
interfaceMethodName = OperatorFacts.OperatorNameFromDeclaration(conversionDeclaration);
explicitInterfaceSpecifier = conversionDeclaration.ExplicitInterfaceSpecifier;
break;
default:
throw ExceptionUtilities.Unreachable();
}
return this.FindExplicitlyImplementedMethod(isOperator: true, _explicitInterfaceType, interfaceMethodName, explicitInterfaceSpecifier, diagnostics);
}
return null;
}
#nullable enable
protected sealed override TypeSymbol? ExplicitInterfaceType => _explicitInterfaceType;
#nullable disable
private void CheckValueParameters(BindingDiagnosticBag diagnostics)
{
// SPEC: The parameters of an operator must be value parameters.
foreach (var p in this.Parameters)
{
if (p.RefKind != RefKind.None && p.RefKind != RefKind.In)
{
diagnostics.Add(ErrorCode.ERR_IllegalRefParam, this.GetFirstLocation());
break;
}
}
}
private void CheckOperatorSignatures(BindingDiagnosticBag diagnostics)
{
if (MethodKind == MethodKind.ExplicitInterfaceImplementation)
{
// The signature is driven by the interface
return;
}
// Have we even got the right formal parameter arity? If not then
// we are in an error recovery scenario and we should just bail
// out immediately.
if (!DoesOperatorHaveCorrectArity(this.Name, this.ParameterCount))
{
return;
}
switch (this.Name)
{
case WellKnownMemberNames.ImplicitConversionName:
case WellKnownMemberNames.ExplicitConversionName:
case WellKnownMemberNames.CheckedExplicitConversionName:
CheckUserDefinedConversionSignature(diagnostics);
break;
case WellKnownMemberNames.CheckedUnaryNegationOperatorName:
case WellKnownMemberNames.UnaryNegationOperatorName:
case WellKnownMemberNames.UnaryPlusOperatorName:
case WellKnownMemberNames.LogicalNotOperatorName:
case WellKnownMemberNames.OnesComplementOperatorName:
CheckUnarySignature(diagnostics);
break;
case WellKnownMemberNames.TrueOperatorName:
case WellKnownMemberNames.FalseOperatorName:
CheckTrueFalseSignature(diagnostics);
break;
case WellKnownMemberNames.CheckedIncrementOperatorName:
case WellKnownMemberNames.IncrementOperatorName:
case WellKnownMemberNames.CheckedDecrementOperatorName:
case WellKnownMemberNames.DecrementOperatorName:
CheckIncrementDecrementSignature(diagnostics);
break;
case WellKnownMemberNames.LeftShiftOperatorName:
case WellKnownMemberNames.RightShiftOperatorName:
case WellKnownMemberNames.UnsignedRightShiftOperatorName:
CheckShiftSignature(diagnostics);
break;
case WellKnownMemberNames.EqualityOperatorName:
case WellKnownMemberNames.InequalityOperatorName:
if (IsAbstract || IsVirtual)
{
CheckAbstractEqualitySignature(diagnostics);
}
else
{
CheckBinarySignature(diagnostics);
}
break;
default:
CheckBinarySignature(diagnostics);
break;
}
}
private static bool DoesOperatorHaveCorrectArity(string name, int parameterCount)
{
switch (name)
{
case WellKnownMemberNames.CheckedIncrementOperatorName:
case WellKnownMemberNames.IncrementOperatorName:
case WellKnownMemberNames.CheckedDecrementOperatorName:
case WellKnownMemberNames.DecrementOperatorName:
case WellKnownMemberNames.CheckedUnaryNegationOperatorName:
case WellKnownMemberNames.UnaryNegationOperatorName:
case WellKnownMemberNames.UnaryPlusOperatorName:
case WellKnownMemberNames.LogicalNotOperatorName:
case WellKnownMemberNames.OnesComplementOperatorName:
case WellKnownMemberNames.TrueOperatorName:
case WellKnownMemberNames.FalseOperatorName:
case WellKnownMemberNames.ImplicitConversionName:
case WellKnownMemberNames.ExplicitConversionName:
case WellKnownMemberNames.CheckedExplicitConversionName:
return parameterCount == 1;
default:
return parameterCount == 2;
}
}
private void CheckUserDefinedConversionSignature(BindingDiagnosticBag diagnostics)
{
CheckReturnIsNotVoid(diagnostics);
// SPEC: For a given source type S and target type T, if S or T are
// SPEC: nullable types let S0 and T0 refer to their underlying types,
// SPEC: otherwise, S0 and T0 are equal to S and T, respectively.
var source = this.GetParameterType(0);
var target = this.ReturnType;
var source0 = source.StrippedType();
var target0 = target.StrippedType();
// SPEC: A class or struct is permitted to declare a conversion from S to T
// SPEC: only if all the following are true:
// SPEC: Neither S0 nor T0 is an interface type.
if (source0.IsInterfaceType() || target0.IsInterfaceType())
{
// CS0552: '{0}': user-defined conversions to or from an interface are not allowed
diagnostics.Add(ErrorCode.ERR_ConversionWithInterface, this.GetFirstLocation(), this);
return;
}
// SPEC: Either S0 or T0 is the class or struct type in which the operator
// SPEC: declaration takes place.
if (!MatchesContainingType(source0) &&
!MatchesContainingType(target0) &&
// allow conversion between T and Nullable<T> in declaration of Nullable<T>
!MatchesContainingType(source) &&
!MatchesContainingType(target))
{
// CS0556: User-defined conversion must convert to or from the enclosing type
diagnostics.Add(IsAbstract || IsVirtual ? ErrorCode.ERR_AbstractConversionNotInvolvingContainedType : ErrorCode.ERR_ConversionNotInvolvingContainedType, this.GetFirstLocation());
return;
}
// SPEC: * S0 and T0 are different types:
if ((ContainingType.SpecialType == SpecialType.System_Nullable_T)
? source.Equals(target, ComparisonForUserDefinedOperators)
: source0.Equals(target0, ComparisonForUserDefinedOperators))
{
// CS0555: User-defined operator cannot convert a type to itself
diagnostics.Add(ErrorCode.ERR_IdentityConversion, this.GetFirstLocation());
return;
}
// Those are the easy ones. Now we come to:
// SPEC:
// Excluding user-defined conversions, a conversion does not exist from
// S to T or T to S. For the purposes of these rules, any type parameters
// associated with S or T are considered to be unique types that have
// no inheritance relationship with other types, and any constraints on
// those type parameters are ignored.
// A counter-intuitive consequence of this rule is that:
//
// class X<U> where U : X<U>
// {
// public implicit operator X<U>(U u) { return u; }
// }
//
// is *legal*, even though there is *already* an implicit conversion
// from U to X<U> because U is constrained to have such a conversion.
//
// In discussing the implications of this rule, let's call the
// containing type (which may be a class or struct) "C". S and T
// are the source and target types.
//
// If we have made it this far in the error analysis we already know that
// exactly one of S and T is C or C? -- if two or zero were, then we'd
// have already reported ERR_ConversionNotInvolvingContainedType or
// ERR_IdentityConversion and returned.
//
// WOLOG for the purposes of this discussion let's assume that S is
// the one that is C or C?, and that T is the one that is neither C nor C?.
//
// So the question is: under what circumstances could T-to-S or S-to-T,
// be a valid conversion, by the definition of valid above?
//
// Let's consider what kinds of types T could be. T cannot be an interface
// because we've already reported an error and returned if it is. If T is
// a delegate, array, enum, pointer, struct or nullable type then there
// is no built-in conversion from T to the user-declared class/struct
// C, or to C?. If T is a type parameter, then by assumption the type
// parameter has no constraints, and therefore is not convertible to
// C or C?.
//
// That leaves T to be a class. We already know that T is not C, (or C?,
// since T is a class) and therefore there is no identity conversion from T to S.
//
// Suppose S is C and C is a class. Then the only way that there can be a
// conversion between T and S is if T is a base class of S or S is a base class of T.
//
// Suppose S is C and C is a struct. Then the only way that there can be a
// conversion between T and S is if T is a base class of S. (And T would
// have to be System.Object or System.ValueType.)
//
// Suppose S is C? and C is a struct. Then the only way that there can be a
// conversion between T and S is again, if T is a base class of S.
//
// Summing up:
//
// WOLOG, we assume that T is not C or C?, and S is C or C?. The conversion is
// illegal only if T is a class, and either T is a base class of S, or S is a
// base class of T.
if (source.IsDynamic() || target.IsDynamic())
{
// '{0}': user-defined conversions to or from the dynamic type are not allowed
diagnostics.Add(ErrorCode.ERR_BadDynamicConversion, this.GetFirstLocation(), this);
return;
}
TypeSymbol same;
TypeSymbol different;
if (MatchesContainingType(source0))
{
same = source;
different = target;
}
else
{
same = target;
different = source;
}
if (different.IsClassType() && !same.IsTypeParameter())
{
// different is a class type:
Debug.Assert(!different.IsTypeParameter());
var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly);
if (same.IsDerivedFrom(different, ComparisonForUserDefinedOperators, useSiteInfo: ref useSiteInfo))
{
// '{0}': user-defined conversions to or from a base type are not allowed
diagnostics.Add(ErrorCode.ERR_ConversionWithBase, this.GetFirstLocation(), this);
}
else if (different.IsDerivedFrom(same, ComparisonForUserDefinedOperators, useSiteInfo: ref useSiteInfo))
{
// '{0}': user-defined conversions to or from a derived type are not allowed
diagnostics.Add(ErrorCode.ERR_ConversionWithDerived, this.GetFirstLocation(), this);
}
diagnostics.Add(this.GetFirstLocation(), useSiteInfo);
}
}
private void CheckReturnIsNotVoid(BindingDiagnosticBag diagnostics)
{
if (this.ReturnsVoid)
{
// CS0590: User-defined operators cannot return void
diagnostics.Add(ErrorCode.ERR_OperatorCantReturnVoid, this.GetFirstLocation());
}
}
private void CheckUnarySignature(BindingDiagnosticBag diagnostics)
{
// SPEC: A unary + - ! ~ operator must take a single parameter of type
// SPEC: T or T? and can return any type.
if (!MatchesContainingType(this.GetParameterType(0).StrippedType()))
{
// The parameter of a unary operator must be the containing type
diagnostics.Add((IsAbstract || IsVirtual) ? ErrorCode.ERR_BadAbstractUnaryOperatorSignature : ErrorCode.ERR_BadUnaryOperatorSignature, this.GetFirstLocation());
}
CheckReturnIsNotVoid(diagnostics);
}
private void CheckTrueFalseSignature(BindingDiagnosticBag diagnostics)
{
// SPEC: A unary true or false operator must take a single parameter of type
// SPEC: T or T? and must return type bool.
if (this.ReturnType.SpecialType != SpecialType.System_Boolean)
{
// The return type of operator True or False must be bool
diagnostics.Add(ErrorCode.ERR_OpTFRetType, this.GetFirstLocation());
}
if (!MatchesContainingType(this.GetParameterType(0).StrippedType()))
{
// The parameter of a unary operator must be the containing type
diagnostics.Add((IsAbstract || IsVirtual) ? ErrorCode.ERR_BadAbstractUnaryOperatorSignature : ErrorCode.ERR_BadUnaryOperatorSignature, this.GetFirstLocation());
}
}
private void CheckIncrementDecrementSignature(BindingDiagnosticBag diagnostics)
{
// SPEC: A unary ++ or -- operator must take a single parameter of type T or T?
// SPEC: and it must return that same type or a type derived from it.
// The native compiler error reporting behavior is not very good in some cases
// here, both because it reports the wrong errors, and because the wording
// of the error messages is misleading. The native compiler reports two errors:
// CS0448: The return type for ++ or -- operator must be the
// containing type or derived from the containing type
//
// CS0559: The parameter type for ++ or -- operator must be the containing type
//
// Neither error message mentions nullable types. But worse, there is a
// situation in which the native compiler reports a misleading error:
//
// struct S { public static S operator ++(S? s) { ... } }
//
// This reports CS0559, but that is not the error; the *parameter* is perfectly
// legal. The error is that the return type does not match the parameter type.
//
// I have changed the error message to reflect the true error, and we now
// report 0448, not 0559, in the given scenario. The error is now:
//
// CS0448: The return type for ++ or -- operator must match the parameter type
// or be derived from the parameter type
//
// However, this now means that we must make *another* change from native compiler
// behavior. The native compiler would report both 0448 and 0559 when given:
//
// struct S { public static int operator ++(int s) { ... } }
//
// The previous wording of error 0448 was *correct* in this scenario, but not
// it is wrong because it *does* match the formal parameter type.
//
// The solution is: First see if 0559 must be reported. Only if the formal
// parameter type is *good* do we then go on to try to report an error against
// the return type.
var parameterType = this.GetParameterType(0);
var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly);
if (!MatchesContainingType(parameterType.StrippedType()))
{
// CS0559: The parameter type for ++ or -- operator must be the containing type
diagnostics.Add((IsAbstract || IsVirtual) ? ErrorCode.ERR_BadAbstractIncDecSignature : ErrorCode.ERR_BadIncDecSignature, this.GetFirstLocation());
}
else if (!(parameterType.IsTypeParameter() ?
this.ReturnType.Equals(parameterType, ComparisonForUserDefinedOperators) :
(((IsAbstract || IsVirtual) && IsContainingType(parameterType) && IsSelfConstrainedTypeParameter(this.ReturnType)) ||
this.ReturnType.EffectiveTypeNoUseSiteDiagnostics.IsEqualToOrDerivedFrom(parameterType, ComparisonForUserDefinedOperators, useSiteInfo: ref useSiteInfo))))
{
// CS0448: The return type for ++ or -- operator must match the parameter type
// or be derived from the parameter type
diagnostics.Add((IsAbstract || IsVirtual) ? ErrorCode.ERR_BadAbstractIncDecRetType : ErrorCode.ERR_BadIncDecRetType, this.GetFirstLocation());
}
diagnostics.Add(this.GetFirstLocation(), useSiteInfo);
}
private bool MatchesContainingType(TypeSymbol type)
{
return IsContainingType(type) || ((IsAbstract || IsVirtual) && IsSelfConstrainedTypeParameter(type));
}
private bool IsContainingType(TypeSymbol type)
{
return type.Equals(this.ContainingType, ComparisonForUserDefinedOperators);
}
public static bool IsSelfConstrainedTypeParameter(TypeSymbol type, NamedTypeSymbol containingType)
{
Debug.Assert(containingType.IsDefinition);
return type is TypeParameterSymbol p &&
(object)p.ContainingSymbol == containingType &&
p.ConstraintTypesNoUseSiteDiagnostics.Any((typeArgument, containingType) => typeArgument.Type.Equals(containingType, ComparisonForUserDefinedOperators),
containingType);
}
private bool IsSelfConstrainedTypeParameter(TypeSymbol type)
{
return IsSelfConstrainedTypeParameter(type, this.ContainingType);
}
private void CheckShiftSignature(BindingDiagnosticBag diagnostics)
{
// SPEC: A binary <<, >> or >>> operator must take two parameters, the first
// SPEC: of which must have type T or T?, the second of which can
// SPEC: have any type. The operator can return any type.
if (!MatchesContainingType(this.GetParameterType(0).StrippedType()))
{
// CS0546: The first operand of an overloaded shift operator must have the
// same type as the containing type
diagnostics.Add((IsAbstract || IsVirtual) ? ErrorCode.ERR_BadAbstractShiftOperatorSignature : ErrorCode.ERR_BadShiftOperatorSignature, this.GetFirstLocation());
}
else if (this.GetParameterType(1).StrippedType().SpecialType != SpecialType.System_Int32)
{
var location = this.GetFirstLocation();
Binder.CheckFeatureAvailability(location.SourceTree, MessageID.IDS_FeatureRelaxedShiftOperator, diagnostics, location);
}
CheckReturnIsNotVoid(diagnostics);
}
private void CheckBinarySignature(BindingDiagnosticBag diagnostics)
{
// SPEC: A binary nonshift operator must take two parameters, at least
// SPEC: one of which must have the type T or T?, and can return any type.
if (!MatchesContainingType(this.GetParameterType(0).StrippedType()) &&
!MatchesContainingType(this.GetParameterType(1).StrippedType()))
{
// CS0563: One of the parameters of a binary operator must be the containing type
diagnostics.Add((IsAbstract || IsVirtual) ? ErrorCode.ERR_BadAbstractBinaryOperatorSignature : ErrorCode.ERR_BadBinaryOperatorSignature, this.GetFirstLocation());
}
CheckReturnIsNotVoid(diagnostics);
}
private void CheckAbstractEqualitySignature(BindingDiagnosticBag diagnostics)
{
if (!IsSelfConstrainedTypeParameter(this.GetParameterType(0).StrippedType()) &&
!IsSelfConstrainedTypeParameter(this.GetParameterType(1).StrippedType()))
{
diagnostics.Add(ErrorCode.ERR_BadAbstractEqualityOperatorSignature, this.GetFirstLocation(), this.ContainingType);
}
CheckReturnIsNotVoid(diagnostics);
}
public sealed override string Name
{
get
{
return _name;
}
}
public sealed override bool IsExtensionMethod
{
get
{
return false;
}
}
public sealed override ImmutableArray<TypeParameterSymbol> TypeParameters
{
get { return ImmutableArray<TypeParameterSymbol>.Empty; }
}
public sealed override ImmutableArray<ImmutableArray<TypeWithAnnotations>> GetTypeParameterConstraintTypes()
=> ImmutableArray<ImmutableArray<TypeWithAnnotations>>.Empty;
public sealed override ImmutableArray<TypeParameterConstraintKind> GetTypeParameterConstraintKinds()
=> ImmutableArray<TypeParameterConstraintKind>.Empty;
protected sealed override void CheckConstraintsForExplicitInterfaceType(ConversionsBase conversions, BindingDiagnosticBag diagnostics)
{
if ((object)_explicitInterfaceType != null)
{
NameSyntax name;
switch (syntaxReferenceOpt.GetSyntax())
{
case OperatorDeclarationSyntax operatorDeclaration:
Debug.Assert(operatorDeclaration.ExplicitInterfaceSpecifier != null);
name = operatorDeclaration.ExplicitInterfaceSpecifier.Name;
break;
case ConversionOperatorDeclarationSyntax conversionDeclaration:
Debug.Assert(conversionDeclaration.ExplicitInterfaceSpecifier != null);
name = conversionDeclaration.ExplicitInterfaceSpecifier.Name;
break;
default:
throw ExceptionUtilities.Unreachable();
}
_explicitInterfaceType.CheckAllConstraints(DeclaringCompilation, conversions, new SourceLocation(name), diagnostics);
}
}
protected sealed override void PartialMethodChecks(BindingDiagnosticBag diagnostics)
{
}
}
}
|