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

Mark interceptors feature as stable and deprecate file path based attributes #76312

Merged
merged 18 commits into from
Dec 13, 2024
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
8 changes: 3 additions & 5 deletions docs/features/interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Summary
[summary]: #summary

*Interceptors* are an experimental compiler feature planned to ship in .NET 8 (with support for C# only). The feature may be subject to breaking changes or removal in a future release.
*Interceptors* is a C# compiler feature, first shipped experimentally in .NET 8, with stable support in .NET 9.0.2xx SDK and later.

An *interceptor* is a method which can declaratively substitute a call to an *interceptable* method with a call to itself at compile time. This substitution occurs by having the interceptor declare the source locations of the calls that it intercepts. This provides a limited facility to change the semantics of existing code by adding new code to a compilation (e.g. in a source generator).

Expand Down Expand Up @@ -68,8 +68,6 @@ In addition to "ordinary" forms `M()` and `receiver.M()`, a call within a condit

File-local declarations of this type (`file class InterceptsLocationAttribute`) are valid and usages are recognized by the compiler when they are within the same file and compilation. A generator which needs to declare this attribute should use a file-local declaration to ensure it doesn't conflict with other generators that need to do the same thing.

In prior experimental releases of the feature, a well-known constructor signature `InterceptsLocation(string path, int line, int column)]` was also supported. Support for this constructor will be **dropped** prior to stable release of the feature.

#### Location encoding

The arguments to `[InterceptsLocation]` are:
Expand Down Expand Up @@ -212,9 +210,9 @@ The reason we permit implicit receiver capture for the above intercepted call is

### Editor experience

Interceptors are treated like a post-compilation step in this design. Diagnostics are given for misuse of interceptors, but some diagnostics are only given in the command-line build and not in the IDE. There is limited traceability in the editor for which calls in a compilation are actually being intercepted. If this feature is brought forward past the experimental stage, this limitation will need to be re-examined.
Interceptors are treated like a post-compilation step in this design. Diagnostics are given for misuse of interceptors, but some diagnostics are only given in the command-line build and not in the IDE. There is limited traceability in the editor for which calls in a compilation are actually being intercepted.

There is an experimental public API `GetInterceptorMethod(this SemanticModel, InvocationExpressionSyntax, CancellationToken)` which enables analyzers to determine if a call is being intercepted, and if so, which method is intercepting the call. See https://github.com/dotnet/roslyn/issues/72093 for further details.
`GetInterceptorMethod(this SemanticModel, InvocationExpressionSyntax, CancellationToken)` enables analyzers to determine if a call is being intercepted, and if so, which method is intercepting the call. See https://github.com/dotnet/roslyn/issues/72093 for further details.

### User opt-in

Expand Down
9 changes: 6 additions & 3 deletions src/Compilers/CSharp/Portable/CSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1632,7 +1632,8 @@ public static Conversion ClassifyConversion(this SemanticModel? semanticModel, i
}

/// <summary>If the call represented by <paramref name="node"/> is referenced in an InterceptsLocationAttribute, returns the original definition symbol which is decorated with that attribute. Otherwise, returns null.</summary>
[Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)]
/// <seealso href="https://github.com/dotnet/roslyn/issues/72093"/>
/// <seealso href="https://github.com/dotnet/csharplang/issues/7009"/>
public static IMethodSymbol? GetInterceptorMethod(this SemanticModel? semanticModel, InvocationExpressionSyntax node, CancellationToken cancellationToken = default)
{
var csModel = semanticModel as CSharpSemanticModel;
Expand All @@ -1643,7 +1644,8 @@ public static Conversion ClassifyConversion(this SemanticModel? semanticModel, i
/// If <paramref name="node"/> cannot be intercepted syntactically, returns null.
/// Otherwise, returns an instance which can be used to intercept the call denoted by <paramref name="node"/>.
/// </summary>
[Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)]
/// <seealso href="https://github.com/dotnet/roslyn/issues/72133" />
/// <seealso href="https://github.com/dotnet/csharplang/issues/7009" />
public static InterceptableLocation? GetInterceptableLocation(this SemanticModel? semanticModel, InvocationExpressionSyntax node, CancellationToken cancellationToken = default)
{
var csModel = semanticModel as CSharpSemanticModel;
Expand All @@ -1653,7 +1655,8 @@ public static Conversion ClassifyConversion(this SemanticModel? semanticModel, i
/// <summary>
/// Gets an attribute list syntax consisting of an InterceptsLocationAttribute, which intercepts the call referenced by parameter <paramref name="location"/>.
/// </summary>
[Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)]
/// <seealso href="https://github.com/dotnet/roslyn/issues/72133" />
/// <seealso href="https://github.com/dotnet/csharplang/issues/7009" />
public static string GetInterceptsLocationAttributeSyntax(this InterceptableLocation location)
{
return $"""[global::System.Runtime.CompilerServices.InterceptsLocationAttribute({location.Version}, "{location.Data}")]""";
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -8032,4 +8032,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_UnscopedRefAttributeOldRules_Title" xml:space="preserve">
<value>UnscopedRefAttribute is only valid in C# 11 or later or when targeting net7.0 or later.</value>
</data>
<data name="WRN_InterceptsLocationAttributeUnsupportedSignature" xml:space="preserve">
<value>'InterceptsLocationAttribute(string, int, int)' is not supported. Move to 'InterceptableLocation'-based generation of these attributes instead. (https://github.com/dotnet/roslyn/issues/72133)</value>
</data>
<data name="WRN_InterceptsLocationAttributeUnsupportedSignature_Title" xml:space="preserve">
<value>'InterceptsLocationAttribute(string, int, int)' is not supported. Move to 'InterceptableLocation'-based generation of these attributes instead. (https://github.com/dotnet/roslyn/issues/72133)</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2354,6 +2354,7 @@ internal enum ErrorCode
WRN_ExperimentalWithMessage = 9268,

WRN_UnscopedRefAttributeOldRules = 9269,
WRN_InterceptsLocationAttributeUnsupportedSignature = 9270,

// Note: you will need to do the following after adding errors:
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_UninitializedNonNullableBackingField:
case ErrorCode.WRN_AccessorDoesNotUseBackingField:
case ErrorCode.WRN_UnscopedRefAttributeOldRules:
case ErrorCode.WRN_InterceptsLocationAttributeUnsupportedSignature:
return 1;
default:
return 0;
Expand Down Expand Up @@ -2471,6 +2472,7 @@ or ErrorCode.WRN_UnassignedInternalRefField
or ErrorCode.WRN_AccessorDoesNotUseBackingField
or ErrorCode.ERR_IteratorRefLikeElementType
or ErrorCode.WRN_UnscopedRefAttributeOldRules
or ErrorCode.WRN_InterceptsLocationAttributeUnsupportedSignature
=> false,
};
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
Expand Down

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

2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#nullable enable
[RSEXPERIMENTAL001]Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSemanticModel(Microsoft.CodeAnalysis.SyntaxTree! syntaxTree, Microsoft.CodeAnalysis.SemanticModelOptions options) -> Microsoft.CodeAnalysis.SemanticModel!
[RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptorMethod(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IMethodSymbol?
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptorMethod(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IMethodSymbol?
~Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken asyncKeyword, Microsoft.CodeAnalysis.SyntaxToken delegateKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode body) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax
~Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken asyncKeyword, Microsoft.CodeAnalysis.SyntaxToken delegateKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax
~Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax.WithAsyncKeyword(Microsoft.CodeAnalysis.SyntaxToken asyncKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousMethodExpressionSyntax
Expand Down
16 changes: 8 additions & 8 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
Microsoft.CodeAnalysis.CSharp.Conversion.IsSpan.get -> bool
Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp13 = 1300 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion
[RSEXPERIMENTAL002]Microsoft.CodeAnalysis.CSharp.InterceptableLocation
[RSEXPERIMENTAL002]abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Data.get -> string!
[RSEXPERIMENTAL002]abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.GetDisplayLocation() -> string!
[RSEXPERIMENTAL002]abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Version.get -> int
Microsoft.CodeAnalysis.CSharp.InterceptableLocation
abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Data.get -> string!
abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.GetDisplayLocation() -> string!
abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Version.get -> int
Microsoft.CodeAnalysis.CSharp.SyntaxKind.RazorContentToken = 8523 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
[RSEXPERIMENTAL002]override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Equals(object? obj) -> bool
[RSEXPERIMENTAL002]override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.GetHashCode() -> int
[RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptableLocation(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.CSharp.InterceptableLocation?
[RSEXPERIMENTAL002]static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptsLocationAttributeSyntax(this Microsoft.CodeAnalysis.CSharp.InterceptableLocation! location) -> string!
override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.Equals(object? obj) -> bool
override abstract Microsoft.CodeAnalysis.CSharp.InterceptableLocation.GetHashCode() -> int
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptableLocation(this Microsoft.CodeAnalysis.SemanticModel? semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.InvocationExpressionSyntax! node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.CSharp.InterceptableLocation?
static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInterceptsLocationAttributeSyntax(this Microsoft.CodeAnalysis.CSharp.InterceptableLocation! location) -> string!
[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser
[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Dispose() -> void
[RSEXPERIMENTAL003]Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.ParseLeadingTrivia() -> Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser.Result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,6 @@ static void reportFeatureNotEnabled(BindingDiagnosticBag diagnostics, Location a
}
}

// https://github.com/dotnet/roslyn/issues/72265: Remove support for path-based interceptors prior to stable release.
private void DecodeInterceptsLocationAttributeExperimentalCompat(
DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments,
string? attributeFilePath,
Expand All @@ -1171,6 +1170,8 @@ private void DecodeInterceptsLocationAttributeExperimentalCompat(
var attributeSyntax = arguments.AttributeSyntaxOpt;
Debug.Assert(attributeSyntax is object);
var attributeLocation = attributeSyntax.Location;
diagnostics.Add(ErrorCode.WRN_InterceptsLocationAttributeUnsupportedSignature, attributeLocation);
Copy link
Member

Choose a reason for hiding this comment

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

We've added a warning, but it doesn't look like support was actually removed. Is that intended? Should we keep the tracking issue to remove the logic below at some point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct, the warning is here to give folks notice that we will fully eliminate support here. At which point the warning will likely be upgraded to error.


const int filePathParameterIndex = 0;
const int lineNumberParameterIndex = 1;
const int characterNumberParameterIndex = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

namespace Microsoft.CodeAnalysis.CSharp;

[Experimental(RoslynExperiments.Interceptors, UrlFormat = RoslynExperiments.Interceptors_Url)]
/// <summary>Denotes an interceptable call. Used by source generators to generate '[InterceptsLocation]' attributes.</summary>
/// <seealso href="https://github.com/dotnet/roslyn/issues/72133" />
/// <seealso href="https://github.com/dotnet/csharplang/issues/7009" />
public abstract class InterceptableLocation
{
private protected InterceptableLocation() { }
Expand Down
10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

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

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

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

10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

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

Loading
Loading