/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs
2 010 строк
106 KB
Jan Jones
Unsafe evolution: extend compat mode to legacy callers (#83660)
26 июн 2026, 17:00
Не верифицирован
26 июн 2026, 17:00
8a40f56
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal delegate void ReportMismatchInReturnType<TArg>(BindingDiagnosticBag bag, MethodSymbol overriddenMethod, MethodSymbol overridingMethod, bool topLevel, TArg arg); internal delegate void ReportMismatchInParameterType<TArg>(BindingDiagnosticBag bag, MethodSymbol overriddenMethod, MethodSymbol overridingMethod, ParameterSymbol parameter, bool topLevel, TArg arg); internal partial class SourceMemberContainerTypeSymbol { /// <summary> /// In some circumstances (e.g. implicit implementation of an interface method by a non-virtual method in a /// base type from another assembly) it is necessary for the compiler to generate explicit implementations for /// some interface methods. They don't go in the symbol table, but if we are emitting, then we should /// generate code for them. /// </summary> internal SynthesizedExplicitImplementations GetSynthesizedExplicitImplementations( CancellationToken cancellationToken) { if (_lazySynthesizedExplicitImplementations is null) { var diagnostics = BindingDiagnosticBag.GetInstance(); try { cancellationToken.ThrowIfCancellationRequested(); CheckMembersAgainstBaseType(diagnostics, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); CheckAbstractClassImplementations(diagnostics); cancellationToken.ThrowIfCancellationRequested(); CheckInterfaceUnification(diagnostics); if (this.IsInterface) { cancellationToken.ThrowIfCancellationRequested(); this.CheckInterfaceVarianceSafety(diagnostics); } if (Interlocked.CompareExchange( ref _lazySynthesizedExplicitImplementations, ComputeInterfaceImplementations(diagnostics, cancellationToken), null) is null) { // Do not cancel from this point on. We've assigned the member, so we must add // the diagnostics. AddDeclarationDiagnostics(diagnostics); state.NotePartComplete(CompletionPart.SynthesizedExplicitImplementations); } } finally { diagnostics.Free(); } } return _lazySynthesizedExplicitImplementations; } internal sealed override IEnumerable<(MethodSymbol Body, MethodSymbol Implemented)> SynthesizedInterfaceMethodImpls() { SynthesizedExplicitImplementations synthesizedImplementations = GetSynthesizedExplicitImplementations(cancellationToken: default); foreach (var methodImpl in synthesizedImplementations.MethodImpls) { yield return methodImpl; } foreach (var forwardingMethod in synthesizedImplementations.ForwardingMethods) { yield return (forwardingMethod.ImplementingMethod, forwardingMethod.ExplicitInterfaceImplementations.Single()); } } private void CheckAbstractClassImplementations(BindingDiagnosticBag diagnostics) { NamedTypeSymbol baseType = this.BaseTypeNoUseSiteDiagnostics; if (this.IsAbstract || (object)baseType == null || !baseType.IsAbstract) { return; } // CONSIDER: We know that no-one will ask for NotOverriddenAbstractMembers again // (since this class is concrete), so we could just call the construction method // directly to avoid storing the result. foreach (var abstractMember in this.AbstractMembers) { // Dev10 reports failure to implement properties/events in terms of the accessors if (abstractMember.Kind == SymbolKind.Method && abstractMember is not SynthesizedRecordOrdinaryMethod) { diagnostics.Add(ErrorCode.ERR_UnimplementedAbstractMethod, this.GetFirstLocation(), this, abstractMember); } } } private SynthesizedExplicitImplementations ComputeInterfaceImplementations( BindingDiagnosticBag diagnostics, CancellationToken cancellationToken) { var forwardingMethods = ArrayBuilder<SynthesizedExplicitImplementationForwardingMethod>.GetInstance(); var methodImpls = ArrayBuilder<(MethodSymbol Body, MethodSymbol Implemented)>.GetInstance(); // NOTE: We can't iterate over this collection directly, since it is not ordered. Instead we // iterate over AllInterfaces and filter out the interfaces that are not in this set. This is // preferable to doing the DFS ourselves because both AllInterfaces and // InterfacesAndTheirBaseInterfaces are cached and used in multiple places. MultiDictionary<NamedTypeSymbol, NamedTypeSymbol> interfacesAndTheirBases = this.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics; foreach (var @interface in this.AllInterfacesNoUseSiteDiagnostics) { cancellationToken.ThrowIfCancellationRequested(); if (!interfacesAndTheirBases[@interface].Contains(@interface)) { continue; } HasBaseTypeDeclaringInterfaceResult? hasBaseClassDeclaringInterface = null; foreach (var interfaceMember in @interface.GetMembers()) { cancellationToken.ThrowIfCancellationRequested(); // Only require implementations for members that can be implemented in C#. SymbolKind interfaceMemberKind = interfaceMember.Kind; switch (interfaceMemberKind) { case SymbolKind.Method: case SymbolKind.Property: case SymbolKind.Event: if (!interfaceMember.IsImplementableInterfaceMember()) { continue; } break; default: continue; } SymbolAndDiagnostics implementingMemberAndDiagnostics; if (this.IsInterface) { MultiDictionary<Symbol, Symbol>.ValueSet explicitImpl = this.GetExplicitImplementationForInterfaceMember(interfaceMember); switch (explicitImpl.Count) { case 0: continue; // There is no requirement to implement anything in an interface case 1: implementingMemberAndDiagnostics = new SymbolAndDiagnostics(explicitImpl.Single(), ReadOnlyBindingDiagnostic<AssemblySymbol>.Empty); break; default: Diagnostic diag = new CSDiagnostic(new CSDiagnosticInfo(ErrorCode.ERR_DuplicateExplicitImpl, interfaceMember), this.GetFirstLocation()); implementingMemberAndDiagnostics = new SymbolAndDiagnostics(null, new ReadOnlyBindingDiagnostic<AssemblySymbol>(ImmutableArray.Create(diag), default)); break; } } else { implementingMemberAndDiagnostics = this.FindImplementationForInterfaceMemberInNonInterfaceWithDiagnostics(interfaceMember); } var implementingMember = implementingMemberAndDiagnostics.Symbol; var synthesizedImplementation = this.SynthesizeInterfaceMemberImplementation(implementingMemberAndDiagnostics, interfaceMember); bool wasImplementingMemberFound = (object)implementingMember != null; if (synthesizedImplementation.ForwardingMethod is SynthesizedExplicitImplementationForwardingMethod forwardingMethod) { if (forwardingMethod.IsVararg) { diagnostics.Add( ErrorCode.ERR_InterfaceImplementedImplicitlyByVariadic, GetImplicitImplementationDiagnosticLocation(interfaceMember, this, implementingMember), implementingMember, interfaceMember, this); } else { forwardingMethods.Add(forwardingMethod); } } if (synthesizedImplementation.MethodImpl is { } methodImpl) { Debug.Assert(methodImpl is { Body: not null, Implemented: not null }); methodImpls.Add(methodImpl); } if (wasImplementingMemberFound && interfaceMemberKind == SymbolKind.Event) { // NOTE: unlike dev11, we're including a related location for the implementing type, because // otherwise the only error location will be in the containing type of the implementing event // (i.e. no indication of which type's interface list is actually problematic). EventSymbol interfaceEvent = (EventSymbol)interfaceMember; EventSymbol implementingEvent = (EventSymbol)implementingMember; EventSymbol maybeWinRTEvent; EventSymbol maybeRegularEvent; if (interfaceEvent.IsWindowsRuntimeEvent) { maybeWinRTEvent = interfaceEvent; // Definitely WinRT. maybeRegularEvent = implementingEvent; // Maybe regular. } else { maybeWinRTEvent = implementingEvent; // Maybe WinRT. maybeRegularEvent = interfaceEvent; // Definitely regular. } if (interfaceEvent.IsWindowsRuntimeEvent != implementingEvent.IsWindowsRuntimeEvent) { // At this point (and not before), we know that maybeWinRTEvent is definitely a WinRT event and maybeRegularEvent is definitely a regular event. var args = new object[] { implementingEvent, interfaceEvent, maybeWinRTEvent, maybeRegularEvent }; var info = new CSDiagnosticInfo(ErrorCode.ERR_MixingWinRTEventWithRegular, args, ImmutableArray<Symbol>.Empty, ImmutableArray.Create<Location>(this.GetFirstLocation())); diagnostics.Add(info, implementingEvent.GetFirstLocation()); } } // Dev10: If a whole property is missing, report the property. If the property is present, but an accessor // is missing, report just the accessor. var associatedPropertyOrEvent = interfaceMemberKind == SymbolKind.Method ? ((MethodSymbol)interfaceMember).AssociatedSymbol : null; if ((object)associatedPropertyOrEvent == null || ReportAccessorOfInterfacePropertyOrEvent(associatedPropertyOrEvent) || (wasImplementingMemberFound && !implementingMember.IsAccessor())) { //we're here because //(a) the interface member is not an accessor, or //(b) the interface member is an accessor of an interesting (see ReportAccessorOfInterfacePropertyOrEvent) property or event, or //(c) the implementing member exists and is not an accessor. bool reportedAnError = false; if (implementingMemberAndDiagnostics.Diagnostics.Diagnostics.Any()) { diagnostics.AddRange(implementingMemberAndDiagnostics.Diagnostics); reportedAnError = implementingMemberAndDiagnostics.Diagnostics.Diagnostics.Any(static d => d.Severity == DiagnosticSeverity.Error); } if (!reportedAnError) { if (!wasImplementingMemberFound || (!implementingMember.ContainingType.Equals(this, TypeCompareKind.ConsiderEverything) && implementingMember.GetExplicitInterfaceImplementations().Contains(interfaceMember, ExplicitInterfaceImplementationTargetMemberEqualityComparer.Instance))) { // NOTE: An alternative approach would be to keep track of this while searching for the implementing member. // In some cases, we might even be able to stop looking and just accept that a base type has things covered // (though we'd have to be careful about losing diagnostics and we might produce fewer bridge methods). // However, this approach has the advantage that there is no cost unless we encounter a base type that // claims to implement an interface, but we can't figure out how (i.e. free in nearly all cases). hasBaseClassDeclaringInterface = hasBaseClassDeclaringInterface ?? HasBaseClassDeclaringInterface(@interface); HasBaseTypeDeclaringInterfaceResult matchResult = hasBaseClassDeclaringInterface.GetValueOrDefault(); if (matchResult != HasBaseTypeDeclaringInterfaceResult.ExactMatch && wasImplementingMemberFound && implementingMember.ContainingType.IsInterface) { HasBaseInterfaceDeclaringInterface(implementingMember.ContainingType, @interface, ref matchResult); } // If a base type from metadata declares that it implements the interface, we'll just trust it. // (See fFoundImport in SymbolPreparer::CheckInterfaceMethodImplementation.) switch (matchResult) { case HasBaseTypeDeclaringInterfaceResult.NoMatch: { // CONSIDER: Dev10 does not emit this diagnostic for interface properties if the // derived type attempts to implement an accessor directly as a method. // Suppress for bogus properties and events and for indexed properties. if (!interfaceMember.MustCallMethodsDirectly() && !interfaceMember.IsIndexedProperty()) { DiagnosticInfo useSiteDiagnostic = interfaceMember.GetUseSiteInfo().DiagnosticInfo; if (useSiteDiagnostic != null && useSiteDiagnostic.DefaultSeverity == DiagnosticSeverity.Error) { diagnostics.Add(useSiteDiagnostic, GetImplementsLocationOrFallback(@interface)); } else { diagnostics.Add(ErrorCode.ERR_UnimplementedInterfaceMember, GetImplementsLocationOrFallback(@interface), this, interfaceMember); } } } break; case HasBaseTypeDeclaringInterfaceResult.ExactMatch: break; case HasBaseTypeDeclaringInterfaceResult.IgnoringNullableMatch: diagnostics.Add(ErrorCode.WRN_NullabilityMismatchInInterfaceImplementedByBase, GetImplementsLocationOrFallback(@interface), this, interfaceMember); break; default: throw ExceptionUtilities.UnexpectedValue(matchResult); } } if (wasImplementingMemberFound && interfaceMemberKind == SymbolKind.Method) { // Don't report use site errors on properties - we'll report them on each of their accessors. // Don't report use site errors for implementations in other types unless // a synthesized implementation is needed that invokes the base method. // We can do so only if there are no use-site errors. if (synthesizedImplementation.ForwardingMethod is not null || TypeSymbol.Equals(implementingMember.ContainingType, this, TypeCompareKind.ConsiderEverything2)) { UseSiteInfo<AssemblySymbol> useSiteInfo = interfaceMember.GetUseSiteInfo(); // Don't report a use site error with a location in another compilation. For example, // if the error is that a base type in another assembly implemented an interface member // on our behalf and the use site error is that the current assembly does not reference // some required assembly, then we want to report the error in the current assembly - // not in the implementing assembly. Location location = implementingMember.IsFromCompilation(this.DeclaringCompilation) ? implementingMember.GetFirstLocation() : this.GetFirstLocation(); diagnostics.Add(useSiteInfo, location); } } } } } } return SynthesizedExplicitImplementations.Create(forwardingMethods.ToImmutableAndFree(), methodImpls.ToImmutableAndFree()); } protected abstract Location GetCorrespondingBaseListLocation(NamedTypeSymbol @base); #nullable enable private Location GetImplementsLocationOrFallback(NamedTypeSymbol implementedInterface) { return GetImplementsLocation(implementedInterface) ?? this.GetFirstLocation(); } internal Location? GetImplementsLocation(NamedTypeSymbol implementedInterface) #nullable disable { // We ideally want to identify the interface location in the base list with an exact match but // will fall back and use the first derived interface if exact interface is not present. // this is the similar logic as the VB implementation. Debug.Assert(this.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics[implementedInterface].Contains(implementedInterface)); var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; NamedTypeSymbol directInterface = null; foreach (var iface in this.InterfacesNoUseSiteDiagnostics()) { if (TypeSymbol.Equals(iface, implementedInterface, TypeCompareKind.ConsiderEverything2)) { directInterface = iface; break; } else if ((object)directInterface == null && iface.ImplementsInterface(implementedInterface, ref discardedUseSiteInfo)) { directInterface = iface; } } Debug.Assert((object)directInterface != null); return GetCorrespondingBaseListLocation(directInterface); } /// <summary> /// It's not interesting to report diagnostics on implementation of interface accessors /// if the corresponding events or properties are not implemented (i.e. we want to suppress /// cascading diagnostics). /// Caveat: Indexed property accessors are always interesting. /// Caveat: It's also uninteresting if a WinRT event is implemented by a non-WinRT event, /// or vice versa. /// </summary> private bool ReportAccessorOfInterfacePropertyOrEvent(Symbol interfacePropertyOrEvent) { Debug.Assert((object)interfacePropertyOrEvent != null); // Accessors of indexed properties are always interesting. if (interfacePropertyOrEvent.IsIndexedProperty()) { return true; } Symbol implementingPropertyOrEvent; if (this.IsInterface) { MultiDictionary<Symbol, Symbol>.ValueSet explicitImpl = this.GetExplicitImplementationForInterfaceMember(interfacePropertyOrEvent); switch (explicitImpl.Count) { case 0: return true; case 1: implementingPropertyOrEvent = explicitImpl.Single(); break; default: implementingPropertyOrEvent = null; break; } } else { implementingPropertyOrEvent = this.FindImplementationForInterfaceMemberInNonInterface(interfacePropertyOrEvent); } // If the property or event wasn't implemented, then we'd prefer to report diagnostics about that. if ((object)implementingPropertyOrEvent == null) { return false; } // If the property or event was an event and was implemented, but the WinRT-ness didn't agree, // then we'd prefer to report diagnostics about that. if (interfacePropertyOrEvent.Kind == SymbolKind.Event && implementingPropertyOrEvent.Kind == SymbolKind.Event && ((EventSymbol)interfacePropertyOrEvent).IsWindowsRuntimeEvent != ((EventSymbol)implementingPropertyOrEvent).IsWindowsRuntimeEvent) { return false; } return true; } private enum HasBaseTypeDeclaringInterfaceResult { NoMatch, IgnoringNullableMatch, ExactMatch, } private HasBaseTypeDeclaringInterfaceResult HasBaseClassDeclaringInterface(NamedTypeSymbol @interface) { HasBaseTypeDeclaringInterfaceResult result = HasBaseTypeDeclaringInterfaceResult.NoMatch; for (NamedTypeSymbol currType = this.BaseTypeNoUseSiteDiagnostics; (object)currType != null; currType = currType.BaseTypeNoUseSiteDiagnostics) { if (DeclaresBaseInterface(currType, @interface, ref result)) { break; } } return result; } private static bool DeclaresBaseInterface(NamedTypeSymbol currType, NamedTypeSymbol @interface, ref HasBaseTypeDeclaringInterfaceResult result) { MultiDictionary<NamedTypeSymbol, NamedTypeSymbol>.ValueSet set = currType.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics[@interface]; if (set.Count != 0) { if (set.Contains(@interface)) { result = HasBaseTypeDeclaringInterfaceResult.ExactMatch; return true; } else if (result == HasBaseTypeDeclaringInterfaceResult.NoMatch && set.Contains(@interface, Symbols.SymbolEqualityComparer.IgnoringNullable)) { result = HasBaseTypeDeclaringInterfaceResult.IgnoringNullableMatch; } } return false; } private void HasBaseInterfaceDeclaringInterface(NamedTypeSymbol baseInterface, NamedTypeSymbol @interface, ref HasBaseTypeDeclaringInterfaceResult matchResult) { // Let's check for the trivial case first if (DeclaresBaseInterface(baseInterface, @interface, ref matchResult)) { return; } foreach (var interfaceType in this.AllInterfacesNoUseSiteDiagnostics) { if ((object)interfaceType == baseInterface) { continue; } if (interfaceType.Equals(baseInterface, TypeCompareKind.CLRSignatureCompareOptions) && DeclaresBaseInterface(interfaceType, @interface, ref matchResult)) { return; } } } private void CheckMembersAgainstBaseType( BindingDiagnosticBag diagnostics, CancellationToken cancellationToken) { var baseType = this.BaseTypeNoUseSiteDiagnostics; if (baseType?.IsErrorType() == true) { // Avoid cascading diagnostics return; } // Check if the base type is a valid record base first. If it's not a record, then ERR_BadRecordBase // will have already been reported, and we don't need to report cascaded warnings and errors for the // members in this type. var hasInvalidRecordInheritance = this.IsRecord && !baseType.IsObjectType() && !baseType.IsRecord; switch (this.TypeKind) { // These checks don't make sense for enums, delegates or extensions: case TypeKind.Enum: case TypeKind.Delegate: case TypeKind.Extension: return; case TypeKind.Class: case TypeKind.Struct: case TypeKind.Interface: case TypeKind.Submission: // we have to check that "override" is not used break; default: throw ExceptionUtilities.UnexpectedValue(this.TypeKind); } foreach (var member in this.GetMembersUnordered()) { cancellationToken.ThrowIfCancellationRequested(); // If we have invalid record inheritance (a record deriving from a non-record), skip checking the // synthesized members for proper new/override usage as we will end up just reporting cascaded // errors that all stem from the same root cause. if (hasInvalidRecordInheritance && member is SynthesizedRecordBaseEquals or SynthesizedRecordEqualityContractProperty or SynthesizedRecordPrintMembers) { continue; } bool suppressAccessors; switch (member.Kind) { case SymbolKind.Method: var method = (MethodSymbol)member; if (MethodSymbol.CanOverrideOrHide(method.MethodKind) && !method.IsAccessor()) { if (member.IsOverride) { CheckOverrideMember(method, method.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } else { var sourceMethod = method as SourceMemberMethodSymbol; if ((object)sourceMethod != null) // skip submission initializer { var isNew = sourceMethod.IsNew; CheckNonOverrideMember(method, isNew, method.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } } } else if (method.MethodKind == MethodKind.Destructor) { // NOTE: Normal finalize methods CanOverrideOrHide and will go through the normal code path. // First is fine, since there should only be one, since there are no parameters. MethodSymbol overridden = method.GetFirstRuntimeOverriddenMethodIgnoringNewSlot(out _); // NOTE: Dev11 doesn't expose symbols, so it can treat destructors as override and let them go through the normal // checks. Roslyn can't, since the language says they are not virtual/override and that's what we need to expose // in the symbol model. Having said that, Dev11 doesn't seem to produce override errors other than this one // (see SymbolPreparer::prepareOperator). if ((object)overridden != null && overridden.IsMetadataFinal) { diagnostics.Add(ErrorCode.ERR_CantOverrideSealed, method.GetFirstLocation(), method, overridden); } } break; case SymbolKind.Property: var property = (PropertySymbol)member; var getMethod = property.GetMethod; var setMethod = property.SetMethod; // Handle the accessors here, instead of in the loop, so that we can ensure that // they're checked *after* the corresponding property. if (member.IsOverride) { CheckOverrideMember(property, property.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); if (!suppressAccessors) { if ((object)getMethod != null) { CheckOverrideMember(getMethod, getMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } if ((object)setMethod != null) { CheckOverrideMember(setMethod, setMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } } } else if (property is SourcePropertySymbolBase sourceProperty) { var isNewProperty = sourceProperty.IsNew; CheckNonOverrideMember(property, isNewProperty, property.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); if (!suppressAccessors) { if ((object)getMethod != null) { CheckNonOverrideMember(getMethod, isNewProperty, getMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } if ((object)setMethod != null) { CheckNonOverrideMember(setMethod, isNewProperty, setMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } } } break; case SymbolKind.Event: var @event = (EventSymbol)member; var addMethod = @event.AddMethod; var removeMethod = @event.RemoveMethod; // Handle the accessors here, instead of in the loop, so that we can ensure that // they're checked *after* the corresponding event. if (member.IsOverride) { CheckOverrideMember(@event, @event.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); if (!suppressAccessors) { if ((object)addMethod != null) { CheckOverrideMember(addMethod, addMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } if ((object)removeMethod != null) { CheckOverrideMember(removeMethod, removeMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } } } else { var isNewEvent = ((SourceEventSymbol)@event).IsNew; CheckNonOverrideMember(@event, isNewEvent, @event.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); if (!suppressAccessors) { if ((object)addMethod != null) { CheckNonOverrideMember(addMethod, isNewEvent, addMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } if ((object)removeMethod != null) { CheckNonOverrideMember(removeMethod, isNewEvent, removeMethod.OverriddenOrHiddenMembers, diagnostics, out suppressAccessors); } } } break; case SymbolKind.Field: var sourceField = member as SourceFieldSymbol; var isNewField = (object)sourceField != null && sourceField.IsNew; // We don't want to report diagnostics for field-like event backing fields (redundant), // but that shouldn't be an issue since they shouldn't be in the member list. Debug.Assert((object)sourceField == null || (object)sourceField.AssociatedSymbol == null || sourceField.AssociatedSymbol.Kind != SymbolKind.Event); CheckNewModifier(member, isNewField, diagnostics); break; case SymbolKind.NamedType: CheckNewModifier(member, ((SourceMemberContainerTypeSymbol)member).IsNew, diagnostics); break; } } } private void CheckNewModifier(Symbol symbol, bool isNew, BindingDiagnosticBag diagnostics) { Debug.Assert(symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.NamedType); // Do not give warnings about missing 'new' modifier for implicitly declared members, // e.g. backing fields for auto-properties if (symbol.IsImplicitlyDeclared) { return; } if (symbol.ContainingType.IsInterface) { CheckNonOverrideMember(symbol, isNew, OverriddenOrHiddenMembersHelpers.MakeInterfaceOverriddenOrHiddenMembers(symbol, memberIsFromSomeCompilation: true), diagnostics, out _); return; } // for error cases if ((object)this.BaseTypeNoUseSiteDiagnostics == null) { return; } int symbolArity = symbol.GetMemberArity(); Location symbolLocation = symbol.TryGetFirstLocation(); bool unused = false; NamedTypeSymbol currType = this.BaseTypeNoUseSiteDiagnostics; while ((object)currType != null) { foreach (var hiddenMember in currType.GetMembers(symbol.Name)) { if (hiddenMember.Kind == SymbolKind.Method && !((MethodSymbol)hiddenMember).CanBeHiddenByMember(symbol)) { continue; } var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly); bool isAccessible = AccessCheck.IsSymbolAccessible(hiddenMember, this, ref useSiteInfo); diagnostics.Add(symbolLocation, useSiteInfo); if (isAccessible && hiddenMember.GetMemberArity() == symbolArity) { if (!isNew) { diagnostics.Add(ErrorCode.WRN_NewRequired, symbolLocation, symbol, hiddenMember); } AddHidingAbstractDiagnostic(symbol, symbolLocation, hiddenMember, diagnostics, ref unused); if (hiddenMember.IsRequired()) { // Required member '{0}' cannot be hidden by '{1}'. diagnostics.Add(ErrorCode.ERR_RequiredMemberCannotBeHidden, symbolLocation, hiddenMember, symbol); } return; } } currType = currType.BaseTypeNoUseSiteDiagnostics; } if (isNew) { diagnostics.Add(ErrorCode.WRN_NewNotRequired, symbolLocation, symbol); } } private void CheckOverrideMember( Symbol overridingMember, OverriddenOrHiddenMembersResult overriddenOrHiddenMembers, BindingDiagnosticBag diagnostics, out bool suppressAccessors) { Debug.Assert((object)overridingMember != null); Debug.Assert(overriddenOrHiddenMembers != null); suppressAccessors = false; var overridingMemberIsMethod = overridingMember.Kind == SymbolKind.Method; var overridingMemberIsProperty = overridingMember.Kind == SymbolKind.Property; var overridingMemberIsEvent = overridingMember.Kind == SymbolKind.Event; Debug.Assert(overridingMemberIsMethod ^ overridingMemberIsProperty ^ overridingMemberIsEvent); var overridingMemberLocation = overridingMember.GetFirstLocation(); var overriddenMembers = overriddenOrHiddenMembers.OverriddenMembers; Debug.Assert(!overriddenMembers.IsDefault); if (overriddenMembers.Length == 0) { var hiddenMembers = overriddenOrHiddenMembers.HiddenMembers; Debug.Assert(!hiddenMembers.IsDefault); if (hiddenMembers.Any()) { ErrorCode errorCode = overridingMemberIsMethod ? ErrorCode.ERR_CantOverrideNonFunction : overridingMemberIsProperty ? ErrorCode.ERR_CantOverrideNonProperty : ErrorCode.ERR_CantOverrideNonEvent; diagnostics.Add(errorCode, overridingMemberLocation, overridingMember, hiddenMembers[0]); } else { Symbol associatedPropertyOrEvent = null; if (overridingMemberIsMethod) { associatedPropertyOrEvent = ((MethodSymbol)overridingMember).AssociatedSymbol; } if ((object)associatedPropertyOrEvent == null) { bool suppressError = false; if (overridingMemberIsMethod || overridingMember.IsIndexer()) { var parameterTypes = overridingMemberIsMethod ? ((MethodSymbol)overridingMember).ParameterTypesWithAnnotations : ((PropertySymbol)overridingMember).ParameterTypesWithAnnotations; foreach (var parameterType in parameterTypes) { if (IsOrContainsErrorType(parameterType.Type)) { suppressError = true; // The parameter type must be fixed before the override can be found, so suppress error break; } } } if (!suppressError) { diagnostics.Add(ErrorCode.ERR_OverrideNotExpected, overridingMemberLocation, overridingMember); } } else if (associatedPropertyOrEvent.Kind == SymbolKind.Property) //no specific errors for event accessors { PropertySymbol associatedProperty = (PropertySymbol)associatedPropertyOrEvent; PropertySymbol overriddenProperty = associatedProperty.OverriddenProperty; if ((object)overriddenProperty == null) { //skip remaining checks } else if (associatedProperty.GetMethod == overridingMember && (object)overriddenProperty.GetMethod == null) { diagnostics.Add(ErrorCode.ERR_NoGetToOverride, overridingMemberLocation, overridingMember, overriddenProperty); } else if (associatedProperty.SetMethod == overridingMember && (object)overriddenProperty.SetMethod == null) { diagnostics.Add(ErrorCode.ERR_NoSetToOverride, overridingMemberLocation, overridingMember, overriddenProperty); } else { diagnostics.Add(ErrorCode.ERR_OverrideNotExpected, overridingMemberLocation, overridingMember); } } } } else { NamedTypeSymbol overridingType = overridingMember.ContainingType; if (overriddenMembers.Length > 1) { diagnostics.Add(ErrorCode.ERR_AmbigOverride, overridingMemberLocation, overriddenMembers[0].OriginalDefinition, overriddenMembers[1].OriginalDefinition, overridingType); suppressAccessors = true; } else { checkSingleOverriddenMember(overridingMember, overriddenMembers[0], diagnostics, ref suppressAccessors); } } // Both `ref` and `out` parameters (and `in` too) are implemented as references and are not distinguished by the runtime // when resolving overrides. Similarly, distinctions between types that would map together because of generic substitution // in the derived type where the override appears are the same from the runtime's point of view. In these cases we will // need to produce a methodimpl to disambiguate. See the call to `RequiresExplicitOverride` below. It produces a boolean // `warnAmbiguous` if the methodimpl could be misinterpreted due to a bug in the runtime // (https://github.com/dotnet/runtime/issues/38119) in which case we produce a warning regarding that ambiguity. // See https://github.com/dotnet/roslyn/issues/45453 for details. if (!this.ContainingAssembly.RuntimeSupportsCovariantReturnsOfClasses && overridingMember is MethodSymbol overridingMethod) { overridingMethod.RequiresExplicitOverride(out bool warnAmbiguous); if (warnAmbiguous) { var ambiguousMethod = overridingMethod.OverriddenMethod; diagnostics.Add(ErrorCode.WRN_MultipleRuntimeOverrideMatches, ambiguousMethod.GetFirstLocation(), ambiguousMethod, overridingMember); suppressAccessors = true; } } return; void checkSingleOverriddenMember(Symbol overridingMember, Symbol overriddenMember, BindingDiagnosticBag diagnostics, ref bool suppressAccessors) { var overridingMemberLocation = overridingMember.GetFirstLocation(); var overridingMemberIsMethod = overridingMember.Kind == SymbolKind.Method; var overridingMemberIsProperty = overridingMember.Kind == SymbolKind.Property; var overridingMemberIsEvent = overridingMember.Kind == SymbolKind.Event; var overridingType = overridingMember.ContainingType; //otherwise, it would have been excluded during lookup #if DEBUG { var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; Debug.Assert(AccessCheck.IsSymbolAccessible(overriddenMember, overridingType, ref discardedUseSiteInfo)); } #endif Debug.Assert(overriddenMember.Kind == overridingMember.Kind); if (overriddenMember.MustCallMethodsDirectly()) { diagnostics.Add(ErrorCode.ERR_CantOverrideBogusMethod, overridingMemberLocation, overridingMember, overriddenMember); suppressAccessors = true; } else if (!overriddenMember.IsVirtual && !overriddenMember.IsAbstract && !overriddenMember.IsOverride && !(overridingMemberIsMethod && ((MethodSymbol)overriddenMember).MethodKind == MethodKind.Destructor)) //destructors are metadata virtual { // CONSIDER: To match Dev10, skip the error for properties, and don't suppressAccessors diagnostics.Add(ErrorCode.ERR_CantOverrideNonVirtual, overridingMemberLocation, overridingMember, overriddenMember); suppressAccessors = true; } else if (overriddenMember.IsSealed) { // CONSIDER: To match Dev10, skip the error for properties, and don't suppressAccessors diagnostics.Add(ErrorCode.ERR_CantOverrideSealed, overridingMemberLocation, overridingMember, overriddenMember); suppressAccessors = true; } else if (!OverrideHasCorrectAccessibility(overriddenMember, overridingMember)) { var accessibility = SyntaxFacts.GetText(overriddenMember.DeclaredAccessibility); diagnostics.Add(ErrorCode.ERR_CantChangeAccessOnOverride, overridingMemberLocation, overridingMember, accessibility, overriddenMember); suppressAccessors = true; } else if (overridingMember.ContainsTupleNames() && MemberSignatureComparer.ConsideringTupleNamesCreatesDifference(overridingMember, overriddenMember)) { // it is ok to override with no tuple names, for compatibility with C# 6, but otherwise names should match diagnostics.Add(ErrorCode.ERR_CantChangeTupleNamesOnOverride, overridingMemberLocation, overridingMember, overriddenMember); } else if (overriddenMember is PropertySymbol { IsRequired: true } && overridingMember is PropertySymbol { IsRequired: false }) { // '{0}' must be required because it overrides required member '{1}' diagnostics.Add(ErrorCode.ERR_OverrideMustHaveRequired, overridingMemberLocation, overridingMember, overriddenMember); } else if (overriddenMember is MethodSymbol overridden && overridden.IsOperator() != ((MethodSymbol)overridingMember).IsOperator()) { diagnostics.Add(ErrorCode.ERR_OperatorMismatchOnOverride, overridingMemberLocation, overridingMember, overriddenMember); } else { // As in dev11, we don't compare obsoleteness to the immediately-overridden member, // but to the least-overridden member. var leastOverriddenMember = overriddenMember.GetLeastOverriddenMember(this); overridingMember.ForceCompleteObsoleteAttribute(); leastOverriddenMember.ForceCompleteObsoleteAttribute(); Debug.Assert(overridingMember.ObsoleteState != ThreeState.Unknown); Debug.Assert(leastOverriddenMember.ObsoleteState != ThreeState.Unknown); bool overridingMemberIsObsolete = overridingMember.ObsoleteState == ThreeState.True; bool leastOverriddenMemberIsObsolete = leastOverriddenMember.ObsoleteState == ThreeState.True; if (overridingMemberIsObsolete != leastOverriddenMemberIsObsolete) { ErrorCode code = overridingMemberIsObsolete ? ErrorCode.WRN_ObsoleteOverridingNonObsolete : ErrorCode.WRN_NonObsoleteOverridingObsolete; diagnostics.Add(code, overridingMemberLocation, overridingMember, leastOverriddenMember); } if (overridingMemberIsProperty) { checkOverriddenProperty((PropertySymbol)overridingMember, (PropertySymbol)overriddenMember, diagnostics, ref suppressAccessors); } else if (overridingMemberIsEvent) { EventSymbol overridingEvent = (EventSymbol)overridingMember; EventSymbol overriddenEvent = (EventSymbol)overriddenMember; TypeWithAnnotations overridingMemberType = overridingEvent.TypeWithAnnotations; TypeWithAnnotations overriddenMemberType = overriddenEvent.TypeWithAnnotations; // Ignore custom modifiers because this diagnostic is based on the C# semantics. if (!overridingMemberType.Equals(overriddenMemberType, TypeCompareKind.AllIgnoreOptions)) { // if the type is or contains an error type, the type must be fixed before the override can be found, so suppress error if (!IsOrContainsErrorType(overridingMemberType.Type)) { diagnostics.Add(ErrorCode.ERR_CantChangeTypeOnOverride, overridingMemberLocation, overridingMember, overriddenMember, overriddenMemberType.Type); } suppressAccessors = true; //we get really unhelpful errors from the accessor if the type is mismatched } else { CheckValidNullableEventOverride(overridingEvent.DeclaringCompilation, overriddenEvent, overridingEvent, diagnostics, (diagnostics, overriddenEvent, overridingEvent, location) => diagnostics.Add(ErrorCode.WRN_NullabilityMismatchInTypeOnOverride, location), overridingMemberLocation); CheckCallerUnsafeMismatch( implementedMember: null, overridingEvent, ErrorCode.ERR_CallerUnsafeOverridingSafe, overridingMemberLocation, static l => l, diagnostics); } } else { Debug.Assert(overridingMemberIsMethod); var overridingMethod = (MethodSymbol)overridingMember; var overriddenMethod = (MethodSymbol)overriddenMember; if (overridingMethod.IsGenericMethod) { overriddenMethod = overriddenMethod.Construct(TypeMap.TypeParametersAsTypeSymbolsWithIgnoredAnnotations(overridingMethod.TypeParameters)); } // Check for mismatched byref returns and return type. Ignore custom modifiers, because this diagnostic is based on the C# semantics. if (overridingMethod.RefKind != overriddenMethod.RefKind) { diagnostics.Add(ErrorCode.ERR_CantChangeRefReturnOnOverride, overridingMemberLocation, overridingMember, overriddenMember); } else if (!IsValidOverrideReturnType(overridingMethod, overridingMethod.ReturnTypeWithAnnotations, overriddenMethod.ReturnTypeWithAnnotations, diagnostics)) { // if the Return type is or contains an error type, the return type must be fixed before the override can be found, so suppress error if (!IsOrContainsErrorType(overridingMethod.ReturnType)) { // If the return type would be a valid covariant return, suggest using covariant return feature. var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; if (DeclaringCompilation.Conversions.HasIdentityOrImplicitReferenceConversion(overridingMethod.ReturnTypeWithAnnotations.Type, overriddenMethod.ReturnTypeWithAnnotations.Type, ref discardedUseSiteInfo)) { if (!overridingMethod.ContainingAssembly.RuntimeSupportsCovariantReturnsOfClasses) { diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportCovariantReturnsOfClasses, overridingMemberLocation, overridingMember, overriddenMember, overriddenMethod.ReturnType); } else if (MessageID.IDS_FeatureCovariantReturnsForOverrides.GetFeatureAvailabilityDiagnosticInfo(this.DeclaringCompilation) is { } diagnosticInfo) { diagnostics.Add(diagnosticInfo, overridingMemberLocation); } else { throw ExceptionUtilities.Unreachable(); } } else { // error CS0508: return type must be 'C<V>' to match overridden member 'M<T>()' diagnostics.Add(ErrorCode.ERR_CantChangeReturnTypeOnOverride, overridingMemberLocation, overridingMember, overriddenMember, overriddenMethod.ReturnType); } } } else if (overriddenMethod.IsRuntimeFinalizer()) { diagnostics.Add(ErrorCode.ERR_OverrideFinalizeDeprecated, overridingMemberLocation); } else if (!overridingMethod.IsAccessor()) { // Accessors will have already been checked above checkValidMethodOverride( overridingMemberLocation, overriddenMethod, overridingMethod, diagnostics); } } // NOTE: this error may be redundant (if an error has already been reported // for the return type or parameter type in question), but the scenario is // too rare to justify complicated checks. if (Binder.ReportUseSite(overriddenMember, diagnostics, overridingMember.GetFirstLocation())) { suppressAccessors = true; } } void checkOverriddenProperty(PropertySymbol overridingProperty, PropertySymbol overriddenProperty, BindingDiagnosticBag diagnostics, ref bool suppressAccessors) { var overridingMemberLocation = overridingProperty.GetFirstLocation(); var overridingType = overridingProperty.ContainingType; TypeWithAnnotations overridingMemberType = overridingProperty.TypeWithAnnotations; TypeWithAnnotations overriddenMemberType = overriddenProperty.TypeWithAnnotations; // Check for mismatched byref returns and return type. Ignore custom modifiers, because this diagnostic is based on the C# semantics. if (overridingProperty.RefKind != overriddenProperty.RefKind) { diagnostics.Add(ErrorCode.ERR_CantChangeRefReturnOnOverride, overridingMemberLocation, overridingProperty, overriddenProperty); suppressAccessors = true; //we get really unhelpful errors from the accessor if the ref kind is mismatched } else if (overridingProperty.SetMethod is null ? !IsValidOverrideReturnType(overridingProperty, overridingMemberType, overriddenMemberType, diagnostics) : !overridingMemberType.Equals(overriddenMemberType, TypeCompareKind.AllIgnoreOptions)) { // if the type is or contains an error type, the type must be fixed before the override can be found, so suppress error if (!IsOrContainsErrorType(overridingMemberType.Type)) { // If the type would be a valid covariant return, suggest using covariant return feature. var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; if (overridingProperty.SetMethod is null && DeclaringCompilation.Conversions.HasIdentityOrImplicitReferenceConversion(overridingMemberType.Type, overriddenMemberType.Type, ref discardedUseSiteInfo)) { if (!overridingProperty.ContainingAssembly.RuntimeSupportsCovariantReturnsOfClasses) { diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportCovariantPropertiesOfClasses, overridingMemberLocation, overridingMember, overriddenMember, overriddenMemberType.Type); } else { var diagnosticInfo = MessageID.IDS_FeatureCovariantReturnsForOverrides.GetFeatureAvailabilityDiagnosticInfo(this.DeclaringCompilation); Debug.Assert(diagnosticInfo is { }); diagnostics.Add(diagnosticInfo, overridingMemberLocation); } } else { // error CS1715: 'Derived.M': type must be 'object' to match overridden member 'Base.M' diagnostics.Add(ErrorCode.ERR_CantChangeTypeOnOverride, overridingMemberLocation, overridingMember, overriddenMember, overriddenMemberType.Type); // https://github.com/dotnet/roslyn/issues/44207 when overriddenMemberType.Type is an inheritable reference type and the covariant return // feature is enabled, and the platform supports it, and there is no setter, we can say it has to be 'object' **or a derived type**. // That would probably be a new error code. } } suppressAccessors = true; //we get really unhelpful errors from the accessor if the type is mismatched } else { if (overridingProperty.GetMethod is object) { MethodSymbol overriddenGetMethod = overriddenProperty.GetOwnOrInheritedGetMethod(); checkValidMethodOverride( overridingProperty.GetMethod.GetFirstLocation(), overriddenGetMethod, overridingProperty.GetMethod, diagnostics); } if (overridingProperty.SetMethod is object) { var ownOrInheritedOverriddenSetMethod = overriddenProperty.GetOwnOrInheritedSetMethod(); checkValidMethodOverride( overridingProperty.SetMethod.GetFirstLocation(), ownOrInheritedOverriddenSetMethod, overridingProperty.SetMethod, diagnostics); if (ownOrInheritedOverriddenSetMethod is object && overridingProperty.SetMethod.IsInitOnly != ownOrInheritedOverriddenSetMethod.IsInitOnly) { diagnostics.Add(ErrorCode.ERR_CantChangeInitOnlyOnOverride, overridingMemberLocation, overridingProperty, overriddenProperty); } } } // If the overriding property is sealed, then the overridden accessors cannot be inaccessible, since we // have to override them to make them sealed in metadata. // CONSIDER: It might be nice if this had its own error code(s) since it's an implementation restriction, // rather than a language restriction as above. if (overridingProperty.IsSealed) { MethodSymbol ownOrInheritedGetMethod = overridingProperty.GetOwnOrInheritedGetMethod(); var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, overridingProperty.ContainingAssembly); if (overridingProperty.GetMethod != ownOrInheritedGetMethod && !AccessCheck.IsSymbolAccessible(ownOrInheritedGetMethod, overridingType, ref useSiteInfo)) { diagnostics.Add(ErrorCode.ERR_NoGetToOverride, overridingMemberLocation, overridingProperty, overriddenProperty); } MethodSymbol ownOrInheritedSetMethod = overridingProperty.GetOwnOrInheritedSetMethod(); if (overridingProperty.SetMethod != ownOrInheritedSetMethod && !AccessCheck.IsSymbolAccessible(ownOrInheritedSetMethod, overridingType, ref useSiteInfo)) { diagnostics.Add(ErrorCode.ERR_NoSetToOverride, overridingMemberLocation, overridingProperty, overriddenProperty); } diagnostics.Add(overridingMemberLocation, useSiteInfo); } } } static void checkValidMethodOverride( Location overridingMemberLocation, MethodSymbol overriddenMethod, MethodSymbol overridingMethod, BindingDiagnosticBag diagnostics) { if (RequiresValidScopedOverrideForRefSafety(overriddenMethod, overridingMethod.TryGetThisParameter(out var thisParam) ? thisParam : null)) { CheckValidScopedOverride( overriddenMethod, overridingMethod, diagnostics, static (diagnostics, overriddenMethod, overridingMethod, overridingParameter, _, location) => { diagnostics.Add( ReportInvalidScopedOverrideAsError(overriddenMethod, overridingMethod) ? ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation : ErrorCode.WRN_ScopedMismatchInParameterOfOverrideOrImplementation, location, new FormattedSymbol(overridingParameter, SymbolDisplayFormat.ShortFormat)); }, overridingMemberLocation, allowVariance: true, invokedAsExtensionMethod: false); } CheckValidNullableMethodOverride(overridingMethod.DeclaringCompilation, overriddenMethod, overridingMethod, diagnostics, ReportBadReturn, ReportBadParameter, overridingMemberLocation); CheckRefReadonlyInMismatch( overriddenMethod, overridingMethod, diagnostics, static (diagnostics, _, _, overridingParameter, _, arg) => { var (overriddenParameter, location) = arg; // Reference kind modifier of parameter '{0}' doesn't match the corresponding parameter '{1}' in overridden or implemented member. diagnostics.Add(ErrorCode.WRN_OverridingDifferentRefness, location, overridingParameter, overriddenParameter); }, overridingMemberLocation, invokedAsExtensionMethod: false); CheckCallerUnsafeMismatch( implementedMember: null, overridingMethod, ErrorCode.ERR_CallerUnsafeOverridingSafe, overridingMemberLocation, static l => l, diagnostics); } } internal static bool IsOrContainsErrorType(TypeSymbol typeSymbol) { return (object)typeSymbol.VisitType((currentTypeSymbol, unused1, unused2) => currentTypeSymbol.IsErrorType(), (object)null) != null; } /// <summary> /// Return true if <paramref name="overridingReturnType"/> is valid for the return type of an override method when the overridden method's return type is <paramref name="overriddenReturnType"/>. /// </summary> private bool IsValidOverrideReturnType(Symbol overridingSymbol, TypeWithAnnotations overridingReturnType, TypeWithAnnotations overriddenReturnType, BindingDiagnosticBag diagnostics) { if (overridingSymbol.ContainingAssembly.RuntimeSupportsCovariantReturnsOfClasses && DeclaringCompilation.LanguageVersion >= MessageID.IDS_FeatureCovariantReturnsForOverrides.RequiredVersion()) { var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, ContainingAssembly); var result = DeclaringCompilation.Conversions.HasIdentityOrImplicitReferenceConversion(overridingReturnType.Type, overriddenReturnType.Type, ref useSiteInfo); Location symbolLocation = overridingSymbol.TryGetFirstLocation(); diagnostics.Add(symbolLocation, useSiteInfo); return result; } else { return overridingReturnType.Equals(overriddenReturnType, TypeCompareKind.AllIgnoreOptions); } } private static readonly ReportMismatchInReturnType<Location> ReportBadReturn = (BindingDiagnosticBag diagnostics, MethodSymbol overriddenMethod, MethodSymbol overridingMethod, bool topLevel, Location location) => diagnostics.Add(topLevel ? ErrorCode.WRN_TopLevelNullabilityMismatchInReturnTypeOnOverride : ErrorCode.WRN_NullabilityMismatchInReturnTypeOnOverride, location); private static readonly ReportMismatchInParameterType<Location> ReportBadParameter = (BindingDiagnosticBag diagnostics, MethodSymbol overriddenMethod, MethodSymbol overridingMethod, ParameterSymbol overridingParameter, bool topLevel, Location location) => diagnostics.Add( topLevel ? ErrorCode.WRN_TopLevelNullabilityMismatchInParameterTypeOnOverride : ErrorCode.WRN_NullabilityMismatchInParameterTypeOnOverride, location, new FormattedSymbol(overridingParameter, SymbolDisplayFormat.ShortFormat)); /// <returns> /// <see langword="true"/> if a diagnostic was added. Otherwise, <see langword="false"/>. /// </returns> internal static bool CheckValidNullableMethodOverride<TArg>( CSharpCompilation compilation, MethodSymbol baseMethod, MethodSymbol overrideMethod, BindingDiagnosticBag diagnostics, ReportMismatchInReturnType<TArg> reportMismatchInReturnType, ReportMismatchInParameterType<TArg> reportMismatchInParameterType, TArg extraArgument, bool invokedAsExtensionMethod = false) { if (!PerformValidNullableOverrideCheck(compilation, baseMethod, overrideMethod)) { return false; } bool hasErrors = false; if ((baseMethod.FlowAnalysisAnnotations & FlowAnalysisAnnotations.DoesNotReturn) == FlowAnalysisAnnotations.DoesNotReturn && (overrideMethod.FlowAnalysisAnnotations & FlowAnalysisAnnotations.DoesNotReturn) != FlowAnalysisAnnotations.DoesNotReturn) { diagnostics.Add(ErrorCode.WRN_DoesNotReturnMismatch, overrideMethod.GetFirstLocation(), new FormattedSymbol(overrideMethod, SymbolDisplayFormat.MinimallyQualifiedFormat)); hasErrors = true; } var conversions = compilation.Conversions.WithNullability(true); var baseParameters = baseMethod.Parameters; var overrideParameters = overrideMethod.Parameters; var overrideParameterOffset = invokedAsExtensionMethod ? 1 : 0; Debug.Assert(baseMethod.ParameterCount == overrideMethod.ParameterCount - overrideParameterOffset); if (reportMismatchInReturnType != null) { var overrideReturnType = getNotNullIfNotNullOutputType(overrideMethod.ReturnTypeWithAnnotations, overrideMethod.ReturnNotNullIfParameterNotNull); // check nested nullability if (!isValidNullableConversion( conversions, overrideMethod.RefKind, overrideReturnType.Type, baseMethod.ReturnTypeWithAnnotations.Type)) { reportMismatchInReturnType(diagnostics, baseMethod, overrideMethod, false, extraArgument); return true; } // check top-level nullability including flow analysis annotations if (!NullableWalker.AreParameterAnnotationsCompatible( overrideMethod.RefKind == RefKind.Ref ? RefKind.Ref : RefKind.Out, baseMethod.ReturnTypeWithAnnotations, baseMethod.ReturnTypeFlowAnalysisAnnotations, overrideReturnType, overrideMethod.ReturnTypeFlowAnalysisAnnotations)) { reportMismatchInReturnType(diagnostics, baseMethod, overrideMethod, true, extraArgument); return true; } } if (reportMismatchInParameterType != null) { for (int i = 0; i < baseParameters.Length; i++) { var baseParameter = baseParameters[i]; var baseParameterType = baseParameter.TypeWithAnnotations; int parameterIndex = i + overrideParameterOffset; var overrideParameter = overrideParameters[parameterIndex]; var overrideParameterType = getNotNullIfNotNullOutputType(overrideParameter.TypeWithAnnotations, overrideParameter.NotNullIfParameterNotNull); // check nested nullability if (!isValidNullableConversion( conversions, overrideParameter.RefKind, baseParameterType.Type, overrideParameterType.Type)) { reportMismatchInParameterType(diagnostics, baseMethod, overrideMethod, overrideParameter, false, extraArgument); hasErrors = true; } // check top-level nullability including flow analysis annotations else if (!NullableWalker.AreParameterAnnotationsCompatible( overrideParameter.RefKind, baseParameterType, baseParameter.FlowAnalysisAnnotations, overrideParameterType, overrideParameter.FlowAnalysisAnnotations)) { reportMismatchInParameterType(diagnostics, baseMethod, overrideMethod, overrideParameter, true, extraArgument); hasErrors = true; } } } return hasErrors; TypeWithAnnotations getNotNullIfNotNullOutputType(TypeWithAnnotations outputType, ImmutableHashSet<string> notNullIfParameterNotNull) { if (!notNullIfParameterNotNull.IsEmpty) { for (var i = 0; i < baseParameters.Length; i++) { var overrideParam = overrideParameters[i + overrideParameterOffset]; var baseParam = baseParameters[i]; if (notNullIfParameterNotNull.Contains(overrideParam.Name) && NullableWalker.GetParameterState(baseParam.TypeWithAnnotations, baseParam.FlowAnalysisAnnotations).IsNotNull) { return outputType.AsNotAnnotated(); } } } return outputType; } static bool isValidNullableConversion( ConversionsBase conversions, RefKind refKind, TypeSymbol sourceType, TypeSymbol targetType) { switch (refKind) { case RefKind.Ref: // ref variables are invariant return sourceType.Equals( targetType, TypeCompareKind.AllIgnoreOptions & ~(TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); case RefKind.Out: // out variables have inverted variance (sourceType, targetType) = (targetType, sourceType); break; default: break; } Debug.Assert(conversions.IncludeNullability); var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; return conversions.ClassifyImplicitConversionFromType(sourceType, targetType, ref discardedUseSiteInfo).Kind != ConversionKind.NoConversion; } } #nullable enable /// <summary> /// Returns true if the method signature must match, with respect to scoped for ref safety, /// in overrides, interface implementations, or delegate conversions. /// </summary> internal static bool RequiresValidScopedOverrideForRefSafety(MethodSymbol? method, ParameterSymbol? overrideThisParameter) { if (method is null) { return false; } var parameters = method.Parameters; // https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch // The compiler will report a diagnostic for _unsafe scoped mismatches_ across overrides, interface implementations, and delegate conversions when: // - The method has a `ref` or `out` parameter of `ref struct` type with a mismatch of adding `[UnscopedRef]` (not removing `scoped`). // (In this case, a silly cyclic assignment is possible, hence no other parameters are necessary.) // ... if (parameters.Any(static p => p is { EffectiveScope: ScopedKind.None, RefKind: RefKind.Ref } or { EffectiveScope: ScopedKind.ScopedRef, RefKind: RefKind.Out } && p.Type.IsRefLikeOrAllowsRefLikeType())) { return true; } // ... // - Or both of these are true: // - The method returns a `ref struct` or returns a `ref` or `ref readonly`, or the method has a `ref` or `out` parameter of `ref struct` type, and // ... int nRefParametersRequired; if ((overrideThisParameter is { RefKind: RefKind.Ref or RefKind.Out } && overrideThisParameter.Type.IsRefLikeOrAllowsRefLikeType()) || method.ReturnType.IsRefLikeOrAllowsRefLikeType() || (method.RefKind is RefKind.Ref or RefKind.RefReadOnly)) { nRefParametersRequired = 1; } else if (parameters.Any(p => (p.RefKind is RefKind.Ref or RefKind.Out) && p.Type.IsRefLikeOrAllowsRefLikeType())) { nRefParametersRequired = 2; // including the parameter found above } else { return false; } // ... // - The method has at least one additional `ref`, `in`, `ref readonly`, or `out` parameter, or a parameter of `ref struct` type. int nRefParameters = parameters.Count(p => p.RefKind is RefKind.Ref or RefKind.In or RefKind.RefReadOnlyParameter or RefKind.Out); if (nRefParameters >= nRefParametersRequired) { return true; } else if (parameters.Any(p => p.RefKind == RefKind.None && p.Type.IsRefLikeOrAllowsRefLikeType())) { return true; } return false; } /// <summary> /// Returns true if a scoped mismatch should be reported as an error rather than a warning. /// </summary> internal static bool ReportInvalidScopedOverrideAsError(MethodSymbol baseMethod, MethodSymbol overrideMethod) { // https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#scoped-mismatch // The diagnostic is reported as an error if the mismatched signatures are both using C#11 ref safety rules; otherwise, the diagnostic is a warning. return baseMethod.UseUpdatedEscapeRules && overrideMethod.UseUpdatedEscapeRules; } /// <summary> /// Returns true if a diagnostic was added. /// </summary> internal static bool CheckValidScopedOverride<TArg>( MethodSymbol? baseMethod, MethodSymbol? overrideMethod, BindingDiagnosticBag diagnostics, ReportMismatchInParameterType<TArg> reportMismatchInParameterType, TArg extraArgument, bool allowVariance, bool invokedAsExtensionMethod) { Debug.Assert(reportMismatchInParameterType is { }); if (baseMethod is null || overrideMethod is null) { return false; } bool hasErrors = false; var baseParameters = baseMethod.Parameters; var overrideParameters = overrideMethod.Parameters; var overrideParameterOffset = invokedAsExtensionMethod ? 1 : 0; Debug.Assert(baseMethod.ParameterCount == overrideMethod.ParameterCount - overrideParameterOffset); for (int i = 0; i < baseParameters.Length; i++) { var baseParameter = baseParameters[i]; var overrideParameter = overrideParameters[i + overrideParameterOffset]; if (!isValidScopedConversion(allowVariance, baseParameter.EffectiveScope, baseParameter.HasUnscopedRefAttribute, overrideParameter.EffectiveScope, overrideParameter.HasUnscopedRefAttribute)) { reportMismatchInParameterType(diagnostics, baseMethod, overrideMethod, overrideParameter, topLevel: true, extraArgument); hasErrors = true; } } return hasErrors; static bool isValidScopedConversion( bool allowVariance, ScopedKind baseScope, bool baseHasUnscopedRefAttribute, ScopedKind overrideScope, bool overrideHasUnscopedRefAttribute) { if (baseScope == overrideScope) { if (baseHasUnscopedRefAttribute == overrideHasUnscopedRefAttribute) { return true; } return allowVariance && !overrideHasUnscopedRefAttribute; } return allowVariance && baseScope == ScopedKind.None; } } internal static void CheckRefReadonlyInMismatch<TArg>( MethodSymbol? baseMethod, MethodSymbol? overrideMethod, BindingDiagnosticBag diagnostics, ReportMismatchInParameterType<(ParameterSymbol BaseParameter, TArg Arg)> reportMismatchInParameterType, TArg extraArgument, bool invokedAsExtensionMethod) { Debug.Assert(reportMismatchInParameterType is { }); if (baseMethod is null || overrideMethod is null) { return; } var baseParameters = baseMethod.Parameters; var overrideParameters = overrideMethod.Parameters; var overrideParameterOffset = invokedAsExtensionMethod ? 1 : 0; Debug.Assert(baseMethod.ParameterCount == overrideMethod.ParameterCount - overrideParameterOffset); for (int i = 0; i < baseParameters.Length; i++) { var baseParameter = baseParameters[i]; var overrideParameter = overrideParameters[i + overrideParameterOffset]; if (baseParameter.RefKind != overrideParameter.RefKind) { reportMismatchInParameterType(diagnostics, baseMethod, overrideMethod, overrideParameter, topLevel: true, (baseParameter, extraArgument)); } } } internal static void CheckCallerUnsafeMismatch<TArg>( Symbol? implementedMember, Symbol? overridingMember, ErrorCode errorCode, TArg arg, Func<TArg, Location> overridingMemberLocation, BindingDiagnosticBag diagnostics) { if (overridingMember is null) { return; } Debug.Assert(implementedMember is not null || overridingMember.IsDefinition); var leastOverriddenMember = implementedMember ?? overridingMember.GetLeastOverriddenMember(overridingMember.ContainingType); if (overridingMember.GetCallerUnsafeMode(ConsList<FieldSymbol>.Empty) == CallerUnsafeMode.Explicit && leastOverriddenMember.GetCallerUnsafeMode(ConsList<FieldSymbol>.Empty) == CallerUnsafeMode.None) { diagnostics.Add(errorCode, overridingMemberLocation(arg), overridingMember, leastOverriddenMember); } } #nullable disable private static bool PerformValidNullableOverrideCheck( CSharpCompilation compilation, Symbol overriddenMember, Symbol overridingMember) { // Don't do any validation if the nullable feature is not enabled or // the override is not written directly in source return overriddenMember is object && overridingMember is object && compilation is object && compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); } internal static void CheckValidNullableEventOverride<TArg>( CSharpCompilation compilation, EventSymbol overriddenEvent, EventSymbol overridingEvent, BindingDiagnosticBag diagnostics, Action<BindingDiagnosticBag, EventSymbol, EventSymbol, TArg> reportMismatch, TArg extraArgument) { if (!PerformValidNullableOverrideCheck(compilation, overriddenEvent, overridingEvent)) { return; } var conversions = compilation.Conversions.WithNullability(true); if (!conversions.HasAnyNullabilityImplicitConversion(overriddenEvent.TypeWithAnnotations, overridingEvent.TypeWithAnnotations)) { reportMismatch(diagnostics, overriddenEvent, overridingEvent, extraArgument); } } private static void CheckNonOverrideMember( Symbol hidingMember, bool hidingMemberIsNew, OverriddenOrHiddenMembersResult overriddenOrHiddenMembers, BindingDiagnosticBag diagnostics, out bool suppressAccessors) { suppressAccessors = false; var hidingMemberLocation = hidingMember.GetFirstLocation(); Debug.Assert(overriddenOrHiddenMembers != null); Debug.Assert(!overriddenOrHiddenMembers.OverriddenMembers.Any()); //since hidingMethod.IsOverride is false var hiddenMembers = overriddenOrHiddenMembers.HiddenMembers; Debug.Assert(!hiddenMembers.IsDefault); if (hiddenMembers.Length == 0) { if (hidingMemberIsNew && !hidingMember.IsAccessor()) { diagnostics.Add(ErrorCode.WRN_NewNotRequired, hidingMemberLocation, hidingMember); } } else { var diagnosticAdded = false; //for interfaces, we always report WRN_NewRequired //if we went into the loop, the pseudo-abstract nature of interfaces would throw off the other checks if (!hidingMember.ContainingType.IsInterface) { foreach (var hiddenMember in hiddenMembers) { diagnosticAdded |= AddHidingAbstractDiagnostic(hidingMember, hidingMemberLocation, hiddenMember, diagnostics, ref suppressAccessors); //can actually get both, so don't use else if if (!hidingMemberIsNew && hiddenMember.Kind == hidingMember.Kind && !hidingMember.IsAccessor() && (hiddenMember.IsAbstract || hiddenMember.IsVirtual || hiddenMember.IsOverride) && !IsShadowingSynthesizedRecordMember(hidingMember)) { diagnostics.Add(ErrorCode.WRN_NewOrOverrideExpected, hidingMemberLocation, hidingMember, hiddenMember); diagnosticAdded = true; } if (hiddenMember.IsRequired()) { // Required member '{0}' cannot be hidden by '{1}'. diagnostics.Add(ErrorCode.ERR_RequiredMemberCannotBeHidden, hidingMemberLocation, hiddenMember, hidingMember); diagnosticAdded = true; } if (diagnosticAdded) { break; } } } if (!hidingMemberIsNew && !IsShadowingSynthesizedRecordMember(hidingMember) && !diagnosticAdded && !hidingMember.IsAccessor() && (!hidingMember.IsOperator() || hiddenMembers[0].IsOperator())) { diagnostics.Add(ErrorCode.WRN_NewRequired, hidingMemberLocation, hidingMember, hiddenMembers[0]); } if (hidingMember is MethodSymbol hidingMethod && hiddenMembers[0] is MethodSymbol hiddenMethod) { CheckRefReadonlyInMismatch( hiddenMethod, hidingMethod, diagnostics, static (diagnostics, _, _, hidingParameter, _, arg) => { var (hiddenParameter, location) = arg; // Reference kind modifier of parameter '{0}' doesn't match the corresponding parameter '{1}' in hidden member. diagnostics.Add(ErrorCode.WRN_HidingDifferentRefness, location, hidingParameter, hiddenParameter); }, hidingMemberLocation, invokedAsExtensionMethod: false); } } } private static bool IsShadowingSynthesizedRecordMember(Symbol hidingMember) { return hidingMember is SynthesizedRecordEquals || hidingMember is SynthesizedRecordDeconstruct || hidingMember is SynthesizedRecordClone; } /// <summary> /// If necessary, report a diagnostic for a hidden abstract member. /// </summary> /// <returns>True if a diagnostic was reported.</returns> private static bool AddHidingAbstractDiagnostic(Symbol hidingMember, Location hidingMemberLocation, Symbol hiddenMember, BindingDiagnosticBag diagnostics, ref bool suppressAccessors) { switch (hiddenMember.Kind) { case SymbolKind.Method: case SymbolKind.Property: case SymbolKind.Event: break; // Can result in diagnostic default: return false; // Cannot result in diagnostic } // If the hidden member isn't abstract, the diagnostic doesn't apply. // If the hiding member is in a non-abstract type, then suppress this cascading error. if (!hiddenMember.IsAbstract || !hidingMember.ContainingType.IsAbstract) { return false; } switch (hidingMember.DeclaredAccessibility) { case Accessibility.Internal: case Accessibility.Private: case Accessibility.ProtectedAndInternal: break; case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: { // At this point we know we're going to report ERR_HidingAbstractMethod, we just have to // figure out the substitutions. switch (hidingMember.Kind) { case SymbolKind.Method: var associatedPropertyOrEvent = ((MethodSymbol)hidingMember).AssociatedSymbol; if ((object)associatedPropertyOrEvent != null) { //Dev10 reports that the property/event is doing the hiding, rather than the method diagnostics.Add(ErrorCode.ERR_HidingAbstractMethod, associatedPropertyOrEvent.GetFirstLocation(), associatedPropertyOrEvent, hiddenMember); break; } goto default; case SymbolKind.Property: case SymbolKind.Event: // NOTE: We used to let the accessors take care of this case, but then we weren't handling the case // where a hiding and hidden properties did not have any accessors in common. // CONSIDER: Dev10 actually reports an error for each accessor of a hidden property/event, but that seems unnecessary. suppressAccessors = true; goto default; default: diagnostics.Add(ErrorCode.ERR_HidingAbstractMethod, hidingMemberLocation, hidingMember, hiddenMember); break; } return true; } default: throw ExceptionUtilities.UnexpectedValue(hidingMember.DeclaredAccessibility); } return false; } private static bool OverrideHasCorrectAccessibility(Symbol overridden, Symbol overriding) { // Check declared accessibility rather than effective accessibility since there's a different // check (CS0560) that determines whether the containing types have compatible accessibility. if (!overriding.ContainingAssembly.HasInternalAccessTo(overridden.ContainingAssembly) && overridden.DeclaredAccessibility == Accessibility.ProtectedOrInternal) { return overriding.DeclaredAccessibility == Accessibility.Protected; } else { return overridden.DeclaredAccessibility == overriding.DeclaredAccessibility; } } #nullable enable /// <summary> /// It is invalid for a type to directly (vs through a base class) implement two interfaces that /// unify (i.e. are the same for some substitution of type parameters). /// </summary> /// <remarks> /// CONSIDER: check this while building up InterfacesAndTheirBaseInterfaces (only in the SourceNamedTypeSymbol case). /// </remarks> private void CheckInterfaceUnification(BindingDiagnosticBag diagnostics) { if (!this.IsGenericType) { return; } // CONSIDER: filtering the list to only include generic types would save iterations. int numInterfaces = this.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Count; if (numInterfaces < 2) { return; } // NOTE: a typical approach to finding duplicates in less than quadratic time // is to use a HashSet with an appropriate comparer. Unfortunately, this approach // does not apply (at least, not straightforwardly), because CanUnifyWith is not // transitive and, thus, is not an equivalence relation. NamedTypeSymbol[] interfaces = this.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys.ToArray(); for (int i1 = 0; i1 < numInterfaces; i1++) { for (int i2 = i1 + 1; i2 < numInterfaces; i2++) { NamedTypeSymbol interface1 = interfaces[i1]; NamedTypeSymbol interface2 = interfaces[i2]; // CanUnifyWith is the real check - the others just short-circuit if (interface1.IsGenericType && interface2.IsGenericType && TypeSymbol.Equals(interface1.OriginalDefinition, interface2.OriginalDefinition, TypeCompareKind.ConsiderEverything2) && interface1.CanUnifyWith(interface2)) { if (GetImplementsLocationOrFallback(interface1).SourceSpan.Start > GetImplementsLocationOrFallback(interface2).SourceSpan.Start) { // Mention interfaces in order of their appearance in the base list, for consistency. var temp = interface1; interface1 = interface2; interface2 = temp; } diagnostics.Add(ErrorCode.ERR_UnifyingInterfaceInstantiations, this.GetFirstLocation(), this, interface1, interface2); } } } } /// <summary> /// Though there is a method that C# considers to be an implementation of the interface method, that /// method may not be considered an implementation by the CLR. In particular, implicit implementation /// methods that are non-virtual or that have different (usually fewer) custom modifiers than the /// interface method, will not be considered CLR overrides. To address this problem, we either make /// them virtual (in metadata, not in C#), or we introduce an explicit interface implementation that /// delegates to the implicit implementation. /// </summary> /// <param name="implementingMemberAndDiagnostics">Returned from FindImplementationForInterfaceMemberWithDiagnostics.</param> /// <param name="interfaceMember">The interface method or property that is being implemented.</param> /// <returns> /// A synthesized forwarding method for the implementation, or information about MethodImpl entry that should be emitted, /// or default if neither needed. /// </returns> private (SynthesizedExplicitImplementationForwardingMethod? ForwardingMethod, (MethodSymbol Body, MethodSymbol Implemented)? MethodImpl) SynthesizeInterfaceMemberImplementation(SymbolAndDiagnostics implementingMemberAndDiagnostics, Symbol interfaceMember) { foreach (Diagnostic diagnostic in implementingMemberAndDiagnostics.Diagnostics.Diagnostics) { if (diagnostic.Severity == DiagnosticSeverity.Error && diagnostic.Code is not ((int)ErrorCode.ERR_ImplicitImplementationOfNonPublicInterfaceMember or (int)ErrorCode.ERR_ImplicitImplementationOfInaccessibleInterfaceMember)) { return default; } } Symbol implementingMember = implementingMemberAndDiagnostics.Symbol; //don't worry about properties or events - we'll catch them through their accessors if ((object)implementingMember == null || implementingMember.Kind != SymbolKind.Method) { return default; } MethodSymbol interfaceMethod = (MethodSymbol)interfaceMember; MethodSymbol implementingMethod = (MethodSymbol)implementingMember; //explicit implementations are always respected by the CLR if (implementingMethod.ExplicitInterfaceImplementations.Contains(interfaceMethod, ExplicitInterfaceImplementationTargetMemberEqualityComparer.Instance)) { return default; } if (!interfaceMethod.IsStatic) { MethodSymbol implementingMethodOriginalDefinition = implementingMethod.OriginalDefinition; bool needSynthesizedImplementation = true; // If the implementing method is from a source file in the same module and the // override is correct from the runtime's perspective (esp the custom modifiers // match), then we can just twiddle the metadata virtual bit. Otherwise, we need // to create an explicit implementation that delegates to the real implementation. if (MemberSignatureComparer.RuntimeImplicitImplementationComparer.Equals(implementingMethod, interfaceMethod) && IsOverrideOfPossibleImplementationUnderRuntimeRules(implementingMethod, @interfaceMethod.ContainingType)) { if (ReferenceEquals(this.ContainingModule, implementingMethodOriginalDefinition.ContainingModule)) { if (implementingMethodOriginalDefinition is SourceMemberMethodSymbol sourceImplementMethodOriginalDefinition) { sourceImplementMethodOriginalDefinition.EnsureMetadataVirtual(); needSynthesizedImplementation = false; } } else if (implementingMethod.IsMetadataVirtual(null, ignoreInterfaceImplementationChanges: true)) { // If the signatures match and the implementation method is definitely virtual, then we're set. needSynthesizedImplementation = false; } } if (!needSynthesizedImplementation) { return default; } } else { if (implementingMethod.ContainingType != (object)this) { if (implementingMethod.ContainingType.IsInterface || implementingMethod.Equals(this.BaseTypeNoUseSiteDiagnostics?.FindImplementationForInterfaceMemberInNonInterfaceWithDiagnostics(interfaceMethod).Symbol, TypeCompareKind.CLRSignatureCompareOptions)) { return default; } } else if (MemberSignatureComparer.RuntimeExplicitImplementationSignatureComparer.Equals(implementingMethod, interfaceMethod)) { return (null, (implementingMethod, interfaceMethod)); } } return (new SynthesizedExplicitImplementationForwardingMethod(interfaceMethod, implementingMethod, this), null); } #nullable disable /// <summary> /// The CLR will only look for an implementation of an interface method in a type that /// 1) declares that it implements that interface; or /// 2) is a base class of a type that declares that it implements the interface but not /// a subtype of a class that declares that it implements the interface. /// /// For example, /// /// interface I /// class A /// class B : A, I /// class C : B /// class D : C, I /// /// Suppose the runtime is looking for D's implementation of a member of I. It will look in /// D because of (1), will not look in C, will look in B because of (1), and will look in A /// because of (2). /// /// The key point is that it does not look in C, which C# *does*. /// </summary> private static bool IsPossibleImplementationUnderRuntimeRules(MethodSymbol implementingMethod, NamedTypeSymbol @interface) { NamedTypeSymbol type = implementingMethod.ContainingType; if (type.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.ContainsKey(@interface)) { return true; } NamedTypeSymbol baseType = type.BaseTypeNoUseSiteDiagnostics; return (object)baseType == null || !baseType.AllInterfacesNoUseSiteDiagnostics.Contains(@interface); } /// <summary> /// If C# picks a different implementation than the CLR (see IsPossibleImplementationUnderClrRules), then we might /// still be okay, but dynamic dispatch might result in C#'s choice getting called anyway. /// </summary> /// <remarks> /// This is based on SymbolPreparer::IsCLRMethodImplSame in the native compiler. /// /// ACASEY: What the native compiler actually does is compute the C# answer, compute the CLR answer, /// and then confirm that they override the same method. What I've done here is check for the situations /// where the answers could disagree. I believe the results will be equivalent. If in doubt, a more conservative /// check would be implementingMethod.ContainingType.InterfacesAndTheirBaseInterfaces.Contains(@interface). /// </remarks> private static bool IsOverrideOfPossibleImplementationUnderRuntimeRules(MethodSymbol implementingMethod, NamedTypeSymbol @interface) { MethodSymbol curr = implementingMethod; while ((object)curr != null) { if (IsPossibleImplementationUnderRuntimeRules(curr, @interface)) { return true; } curr = curr.OverriddenMethod; } return false; } internal sealed override ImmutableArray<NamedTypeSymbol> GetInterfacesToEmit() { return CalculateInterfacesToEmit(); } } }