Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 18 additions & 4 deletions src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,9 @@ public static void CheckAllConstraints(this TypeSymbol type, CheckConstraintsArg

internal readonly struct CheckConstraintsArgs
{
public readonly CSharpCompilation CurrentCompilation;
#nullable enable
public readonly CSharpCompilation? CurrentCompilation;
#nullable disable
public readonly ConversionsBase Conversions;
public readonly bool IncludeNullability;
public readonly Location Location;
Expand Down Expand Up @@ -590,7 +592,12 @@ private static bool CheckConstraintsSingleType(TypeSymbol type, in CheckConstrai
}
else if (type.Kind == SymbolKind.PointerType)
{
Binder.CheckManagedAddr(args.CurrentCompilation, ((PointerTypeSymbol)type).PointedAtType, args.Location, args.Diagnostics);
#nullable enable
if (args.CurrentCompilation is not null)
{
Binder.CheckManagedAddr(args.CurrentCompilation, ((PointerTypeSymbol)type).PointedAtType, args.Location, args.Diagnostics);
}
#nullable disable
}
return false; // continue walking types
}
Expand Down Expand Up @@ -734,6 +741,7 @@ public static bool CheckConstraints(this NamedTypeSymbol type, in CheckConstrain

diagnosticsBuilder.Free();

#nullable enable
// we only check for distinct interfaces when the type is not from source, as we
// trust that types that are from source have already been checked by the compiler
// to prevent this from happening in the first place.
Expand All @@ -742,7 +750,7 @@ public static bool CheckConstraints(this NamedTypeSymbol type, in CheckConstrain
result = false;
args.Diagnostics.Add(ErrorCode.ERR_BogusType, args.Location, type);
}

#nullable disable
return result;
}

Expand Down Expand Up @@ -958,7 +966,8 @@ private static bool CheckBasicConstraints(
{
if (typeParameter.AllowsRefLikeType)
{
if (args.CurrentCompilation.SourceModule != typeParameter.ContainingModule)
#nullable enable
if (args.CurrentCompilation is not null && args.CurrentCompilation.SourceModule != typeParameter.ContainingModule)
{
if (MessageID.IDS_FeatureAllowsRefStructConstraint.GetFeatureAvailabilityDiagnosticInfo(args.CurrentCompilation) is { } diagnosticInfo)
{
Expand All @@ -970,6 +979,7 @@ private static bool CheckBasicConstraints(
diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_RuntimeDoesNotSupportByRefLikeGenerics))));
}
}
#nullable disable
}
else
{
Expand Down Expand Up @@ -1011,6 +1021,7 @@ private static bool CheckBasicConstraints(
}
else if (managedKind == ManagedKind.UnmanagedWithGenerics)
{
#nullable enable
// When there is no compilation, we are being invoked through the API IMethodSymbol.ReduceExtensionMethod(...).
// In that case we consider the unmanaged constraint to be satisfied as if we were compiling with the latest
// language version. The net effect of this is that in some IDE scenarios completion might consider an
Expand All @@ -1024,6 +1035,7 @@ private static bool CheckBasicConstraints(
return false;
}
}
#nullable disable
}
}

Expand Down Expand Up @@ -1203,9 +1215,11 @@ private static void CheckConstraintType(
}
else
{
#nullable enable
SymbolDistinguisher distinguisher = new SymbolDistinguisher(args.CurrentCompilation, constraintType.Type, typeArgument.Type);
constraintTypeErrorArgument = distinguisher.First;
typeArgumentErrorArgument = distinguisher.Second;
#nullable disable
}

diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(errorCode, containingSymbol.ConstructedFrom(), constraintTypeErrorArgument, typeParameter, typeArgumentErrorArgument))));
Expand Down
20 changes: 9 additions & 11 deletions src/Compilers/CSharp/Portable/Symbols/SymbolDistinguisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// 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 Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities;
using System;
Expand All @@ -24,13 +22,13 @@ namespace Microsoft.CodeAnalysis.CSharp
/// </remarks>
internal sealed class SymbolDistinguisher
{
private readonly CSharpCompilation _compilation;
private readonly CSharpCompilation? _compilation;
private readonly Symbol _symbol0;
private readonly Symbol _symbol1;

private ImmutableArray<string> _lazyDescriptions;

public SymbolDistinguisher(CSharpCompilation compilation, Symbol symbol0, Symbol symbol1)
public SymbolDistinguisher(CSharpCompilation? compilation, Symbol symbol0, Symbol symbol1)
{
Debug.Assert(symbol0 != symbol1);
CheckSymbolKind(symbol0);
Expand Down Expand Up @@ -96,8 +94,8 @@ private void MakeDescriptions()
Symbol unwrappedSymbol0 = UnwrapSymbol(_symbol0);
Symbol unwrappedSymbol1 = UnwrapSymbol(_symbol1);

string location0 = GetLocationString(_compilation, unwrappedSymbol0);
string location1 = GetLocationString(_compilation, unwrappedSymbol1);
string? location0 = GetLocationString(_compilation, unwrappedSymbol0);
string? location1 = GetLocationString(_compilation, unwrappedSymbol1);

// The locations should not be equal, but they might be if the same
// SyntaxTree is referenced by two different compilations.
Expand Down Expand Up @@ -158,7 +156,7 @@ private static Symbol UnwrapSymbol(Symbol symbol)
}
}

