Skip to content

Conversation

@AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Jul 23, 2025

Addresses part of #78963 ("switch away from ordinals in skeleton type names")
Relates to test plan #76130

@AlekseyTs AlekseyTs requested review from jcouv and jjonescz July 23, 2025 20:56
@AlekseyTs AlekseyTs requested a review from a team as a code owner July 23, 2025 20:56
@AlekseyTs AlekseyTs added Area-Compilers Feature - Extension Everything The extension everything feature labels Jul 23, 2025
@AlekseyTs
Copy link
Contributor Author

@jcouv, @jjonescz, @dotnet/roslyn-compiler Please review

}
}

internal static void ReverseEndianness(Span<short> shortSpan)
Copy link
Member

@tmat tmat Jul 23, 2025

Choose a reason for hiding this comment

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

You can define this in MS.CA.Contracts project as a static extension polyfill API. #WontFix

@jcouv jcouv self-assigned this Jul 23, 2025
System_Collections_Generic_List_T__AddRange,

System_Runtime_CompilerServices_ParamCollectionAttribute__ctor,
System_Runtime_CompilerServices_ExtensionMarkerNameAttribute__ctor,
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

FWIW, I added two follow-up items to #78963

  • add ExtensionMarkerNameAttribute to BCL
  • disallow ExtensionMarkerName attribute in source #Closed

yield break;
}

bool checkForExtensonGroup = this.IsStatic && this.ContainingType is null && this.TypeKind == TypeKind.Class &&
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

checkForExtensonGroup

Typo: checkForExtensionGroup #Closed

yield break;
}

bool checkForExtensonGroup = this.IsStatic && this.ContainingType is null && this.TypeKind == TypeKind.Class &&
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

Should we also check that this.Arity == 0? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we also check that this.Arity == 0?

Good idea

private class ExtensionInfo(TypeDefinitionHandle groupingType, MethodDefinitionHandle markerMethod)
{
public readonly MethodDefinitionHandle MarkerMethod = markerMethod;
public readonly TypeDefinitionHandle GroupingType = groupingType;
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

GroupingType

nit: consider GroupingTypeHandle #Closed


internal override string ExtensionName
=> Name; // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Confirm implementation
internal override string ExtensionName // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Confirm implementation
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

This was changed in main branch to a specific link:

