private bool GetUserDefinedOperators(UnaryOperatorKind kind, BoundExpression operand, ArrayBuilder <UnaryOperatorAnalysisResult> results, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(operand != null); if ((object)operand.Type == null) { // If the operand has no type -- because it is a null reference or a lambda or a method group -- // there is no way we can determine what type to search for user-defined operators. return(false); } // Spec 7.3.5 Candidate user-defined operators // SPEC: Given a type T and an operation op(A) ... the set of candidate user-defined // SPEC: operators provided by T for op(A) is determined as follows: // SPEC: If T is a nullable type then T0 is its underlying type; otherwise T0 is T. // SPEC: For all operator declarations in T0 and all lifted forms of such operators, if // SPEC: at least one operator is applicable with respect to A then the set of candidate // SPEC: operators consists of all such applicable operators. Otherwise, if T0 is object // SPEC: then the set of candidate operators is empty. Otherwise, the set of candidate // SPEC: operators is the set provided by the direct base class of T0, or the effective // SPEC: base class of T0 if T0 is a type parameter. // https://github.com/dotnet/roslyn/issues/34451: The spec quote should be adjusted to cover operators from interfaces as well. // From https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md: // - We only even look for operator implementations in interfaces if one of the operands has a type that is an interface or // a type parameter with a non-empty effective base interface list. // - The applicable operators from classes / structs shadow those in interfaces.This matters for constrained type parameters: // the effective base class can shadow operators from effective base interfaces. // - If we find an applicable candidate in an interface, that candidate shadows all applicable operators in base interfaces: // we stop looking. TypeSymbol type0 = operand.Type.StrippedType(); TypeSymbol constrainedToTypeOpt = type0 as TypeParameterSymbol; // Searching for user-defined operators is expensive; let's take an early out if we can. if (OperatorFacts.DefinitelyHasNoUserDefinedOperators(type0)) { return(false); } string name = OperatorFacts.UnaryOperatorNameFromOperatorKind(kind); var operators = ArrayBuilder <UnaryOperatorSignature> .GetInstance(); bool hadApplicableCandidates = false; NamedTypeSymbol current = type0 as NamedTypeSymbol; if ((object)current == null) { current = type0.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } if ((object)current == null && type0.IsTypeParameter()) { current = ((TypeParameterSymbol)type0).EffectiveBaseClass(ref useSiteInfo); } for (; (object)current != null; current = current.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo)) { operators.Clear(); GetUserDefinedUnaryOperatorsFromType(constrainedToTypeOpt, current, kind, name, operators); results.Clear(); if (CandidateOperators(operators, operand, results, ref useSiteInfo)) { hadApplicableCandidates = true; break; } } // Look in base interfaces, or effective interfaces for type parameters if (!hadApplicableCandidates) { ImmutableArray <NamedTypeSymbol> interfaces = default; if (type0.IsInterfaceType()) { interfaces = type0.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } else if (type0.IsTypeParameter()) { interfaces = ((TypeParameterSymbol)type0).AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } if (!interfaces.IsDefaultOrEmpty) { var shadowedInterfaces = PooledHashSet <NamedTypeSymbol> .GetInstance(); var resultsFromInterface = ArrayBuilder <UnaryOperatorAnalysisResult> .GetInstance(); results.Clear(); foreach (NamedTypeSymbol @interface in interfaces) { if ([email protected]) { // this code could be reachable in error situations continue; } if (shadowedInterfaces.Contains(@interface)) { // this interface is "shadowed" by a derived interface continue; } operators.Clear(); resultsFromInterface.Clear(); GetUserDefinedUnaryOperatorsFromType(constrainedToTypeOpt, @interface, kind, name, operators); if (CandidateOperators(operators, operand, resultsFromInterface, ref useSiteInfo)) { hadApplicableCandidates = true; results.AddRange(resultsFromInterface); // this interface "shadows" all its base interfaces shadowedInterfaces.AddAll(@interface.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo)); } } shadowedInterfaces.Free(); resultsFromInterface.Free(); } } operators.Free(); return(hadApplicableCandidates); }
// Returns true if there were any applicable candidates. private bool GetUserDefinedOperators(UnaryOperatorKind kind, BoundExpression operand, ArrayBuilder <UnaryOperatorAnalysisResult> results, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(operand != null); if ((object)operand.Type == null) { // If the operand has no type -- because it is a null reference or a lambda or a method group -- // there is no way we can determine what type to search for user-defined operators. return(false); } // Spec 7.3.5 Candidate user-defined operators // SPEC: Given a type T and an operation op(A) ... the set of candidate user-defined // SPEC: operators provided by T for op(A) is determined as follows: // SPEC: If T is a nullable type then T0 is its underlying type; otherwise T0 is T. // SPEC: For all operator declarations in T0 and all lifted forms of such operators, if // SPEC: at least one operator is applicable with respect to A then the set of candidate // SPEC: operators consists of all such applicable operators. Otherwise, if T0 is object // SPEC: then the set of candidate operators is empty. Otherwise, the set of candidate // SPEC: operators is the set provided by the direct base class of T0, or the effective // SPEC: base class of T0 if T0 is a type parameter. TypeSymbol type0 = operand.Type.StrippedType(); // Searching for user-defined operators is expensive; let's take an early out if we can. if (OperatorFacts.DefinitelyHasNoUserDefinedOperators(type0)) { return(false); } string name = OperatorFacts.UnaryOperatorNameFromOperatorKind(kind); var operators = ArrayBuilder <UnaryOperatorSignature> .GetInstance(); bool hadApplicableCandidates = false; NamedTypeSymbol current = type0 as NamedTypeSymbol; if ((object)current == null) { current = type0.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics); } if ((object)current == null && type0.IsTypeParameter()) { current = ((TypeParameterSymbol)type0).EffectiveBaseClass(ref useSiteDiagnostics); } for (; (object)current != null; current = current.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics)) { operators.Clear(); GetUserDefinedUnaryOperatorsFromType(current, kind, name, operators); results.Clear(); if (CandidateOperators(operators, operand, results, ref useSiteDiagnostics)) { hadApplicableCandidates = true; break; } } operators.Free(); return(hadApplicableCandidates); }