private static string GetLocationString(CSharpCompilation compilation, Symbol unwrappedSymbol)
private static string? GetLocationString(CSharpCompilation? compilation, Symbol unwrappedSymbol)
{
Debug.Assert((object)unwrappedSymbol == UnwrapSymbol(unwrappedSymbol));

Expand All @@ -179,10 +177,10 @@ private static string GetLocationString(CSharpCompilation compilation, Symbol un
{
if (compilation != null)
{
PortableExecutableReference metadataReference = compilation.GetMetadataReference(containingAssembly) as PortableExecutableReference;
PortableExecutableReference? metadataReference = compilation.GetMetadataReference(containingAssembly) as PortableExecutableReference;
if (metadataReference != null)
{
string path = metadataReference.FilePath;
string? path = metadataReference.FilePath;
if (!string.IsNullOrEmpty(path))
{
return path;
Expand Down Expand Up @@ -219,7 +217,7 @@ private Symbol GetSymbol()
return (_index == 0) ? _distinguisher._symbol0 : _distinguisher._symbol1;
}

public override bool Equals(object obj)
public override bool Equals(object? obj)
{
var other = obj as Description;
return other != null &&
Expand All @@ -243,7 +241,7 @@ public override string ToString()
return _distinguisher.GetDescription(_index);
}

string IFormattable.ToString(string format, IFormatProvider formatProvider)
string IFormattable.ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
Expand Down
28 changes: 28 additions & 0 deletions src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29198,5 +29198,33 @@ static async IAsyncEnumerator<T> B<T>() where T : allows ref struct
Diagnostic(ErrorCode.ERR_IteratorRefLikeElementType, "B").WithLocation(8, 38)
);
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/78430")]
public void Issue78430()
{
var source =
@"
public ref struct TestStruct
{
public int Prop1 {get; set;}
}

public static class TestClass
{
public static void TestExtensionMethod<T>(this T value)
where T : allows ref struct
{
}
}
";
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90);
CompileAndVerify(comp, verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics();

var testStruct = comp.GetTypeByMetadataName("TestStruct");
var extensionMethodSymbol = comp.GetMember<MethodSymbol>("TestClass.TestExtensionMethod");

AssertEx.Equal("void TestStruct.TestExtensionMethod<TestStruct>()", extensionMethodSymbol.ReduceExtensionMethod(testStruct, null).ToTestDisplayString());
Copy link
Member

@jcouv jcouv May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on MethodSymbol.ReduceExtensionMethod may need to be updated. It says:

        /// <summary>
        /// If this is an extension method that can be applied to a receiver of the given type,
        /// returns a reduced extension method symbol thus formed. Otherwise, returns null.
        /// </summary>
        /// <param name="compilation">The compilation in which constraints should be checked.
        /// Should not be null, but if it is null we treat constraints as we would in the latest
        /// language version.</param>
        public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation compilation, out bool wasFullyInferred)

But in fact, we're going to skip some constraint checks when the compilation is null.
That means we'll return symbols that violate some constraints. That may be worth adding a test to observe. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in fact, we're going to skip some constraint checks when the compilation is null.

What constraint checks are getting skipped?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're skipping a check in CheckConstraintsSingleType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're skipping a check in CheckConstraintsSingleType

It doesn't look like Binder.CheckManagedAddr actually checks any constraints. This helper used in many other settings, I guess it was convenient to include it here as well, as we do with some other checks that aren't primarily related to constraint checking. Besides, it doesn't look like CheckConstraintsSingleType is reachable from ReduceExtensionMethod

}
}
}