        internal override string ExtensionName
            => Name; // Tracked by [https://github.com/dotnet/roslyn/issues/78963](https://github.com/dotnet/roslyn/issues/78963) : Revisit when adopting new metadata design with content-based type names

Consider using a PROTOTYPE comment or removing the link entirely since there's a PROTOTYPE in implementation. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider using a PROTOTYPE comment or removing the link entirely since there's a PROTOTYPE in implementation.

Removing


if (IsExtension)
{
// We do not recognize any attributes on extension blocks
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

nit: consider leaving PROTOTYPE comments to cover the changes in GetAttributes, CreateEvents, CreateFields and CreateNestedTypes showing that attributes/events/fields/types in metadata are ignored. #Closed

public override IEnumerable<TypeReferenceWithAttributes> GetConstraints(EmitContext context)
{
// Drop all attributes from constraints, they are about C# specific semantics.
foreach (var constrint in base.GetConstraints(context))
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

constrint

Typo: constraint #Closed

@jcouv
Copy link
Member

jcouv commented Jul 24, 2025

        if (AdaptedNamedTypeSymbol.IsScriptClass || AdaptedNamedTypeSymbol.IsExtension) // Tracked by https://github.com/dotnet/roslyn/issues/76130 : we should have checked the presence of System.Object

Is the extension case still reachable in GetBaseClass? #Closed


Refers to: src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs:293 in 221fc17. [](commit_id = 221fc17, deletion_comment = False)


if (_lazyExtensionInfo.LazyExtensionMarkerName is null)
{
_lazyExtensionInfo.LazyExtensionMarkerName = "<Marker>$" + RawNameToHashString(ComputeExtensionMarkerRawName()); // PROTOTYPE: Add a constant for this prefix.
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

If there are any PROTOTYPE comments in this PR that you don't consider blocking for merging the feature branch back to main, it would be helpful to use linked issues instead or tagging them somehow. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there are any PROTOTYPE comments in this PR that you don't consider blocking for merging the feature branch back to main, it would be helpful to use linked issues instead or tagging them somehow.

The things I would consider truly blocking:

  • dock IDs and doc comments on merged extension blocks
  • Detecting and reporting collisions during merge into grouping and marker types.
  • Finalizing anything that affects metadata names. Prefixes, etc.
  • Test that the right things are dropped from grouping types (we can follow up offline)
  • Test that the right things are preserved on marker types (we can follow up offline)

However, I'd rather not make this decision on my own, hence leaving the comments. The comments are also more preferable for me from the context and time consumption perspective.

{
return ((SourceMemberContainerTypeSymbol)containingType.ContainingType).GetExtensionGroupingInfo().GetCorrespondingMarkerType((SourceNamedTypeSymbol)containingType);
}
else if (containingType.IsExtension)
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

containingType.IsExtension

nit: consider AdaptedMethodSymbol.GetIsNewExtensionMember() to be consistent with other adapter checks (I know that we already have containingType at hand above, but the consistency improves readability somewhat) #Closed

{
internal sealed class ExtensionGroupingInfo
{
private readonly Dictionary<string, MultiDictionary<string, SourceNamedTypeSymbol>> _groupingMap;
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

nit: Consider documenting. If I understood correctly, the outer string key is a grouping name, the inner string key is a marker name, and the named types are the extension declarations
nit: it may be good to extract the computation logic that creates the dictionary and ExtensionGroupingInfo from GetExtensionGroupingInfo and move it into ExtensionGroupingInfo so the logic constructing this dictionary is closer to the dictionary's definition. #Closed

}
}

private class ExtensionGroupingTypeParameter : InheritedTypeParameter
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

ExtensionGroupingTypeParameter

nit: ExtensionGroupingTypeTypeParameter? #Closed

int IComparable<ExtensionGroupingType>.CompareTo(ExtensionGroupingType? other)
{
Debug.Assert(other is { });
return ExtensionMarkerTypes[0].CompareTo(other.ExtensionMarkerTypes[0]);
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

Could we simply compare names? Question also applies to ExtensionMarkerType comparison #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could we simply compare names?

I think we should stick to the lexical declaration order for emit purposes.

builder.Sort();
ExtensionMarkerTypes = builder.ToImmutableAndFree();

_typeParameters = ExtensionMarkerTypes[0].UnderlyingExtensions[0].Arity != 0 ?
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

Would it be useful to check/assert that the merged signatures are identical (ignoring C#-isms)? That could help catch issues as new features are added in the future and the content-hashes were not updated.
The question also applies to ExtensionMarkerType (that one would not ignore C#-isms)
#Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would it be useful to check/assert that the merged signatures are identical (ignoring C#-isms)?

This check should be performed not as an assert. It is not implemented in this PR and a PROTOTYPE comment is left in SourceMemberContainerTypeSymbol.


protected override bool IsSealed => true;

protected override ITypeDefinition ContainingTypeDefinition => ExtensionMarkerTypes[0].UnderlyingExtensions[0].ContainingType!.GetCciAdapter();
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

!

This should be an assertion (here or possibly in the constructor) #WontFix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be an assertion (here or possibly in the constructor)

I do not think its worth the ceremony. The fact is obvious by construction, i.e. these are nested types symbols.

{
Debug.Assert((object)method != null);

// PROTOTYPE: SynthesizedExtensionMarker should not be added as a member of the extension type.
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

I'm not clear what you intend for SynthesizedExtensionMarker. Consider leaving more details if this is something I'll need to handle #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not clear what you intend for SynthesizedExtensionMarker. Consider leaving more details if this is something I'll need to handle

I went ahead and made the change in this PR


var builder = ArrayBuilder<TResult>.GetInstance();

int index = 0;
Copy link
Member

@jcouv jcouv Jul 24, 2025

Choose a reason for hiding this comment

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

The index seems unused #Closed

@jcouv
Copy link
Member

jcouv commented Jul 24, 2025

""";

  • The tests that show IL show the ExtensionMarkerName attribute, but the value is garbled. Would it be useful to have some direct tests showing the value (ie. the marker name)?
  • Do we have a test with two extension blocks that result in a single extension group with two marker types?
  • Do we have a test with two identical extension blocks that get merged? (we probably need a PROTOTYPE for xml docs) #Closed

Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs:55 in 221fc17. [](commit_id = 221fc17, deletion_comment = False)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (commit 1)

y.OriginalDefinition.AsMember(normalize(yExtension)));
}

private static NamedTypeSymbol normalize(NamedTypeSymbol extension)
Copy link
Member

@jjonescz jjonescz Jul 24, 2025

Choose a reason for hiding this comment

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

Suggested change
private static NamedTypeSymbol normalize(NamedTypeSymbol extension)
private static NamedTypeSymbol Normalize(NamedTypeSymbol extension)
``` #Resolved

}
}

private class ExtensionGroupingTypeParameter : InheritedTypeParameter
Copy link
Member

@jjonescz jjonescz Jul 24, 2025

Choose a reason for hiding this comment

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

Suggested change
private class ExtensionGroupingTypeParameter : InheritedTypeParameter
private sealed class ExtensionGroupingTypeParameter : InheritedTypeParameter
``` #Resolved

Comment on lines 431 to 432
yield return synthesized;
break;
Copy link
Member

@jjonescz jjonescz Jul 24, 2025

Choose a reason for hiding this comment

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

Looks like we don't need an iterator method but can simply return:

Suggested change
yield return synthesized;
break;
return [synthesized];
``` #Resolved

if (marker.TryGetExtensionMarkerMethod() is { IsNil: false } markerHandle)
{
marker = PENamedTypeSymbol.Create(moduleSymbol, this, markerRid, type.Handle, markerHandle);
yield return marker;
Copy link
Member

@jjonescz jjonescz Jul 24, 2025

Choose a reason for hiding this comment

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

Aren't marker types nested in the grouping type? But we seem to be returning them as nested for the extension enclosing static class. #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.

Aren't marker types nested in the grouping type? But we seem to be returning them as nested for the extension enclosing static class.

That is correct, we are transforming what we have in metadata to how the language sees things, i.e. extension blocks directly in the top level static class.

@AlekseyTs
Copy link
Contributor Author

""";

For multiple markers in a group and attribute values adding some extra checks to Implementation_StaticMethod_02_WithLocalFunction and Nullability_Attribute_22.

For merged blocks under one marker adding some extra checks to StaticMethodInvocation_PartialStaticClass. Added a work item for XML docs to #78963.


In reply to: 3111874120


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs:55 in 221fc17. [](commit_id = 221fc17, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

        if (AdaptedNamedTypeSymbol.IsScriptClass || AdaptedNamedTypeSymbol.IsExtension) // Tracked by https://github.com/dotnet/roslyn/issues/76130 : we should have checked the presence of System.Object

Is the extension case still reachable in GetBaseClass?

I do not think so, but I will leave this clean-up for a follow-up.


In reply to: 3111750451


Refers to: src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs:293 in 221fc17. [](commit_id = 221fc17, deletion_comment = False)

@AlekseyTs AlekseyTs requested a review from jcouv July 24, 2025 14:57
Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (commit 3)

@jcouv
Copy link
Member

jcouv commented Jul 24, 2025

""";

Btw, I think this PR closes issue #79043 (adjust conflict rules). I'll add a test and close in a follow-up PR


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs:55 in faf186a. [](commit_id = faf186a, deletion_comment = False)

@jcouv jcouv mentioned this pull request Jul 24, 2025
28 tasks
@AlekseyTs AlekseyTs merged commit f388cb7 into dotnet:features/extensions Jul 24, 2025
28 checks passed
@jcouv
Copy link
Member

jcouv commented Jul 24, 2025

            return new SynthesizedExtensionMarker(this, parameterList);

The marker method symbol still has the extension block as containing type (and when we produce metadata, we re-parent it). Should we attach it directly to the right marker type instead?


Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs:1106 in faf186a. [](commit_id = faf186a, deletion_comment = False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Feature - Extension Everything The extension everything feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants