Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Covariant returns part 4 #44025

Merged
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1d95c13
Respond to some review comments on part 3.
May 6, 2020
750018d
Implement and test retargeting symbols for covariant returns.
May 7, 2020
b0dacd9
A PE symbol with multiple methodimpl entries is ignored as an explici…
May 7, 2020
05c5d19
Test binary compatibility scenarios with overrides inserted into the …
May 7, 2020
8ddf059
Test a scenario with duplicate requirements for a methodimpl entry
May 7, 2020
3836d71
A methodimpl may be required anytime the runtime and language disagre…
May 8, 2020
08b055c
Compare types using the public model in tests.
May 8, 2020
40369f6
Strengthen test helpers.
May 8, 2020
584e6ec
Emit a call to the least derived override in the hierarchy with the c…
May 8, 2020
609706f
Test covariant returns in expression trees.
May 11, 2020
3d36772
Test capturing a covariant method in a delegate creation.
May 11, 2020
ae4bdf1
Test consumption and override of covariantly overridden methods, prop…
May 11, 2020
6a2b697
Remove dead code in test.
May 11, 2020
8d0ef22
Require that the target runtime supports covariant returns when the f…
May 12, 2020
03c9b5c
Adjust tests for new special member.
May 12, 2020
50bdd15
Test compile-time behavior of nullable variance in covariant returns.
May 12, 2020
f1591f0
Produce RequireMethodImplToRemainInEffectAttribute on covariant overr…
May 12, 2020
2eabf00
Fix tests.
May 13, 2020
daa9279
Eliminate CS1957 by generating correct code.
May 13, 2020
6f20e38
Cache `PEMethodSymbol.ExplicitlyOverriddenClassMethod`
May 13, 2020
1bb46b6
Move some PROTOTYPE comments to issues.
May 13, 2020
1918613
Merge branch 'features/covariant-returns' of https://github.com/dotne…
May 15, 2020
4bae415
Remove virtual `ExplicitlyOverriddenClassMethod` from MethodSymbol.
May 15, 2020
1acf0b5
Enhance the covariant return tests per PR review.
May 20, 2020
8dfae1f
Rename `RequireMethodImplToRemainInEffectAttribute` to `PreserveBaseO…
Jun 1, 2020
ad75501
Merge branch 'features/covariant-returns' of https://github.com/dotne…
Jun 1, 2020
f000d3e
Merge branch 'features/covariant-returns' of https://github.com/dotne…
Jun 4, 2020
d526855
Merge branch 'covariant-returns-part4' of https://github.com/gafter/r…
Jun 4, 2020
10ae4b3
Merge branch 'features/covariant-returns' of https://github.com/dotne…
Jun 9, 2020
db2f370
Document breaking change
Jun 13, 2020
a63d236
Changes per review comments
Jun 18, 2020
8f4771f
Modify how overrides are computed for PE symbols as requested in code…
Jun 19, 2020
7ccc8c0
Condition WRN_MultipleRuntimeOverrideMatches on whether runtime has w…
Jun 26, 2020
f724fd8
Remove unneeded using added by overeager IDE
Jun 26, 2020
173a07a
Revise code to determine if an override needs a methodimpl, and if WR…
Jun 30, 2020
cd0752f
Test `SourceMethodSymbol.RequiresExplicitOverride` in ambiguous scena…
Jul 1, 2020
07beda5
Merge branch 'features/covariant-returns' of https://github.com/dotne…
Jul 1, 2020
fafc14d
Add tests requested in review.
Jul 1, 2020
9e15124
Preserve historical test for methodimpl generation as a fallback.
Jul 1, 2020
b49b2ac
Merge branch 'features/covariant-returns' of https://github.com/dotne…
Jul 2, 2020
ee5b72f
Suppress a warning in an error scenario.
Jul 2, 2020
86118ca
Merge branch 'features/covariant-returns' of https://github.com/dotne…
gafter Jul 4, 2020
f865a0a
Adjust test after merge from master.
gafter Jul 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3371,12 +3371,6 @@ Although C# distinguishes between out and ref, the CLR sees them as the same. Wh

Give the compiler some way to differentiate the methods. For example, you can give them different names or provide an additional parameter on one of them.</value>
</data>
<data name="WRN_MultipleRuntimeOverrideMatches" xml:space="preserve">
gafter marked this conversation as resolved.
Show resolved Hide resolved
<value>Member '{1}' overrides '{0}'. There are multiple override candidates at run-time. It is implementation dependent which method will be called.</value>
</data>
<data name="WRN_MultipleRuntimeOverrideMatches_Title" xml:space="preserve">
<value>Member overrides base member with multiple override candidates at run-time</value>
</data>
<data name="ERR_ObjectOrCollectionInitializerWithDelegateCreation" xml:space="preserve">
<value>Object and collection initializer expressions may not be applied to a delegate creation expression</value>
</data>
Expand Down Expand Up @@ -6253,4 +6247,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_FeatureCovariantReturnsForOverrides" xml:space="preserve">
<value>covariant returns</value>
</data>
<data name="ERR_RuntimeDoesNotSupportCovariantReturnsOfClasses" xml:space="preserve">
<value>'{0}': Target runtime doesn't support covariant return types in overrides. Return type must be '{2}' to match overridden member '{1}'</value>
</data>
<data name="ERR_RuntimeDoesNotSupportCovariantPropertiesOfClasses" xml:space="preserve">
<value>'{0}': Target runtime doesn't support covariant types in overrides. Type must be '{2}' to match overridden member '{1}'</value>
</data>
</root>
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/CodeGen/EmitConversion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ private void EmitDelegateCreation(BoundExpression node, BoundExpression receiver
_builder.EmitOpCode(ILOpCode.Ldvirtftn);

// substitute the method with original virtual method
method = method.GetConstructedLeastOverriddenMethod(_method.ContainingType);
method = method.GetConstructedLeastOverriddenMethod(_method.ContainingType, requireSameReturnType: true);
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,7 @@ private void EmitCallExpression(BoundCall call, UseKind useKind)
MethodSymbol actualMethodTargetedByTheCall = method;
if (method.IsOverride && callKind != CallKind.Call)
{
actualMethodTargetedByTheCall = method.GetConstructedLeastOverriddenMethod(_method.ContainingType);
actualMethodTargetedByTheCall = method.GetConstructedLeastOverriddenMethod(_method.ContainingType, requireSameReturnType: true);
}

if (callKind == CallKind.ConstrainedCallVirt && actualMethodTargetedByTheCall.ContainingType.IsValueType)
Expand Down
4 changes: 3 additions & 1 deletion src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ internal enum ErrorCode
ERR_InitializerAddHasParamModifiers = 1954,
ERR_NonInvocableMemberCalled = 1955,
WRN_MultipleRuntimeImplementationMatches = 1956,
WRN_MultipleRuntimeOverrideMatches = 1957,
// WRN_MultipleRuntimeOverrideMatches = 1957, removed per https://github.com/dotnet/roslyn/issues/44067
ERR_ObjectOrCollectionInitializerWithDelegateCreation = 1958,
ERR_InvalidConstantDeclarationType = 1959,
ERR_IllegalVarianceSyntax = 1960,
Expand Down Expand Up @@ -1775,6 +1775,8 @@ internal enum ErrorCode
WRN_MemberNotNullWhen = 8775,
WRN_MemberNotNullBadMember = 8776,
WRN_ParameterDisallowsNull = 8777,
ERR_RuntimeDoesNotSupportCovariantReturnsOfClasses = 8778,
ERR_RuntimeDoesNotSupportCovariantPropertiesOfClasses = 8779,

ERR_DesignatorBeneathPatternCombinator = 8780,
ERR_UnsupportedTypeForRelationalPattern = 8781,
Expand Down
1 change: 0 additions & 1 deletion src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,6 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_BadXMLRefTypeVar:
case ErrorCode.WRN_ReferencedAssemblyReferencesLinkedPIA:
case ErrorCode.WRN_MultipleRuntimeImplementationMatches:
case ErrorCode.WRN_MultipleRuntimeOverrideMatches:
case ErrorCode.WRN_FileAlreadyIncluded:
case ErrorCode.WRN_NoConfigNotOnCommandLine:
case ErrorCode.WRN_AnalyzerCannotBeCreated:
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1187,7 +1187,7 @@ public BoundExpression MethodInfo(MethodSymbol method)
// whether or not to call a method with a value type receiver directly).
if (!method.ContainingType.IsValueType || !Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.MayUseCallForStructMethod(method))
{
method = method.GetConstructedLeastOverriddenMethod(this.CompilationState.Type);
method = method.GetConstructedLeastOverriddenMethod(this.CompilationState.Type, requireSameReturnType: true);
}

return new BoundMethodInfo(
Expand Down
26 changes: 26 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/AssemblySymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,32 @@ internal bool RuntimeSupportsDefaultInterfaceImplementation
get => !(GetSpecialTypeMember(SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__DefaultImplementationsOfInterfaces) is null);
}

/// <summary>
/// Figure out if the target runtime supports covariant returns of methods declared in classes.
/// </summary>
internal bool RuntimeSupportsCovariantReturnsOfClasses
{
get
{
// check for the runtime feature indicator.
if (!(GetSpecialTypeMember(SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__CovariantReturnsOfClasses) is null))
{
return true;
}

// https://github.com/dotnet/roslyn/issues/44206: For testing purposes, we added a feature flag to
// the C# compiler that causes the compiler to skip the requirement that the runtime supports covariant
// returns. We can remove it once we have a runtime that we can test against that actually supports the feature.
if (this is SourceAssemblySymbol { DeclaringCompilation: CSharpCompilation compilation } &&
compilation.Feature("DoNotRequireRuntimeCovariantReturnsSupport") != null)
{
return true;
}

return false;
}
}

/// <summary>
/// Return an array of assemblies involved in canonical type resolution of
/// NoPia local types defined within this assembly. In other words, all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ internal static Symbol GetLeastOverriddenMember(this Symbol member, NamedTypeSym
{
case SymbolKind.Method:
var method = (MethodSymbol)member;
return method.GetConstructedLeastOverriddenMethod(accessingTypeOpt);
return method.GetConstructedLeastOverriddenMethod(accessingTypeOpt, requireSameReturnType: false);

case SymbolKind.Property:
var property = (PropertySymbol)member;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ private sealed class UncommonFields
public ImmutableArray<string> _lazyNotNullMembers;
public ImmutableArray<string> _lazyNotNullMembersWhenTrue;
public ImmutableArray<string> _lazyNotNullMembersWhenFalse;
public MethodSymbol _lazyExplicitClassOverride;
gafter marked this conversation as resolved.
Show resolved Hide resolved
}

private UncommonFields CreateUncommonFields()
Expand Down Expand Up @@ -287,6 +288,11 @@ private UncommonFields CreateUncommonFields()
retVal._lazyNotNullMembersWhenFalse = ImmutableArray<string>.Empty;
}

if (_packedFlags.IsExplicitOverrideIsPopulated)
{
retVal._lazyExplicitClassOverride = null;
}

return retVal;
}

Expand Down Expand Up @@ -1177,7 +1183,13 @@ public override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations
return explicitInterfaceImplementations;
}

var explicitlyOverriddenMethods = ExplicitlyOverriddenMethods();
var moduleSymbol = _containingType.ContainingPEModule;

// Context: we need the containing type of this method as context so that we can substitute appropriately into
// any generic interfaces that we might be explicitly implementing. There is no reason to pass in the method
// context, however, because any method type parameters will belong to the implemented (i.e. interface) method,
// which we do not yet know.
var explicitlyOverriddenMethods = new MetadataDecoder(moduleSymbol, _containingType).GetExplicitlyOverriddenMethods(_containingType.Handle, _handle, this.ContainingType);

//avoid allocating a builder in the common case
var anyToRemove = false;
Expand All @@ -1199,15 +1211,9 @@ public override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations
}
}

// CONSIDER: could assert that we're writing the existing value if it's already there
// CONSIDER: what we'd really like to do is set this bit only in cases where the explicitly
// overridden method matches the method that will be returned by MethodSymbol.OverriddenMethod.
// Unfortunately, this MethodSymbol will not be sufficiently constructed (need IsOverride and MethodKind,
// which depend on this property) to determine which method OverriddenMethod will return.
_packedFlags.InitializeIsExplicitOverride(isExplicitFinalizerOverride: sawObjectFinalize, isExplicitClassOverride: anyToRemove);

explicitInterfaceImplementations = explicitlyOverriddenMethods;

bool hasUniqueClassOverride = false;
if (anyToRemove)
{
var explicitInterfaceImplementationsBuilder = ArrayBuilder<MethodSymbol>.GetInstance();
Expand All @@ -1220,41 +1226,48 @@ public override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations
}

explicitInterfaceImplementations = explicitInterfaceImplementationsBuilder.ToImmutableAndFree();

MethodSymbol uniqueClassOverride = null;
foreach (MethodSymbol method in explicitlyOverriddenMethods)
{
if (method.ContainingType.IsClassType())
{
if (uniqueClassOverride is { })
{
// not unique
uniqueClassOverride = null;
break;
}

uniqueClassOverride = method;
}
}

if (uniqueClassOverride is { })
{
hasUniqueClassOverride = true;
Interlocked.CompareExchange(ref AccessUncommonFields()._lazyExplicitClassOverride, uniqueClassOverride, null);
}
}

// CONSIDER: what we'd really like to do is set this bit only in cases where the explicitly
// overridden method matches the method that will be returned by MethodSymbol.OverriddenMethod.
// Unfortunately, this MethodSymbol will not be sufficiently constructed (need IsOverride and MethodKind,
// which depend on this property) to determine which method OverriddenMethod will return.
_packedFlags.InitializeIsExplicitOverride(isExplicitFinalizerOverride: sawObjectFinalize, isExplicitClassOverride: hasUniqueClassOverride);
gafter marked this conversation as resolved.
Show resolved Hide resolved

return InterlockedOperations.Initialize(ref _lazyExplicitMethodImplementations, explicitInterfaceImplementations);
}
}

private ImmutableArray<MethodSymbol> ExplicitlyOverriddenMethods()
{
var moduleSymbol = _containingType.ContainingPEModule;

// Context: we need the containing type of this method as context so that we can substitute appropriately into
// any generic interfaces that we might be explicitly implementing. There is no reason to pass in the method
// context, however, because any method type parameters will belong to the implemented (i.e. interface) method,
// which we do not yet know.
return new MetadataDecoder(moduleSymbol, _containingType).GetExplicitlyOverriddenMethods(_containingType.Handle, _handle, this.ContainingType);
}

/// <summary>
/// If a methodimpl record indicates a unique overridden method, that method. Otherwise null.
/// </summary>
internal MethodSymbol ExplicitlyOverriddenClassMethod
{
get
{
if (IsExplicitClassOverride)
{
foreach (MethodSymbol method in ExplicitlyOverriddenMethods())
{
if (method.ContainingType.IsClassType())
{
return method;
}
}

throw ExceptionUtilities.Unreachable;
}

return null;
return IsExplicitClassOverride ? AccessUncommonFields()._lazyExplicitClassOverride : null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,21 +535,6 @@ public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
}
}

internal PropertySymbol ExplicitlyOverriddenClassProperty
{
get
{
// A covariant return readonly property override is indicated in metadata by
// a get accessor with an explicit override (methodimpl).
if (GetMethod is PEMethodSymbol { ExplicitlyOverriddenClassMethod: { AssociatedSymbol: PropertySymbol overriddenProperty } })
{
return overriddenProperty;
}

return null;
}
}

public override ImmutableArray<CSharpAttributeData> GetAttributes()
{
if (_lazyCustomAttributes.IsDefault)
Expand Down
20 changes: 17 additions & 3 deletions src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,17 @@ public virtual ImmutableArray<CSharpAttributeData> GetReturnTypeAttributes()
/// </summary>
/// <param name="accessingTypeOpt">The search must respect accessibility from this type.</param>
internal MethodSymbol GetLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt)
{
return GetLeastOverriddenMethodCore(accessingTypeOpt, requireSameReturnType: false);
}

/// <summary>
/// Returns the original virtual or abstract method which a given method symbol overrides,
/// ignoring any other overriding methods in base classes.
/// </summary>
/// <param name="accessingTypeOpt">The search must respect accessibility from this type.</param>
/// <param name="requireSameReturnType">The returned method must have the same return type.</param>
private MethodSymbol GetLeastOverriddenMethodCore(NamedTypeSymbol accessingTypeOpt, bool requireSameReturnType)
{
var accessingType = ((object)accessingTypeOpt == null ? this.ContainingType : accessingTypeOpt).OriginalDefinition;

Expand Down Expand Up @@ -421,7 +432,9 @@ internal MethodSymbol GetLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt)
// See InternalsVisibleToAndStrongNameTests: IvtVirtualCall1, IvtVirtualCall2, IvtVirtual_ParamsAndDynamic.
MethodSymbol overridden = m.OverriddenMethod;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
if ((object)overridden == null || !AccessCheck.IsSymbolAccessible(overridden, accessingType, ref useSiteDiagnostics))
if ((object)overridden == null ||
!AccessCheck.IsSymbolAccessible(overridden, accessingType, ref useSiteDiagnostics) ||
(requireSameReturnType && !this.ReturnType.Equals(overridden.ReturnType, TypeCompareKind.AllIgnoreOptions)))
gafter marked this conversation as resolved.
Show resolved Hide resolved
{
break;
}
Expand All @@ -438,9 +451,10 @@ internal MethodSymbol GetLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt)
/// Also, if the given method symbol is generic then the resulting virtual or abstract method is constructed with the
/// same type arguments as the given method.
/// </summary>
internal MethodSymbol GetConstructedLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt)
/// <param name="requireSameReturnType">The returned method must have the same return type.</param>
internal MethodSymbol GetConstructedLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt, bool requireSameReturnType)
{
var m = this.ConstructedFrom.GetLeastOverriddenMethod(accessingTypeOpt);
var m = this.ConstructedFrom.GetLeastOverriddenMethodCore(accessingTypeOpt, requireSameReturnType);
return m.IsGenericMethod ? m.Construct(this.TypeArgumentsWithAnnotations) : m;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public static bool IsRuntimeFinalizer(this MethodSymbol method, bool skipFirstMe

// At this point, we know method originated with a DestructorDeclarationSyntax in source,
// so it can't have the "new" modifier.
// First is fine, since there should only be one, since there are no parameters.
method = method.GetFirstRuntimeOverriddenMethodIgnoringNewSlot(ignoreInterfaceImplementationChanges: true);
// Unique is fine, since there should only be one, since there are no parameters.
method = method.GetUniqueRuntimeOverriddenMethodIgnoringNewSlot(ignoreInterfaceImplementationChanges: true);
skipFirstMethodKindCheck = false;
}

Expand Down
Loading