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

Add support for checksum-based interceptors #72814

Merged
merged 24 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3f1a038
Add support for checksum-based interceptors
RikkiGibson Mar 30, 2024
53e0548
Fix broken tests
RikkiGibson Apr 1, 2024
f12ce01
use position
RikkiGibson Apr 1, 2024
db13fb5
pass cancellation token
RikkiGibson Apr 1, 2024
2cb8bfd
Pass token
RikkiGibson Apr 1, 2024
355728a
Update baseline
RikkiGibson Apr 1, 2024
2394377
update interceptors.md
RikkiGibson Apr 1, 2024
4b147f7
Fix nullable warning
RikkiGibson Apr 1, 2024
231c190
remove reference to line/column encoding
RikkiGibson Apr 1, 2024
1056037
more feedback
RikkiGibson Apr 1, 2024
ac01bc4
Update src/Compilers/CSharp/Portable/Utilities/ContentHashComparer.cs
RikkiGibson Apr 2, 2024
282bff3
Address feedback
RikkiGibson Apr 2, 2024
5f85fcb
Apply suggestions from code review
RikkiGibson Apr 4, 2024
8ac441d
Apply diagnostics to attribute name in all cases to reduce churn
RikkiGibson Apr 4, 2024
9763c4f
Add tests to exercise 'interceptable syntactically' concept
RikkiGibson Apr 4, 2024
5bef2bc
address some feedback. Port tests for newly added code paths.
RikkiGibson Apr 4, 2024
499fd18
Decode next to encode
RikkiGibson Apr 4, 2024
e1f9273
fix signature
RikkiGibson Apr 4, 2024
13ece53
Address feedback
RikkiGibson Apr 7, 2024
b2b30c5
Merge remote-tracking branch 'upstream/main' into interceptablelocation
RikkiGibson Apr 7, 2024
00e90be
Adjust error codes
RikkiGibson Apr 7, 2024
2d65d38
Also test other display location
RikkiGibson Apr 7, 2024
e8aed9f
Merge branch 'main' of https://github.com/dotnet/roslyn into intercep…
RikkiGibson Apr 12, 2024
9230536
pack
RikkiGibson Apr 12, 2024
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
45 changes: 22 additions & 23 deletions docs/features/interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ using System;
using System.Runtime.CompilerServices;

var c = new C();
c.InterceptableMethod(1); // (L1,C1): prints "interceptor 1"
c.InterceptableMethod(1); // (L2,C2): prints "other interceptor 1"
c.InterceptableMethod(2); // (L3,C3): prints "other interceptor 2"
c.InterceptableMethod(1); // L1: prints "interceptor 1"
c.InterceptableMethod(1); // L2: prints "other interceptor 1"
c.InterceptableMethod(2); // L3: prints "other interceptor 2"
c.InterceptableMethod(1); // prints "interceptable 1"

class C
Expand All @@ -28,14 +28,14 @@ class C
// generated code
static class D
{
[InterceptsLocation("Program.cs", line: /*L1*/, character: /*C1*/)] // refers to the call at (L1, C1)
[InterceptsLocation(version: 1, data: "...(refers to the call at L1")]
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
public static void InterceptorMethod(this C c, int param)
{
Console.WriteLine($"interceptor {param}");
}

[InterceptsLocation("Program.cs", line: /*L2*/, character: /*C2*/)] // refers to the call at (L2, C2)
[InterceptsLocation("Program.cs", line: /*L3*/, character: /*C3*/)] // refers to the call at (L3, C3)
[InterceptsLocation(version: 1, data: "...(refers to the call at L2")]
[InterceptsLocation(version: 1, data: "...(refers to the call at L3")]
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
public static void OtherInterceptorMethod(this C c, int param)
{
Console.WriteLine($"other interceptor {param}");
Expand All @@ -54,7 +54,7 @@ A method indicates that it is an *interceptor* by adding one or more `[Intercept
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
public sealed class InterceptsLocationAttribute(int version, string data) : Attribute
{
}
}
Expand All @@ -66,29 +66,28 @@ Any "ordinary method" (i.e. with `MethodKind.Ordinary`) can have its calls inter

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.

#### File paths
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.

The *referenced syntax tree* of an `[InterceptsLocation]` is determined by normalizing the `filePath` argument value relative to the path of the containing syntax tree of the `[InterceptsLocation]` usage, similar to how paths in `#line` directives are normalized. Let this normalized path be called `normalizedInterceptorPath`. If exactly one syntax tree in the compilation has a normalized path which matches `normalizedInterceptorPath` by ordinal string comparison, that is the *referenced syntax tree*. Otherwise, an error occurs.
#### Location encoding

`#line` directives are not considered when determining the call referenced by an `[InterceptsLocation]` attribute. In other words, the file path, line and column numbers used in `[InterceptsLocation]` are expected to refer to *unmapped* source locations.
The arguments to `[InterceptsLocation]` are:
1. a version number. The compiler may introduce new encodings for the location in the future, with corresponding new version numbers.
Copy link
Member

@jcouv jcouv Apr 3, 2024

Choose a reason for hiding this comment

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

Was there a discussion of embedding the version number directly in the opaque data string? Maybe <version>-<data>?
The version number doesn't seem especially useful when I look at an attribute like [InterceptsLocation(1, "yPU5+1/pMuRHlz+XbnIQwQYAAAARAAAAUHJvZ3JhbS5jcw==")]

Also, consider adding an example of location encoding in the doc to show what it looks like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was thought that this gives the ability to change the precise encoding used in the data string, e.g. maybe in the future we want something different than base64, splitting out the version gives us the ability to express that.

Copy link
Contributor Author

@RikkiGibson RikkiGibson Apr 3, 2024

Choose a reason for hiding this comment

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

Looked again and I think I better understand the suggestion, which was to have a prefix that isn't necessarily encoded the same way. Yes, it is expressible either way. The idea of combining them together for the "opaque encoding" approach didn't come up in API review.

The only reason I can think of to keep it this way is to separate parts that we think should be user-readable from those that are not, when it comes to diagnosing version incompatibility issues (which would arise from compiling later interceptors code in an earlier compiler.)

2. an opaque data string. This is not intended to be human-readable.

Temporarily, for compatibility purposes, when the initial matching strategy outlined above fails to match any syntax trees, we will fall back to a "compat" matching strategy which works in the following way:
- A *mapped path* of each syntax tree is determined by applying [`/pathmap`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.commandlinearguments.pathmap?view=roslyn-dotnet-4.7.0) substitution to `SyntaxTree.FilePath`.
- For a given `[InterceptsLocation]` usage, the `filePath` argument value is compared to the *mapped path* of each syntax tree using ordinal string comparison. If exactly one syntax tree matches under this comparison, that is the *referenced syntax tree*. Otherwise, an error occurs.

Support for the "compat" strategy will be dropped prior to stable release. Tracked by https://github.com/dotnet/roslyn/issues/72265.
The "version 1" data encoding is a base64-encoded string consisting of the following data:
- 16 byte xxHash128 content checksum of the file containing the intercepted call.
- int32 in little-endian format for the position (i.e. `SyntaxNode.Position`) of the call in syntax.
- utf-8 string data containing a display file name, used for error reporting.
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved

#### Position

Line and column numbers in `[InterceptsLocation]` are 1-indexed to match existing places where source locations are displayed to the user. For example, in `Diagnostic.ToString`.
Line and column numbers in the "version 1" data encoding are 1-indexed to match existing places where source locations are displayed to the user. For example, in `Diagnostic.ToString`.
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved

The location of the call is the location of the simple name syntax which denotes the interceptable method. For example, in `app.MapGet(...)`, the name syntax for `MapGet` would be considered the location of the call. For a static method call like `System.Console.WriteLine(...)`, the name syntax for `WriteLine` is the location of the call. If we allow intercepting calls to property accessors in the future (e.g `obj.Property`), we would also be able to use the name syntax in this way.

#### Attribute creation

The goal of the above decisions is to make it so that when source generators are filling in `[InterceptsLocation(...)]`, they simply need to read `nameSyntax.SyntaxTree.FilePath` and `nameSyntax.GetLineSpan().Span.Start` for the exact file path and position information they need to use.

We should provide samples of recommended coding patterns for generator authors to show correct usage of these, including the "translation" from 0-indexed to 1-indexed positions.
Roslyn provides a convenience API, `GetInterceptableLocation(this SemanticModel, InvocationExpressionSyntax, CancellationToken)` for inserting `[InterceptsLocation]` into generated source code. We recommend that source generators depend on this API in order to intercept calls.

### Non-invocation method usages

Expand All @@ -103,7 +102,7 @@ Interceptors cannot be declared in generic types at any level of nesting.
Interceptors must either be non-generic, or have arity equal to the sum of the arity of the original method's arity and containing type arities. For example:

```cs
Grandparent<int>.Parent<bool>.Original<string>(1, false, "a");
Grandparent<int>.Parent<bool>.Original<string>(1, false, "a"); // L1

class Grandparent<T1>
{
Expand All @@ -115,7 +114,7 @@ class Grandparent<T1>

class Interceptors
{
[InterceptsLocation("Program.cs", 1, 33)]
[InterceptsLocation(1, "..(refers to call at L1)")]
public static void Interceptor<T1, T2, T3>(T1 t1, T2 t2, T3 t3) { }
}
```
Expand All @@ -136,13 +135,13 @@ static class Program
{
public static void M<T2>(T2 t)
{
C.InterceptableMethod(t);
C.InterceptableMethod(t); // L1
}
}

static class D
{
[InterceptsLocation("Program.cs", 12, 11)]
[InterceptsLocation(1, "..(refers to call at L1)")]
public static void Interceptor1<T2>(T2 t) => throw null!;
}
```
Expand Down
20 changes: 20 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,26 @@ public static Conversion ClassifyConversion(this SemanticModel? semanticModel, i
var csModel = semanticModel as CSharpSemanticModel;
return csModel?.GetInterceptorMethod(node, cancellationToken);
}

/// <summary>
/// If 'node' cannot be intercepted syntactically, returns null.
Copy link
Member

@cston cston Apr 2, 2024

Choose a reason for hiding this comment

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

syntactically

What does it mean to be "intercepted syntactically"? Perhaps drop "syntactically" if it is not adding anything. #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.

c.P() where P is defined as Action P { get; } can be intercepted syntactically but an error will occur when you actually try to do it. Basically, the comment is meant to caution that even if you get a non-null location out, intercepting may still fail.

Copy link
Member

@jcouv jcouv Apr 3, 2024

Choose a reason for hiding this comment

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

Do we have a test for the case of a null return because the location is syntactically considered to be not-interceptable? #Closed

RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
/// 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)]
public static InterceptableLocation? GetInterceptableLocation(this SemanticModel? semanticModel, InvocationExpressionSyntax node, CancellationToken cancellationToken = default)
{
var csModel = semanticModel as CSharpSemanticModel;
return csModel?.GetInterceptableLocation(node, cancellationToken);
}

/// <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)]
public static string GetInterceptsLocationAttributeSyntax(this InterceptableLocation location)
{
return $"""[global::System.Runtime.CompilerServices.InterceptsLocationAttribute({location.Version}, "{location.Data}")]""";
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
}
#endregion
}
}
15 changes: 15 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7905,4 +7905,19 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_NoModifiersOnUsing" xml:space="preserve">
<value>Modifiers cannot be placed on using declarations</value>
</data>
<data name="ERR_InterceptsLocationDataInvalidFormat" xml:space="preserve">
<value>The data argument to InterceptsLocationAttribute is not in the correct format.</value>
</data>
<data name="ERR_InterceptsLocationUnsupportedVersion" xml:space="preserve">
<value>Version '{0}' of the interceptors format is not supported. The latest supported version is '1'.</value>
</data>
<data name="ERR_InterceptsLocationDuplicateFile" xml:space="preserve">
<value>Cannot intercept a call in file '{0}' because it is duplicated elsewhere in the compilation.</value>
</data>
<data name="ERR_InterceptsLocationFileNotFound" xml:space="preserve">
<value>Cannot intercept a call in file '{0}' because a matching file was not found in the compilation.</value>
</data>
<data name="ERR_InterceptsLocationDataInvalidPosition" xml:space="preserve">
<value>The data argument to InterceptsLocationAttribute refers to an invalid position in file '{0}'.</value>
</data>
</root>
58 changes: 50 additions & 8 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -160,8 +161,12 @@ internal Conversions Conversions
private ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> _mappedPathToSyntaxTree;

/// <summary>Lazily caches SyntaxTrees by their path. Used to look up the syntax tree referenced by an interceptor.</summary>
/// <remarks>Must be removed prior to interceptors stable release.</remarks>
private ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> _pathToSyntaxTree;

/// <summary>Lazily caches SyntaxTrees by their xxHash128 checksum. Used to look up the syntax tree referenced by an interceptor.</summary>
private ImmutableSegmentedDictionary<ReadOnlyMemory<byte>, OneOrMany<SyntaxTree>> _contentHashToSyntaxTree;

public override string Language
{
get
Expand Down Expand Up @@ -1075,6 +1080,45 @@ ImmutableSegmentedDictionary<string, OneOrMany<SyntaxTree>> computeMappedPathToS
}
}

private class ContentHashComparer : IEqualityComparer<ReadOnlyMemory<byte>>
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
{
public bool Equals(ReadOnlyMemory<byte> x, ReadOnlyMemory<byte> y)
{
return x.Span.SequenceEqual(y.Span);
}

public int GetHashCode(ReadOnlyMemory<byte> obj)
{
return BinaryPrimitives.ReadInt32LittleEndian(obj.Span);
}

public static ContentHashComparer Instance { get; } = new ContentHashComparer();
}

internal OneOrMany<SyntaxTree> GetSyntaxTreesByContentHash(ReadOnlyMemory<byte> contentHash)
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
{
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
var contentHashToSyntaxTree = _contentHashToSyntaxTree;
if (contentHashToSyntaxTree.IsDefault)
{
RoslynImmutableInterlocked.InterlockedInitialize(ref _contentHashToSyntaxTree, computeHashToSyntaxTree());
contentHashToSyntaxTree = _contentHashToSyntaxTree;
}

return contentHashToSyntaxTree.TryGetValue(contentHash, out var value) ? value : OneOrMany<SyntaxTree>.Empty;

ImmutableSegmentedDictionary<ReadOnlyMemory<byte>, OneOrMany<SyntaxTree>> computeHashToSyntaxTree()
{
var builder = ImmutableSegmentedDictionary.CreateBuilder<ReadOnlyMemory<byte>, OneOrMany<SyntaxTree>>(ContentHashComparer.Instance);
foreach (var tree in SyntaxTrees)
{
var text = tree.GetText();
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
var hash = text.GetContentHash().AsMemory();
builder[hash] = builder.TryGetValue(hash, out var existing) ? existing.Add(tree) : OneOrMany.Create(tree);
}
return builder.ToImmutable();
}
}

internal OneOrMany<SyntaxTree> GetSyntaxTreesByPath(string path)
{
// We could consider storing this on SyntaxAndDeclarationManager instead, and updating it incrementally.
Expand Down Expand Up @@ -2399,15 +2443,15 @@ internal void AddModuleInitializerMethod(MethodSymbol method)
internal bool InterceptorsDiscoveryComplete;

// NB: the 'Many' case for these dictionary values means there are duplicates. An error is reported for this after binding.
private ConcurrentDictionary<(string FilePath, int Line, int Character), OneOrMany<(Location AttributeLocation, MethodSymbol Interceptor)>>? _interceptions;
private ConcurrentDictionary<(string FilePath, int Position), OneOrMany<(Location AttributeLocation, MethodSymbol Interceptor)>>? _interceptions;

internal void AddInterception(string filePath, int line, int character, Location attributeLocation, MethodSymbol interceptor)
internal void AddInterception(string filePath, int position, Location attributeLocation, MethodSymbol interceptor)
{
Debug.Assert(!_declarationDiagnosticsFrozen);
Debug.Assert(!InterceptorsDiscoveryComplete);

var dictionary = LazyInitializer.EnsureInitialized(ref _interceptions);
dictionary.AddOrUpdate((filePath, line, character),
dictionary.AddOrUpdate((filePath, position),
addValueFactory: static (key, newValue) => OneOrMany.Create(newValue),
updateValueFactory: static (key, existingValues, newValue) =>
{
Expand All @@ -2427,9 +2471,9 @@ internal void AddInterception(string filePath, int line, int character, Location
factoryArgument: (AttributeLocation: attributeLocation, Interceptor: interceptor));
}

internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(Location? callLocation)
internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(SimpleNameSyntax? node)
{
if (callLocation is null || !callLocation.IsInSource)
if (node is null)
{
return null;
}
Expand All @@ -2440,9 +2484,7 @@ internal void AddInterception(string filePath, int line, int character, Location
return null;
}

var callLineColumn = callLocation.GetLineSpan().Span.Start;
var key = (callLocation.SourceTree.FilePath, callLineColumn.Line, callLineColumn.Character);

var key = (node.SyntaxTree.FilePath, node.Position);
if (_interceptions.TryGetValue(key, out var interceptionsAtAGivenLocation) && interceptionsAtAGivenLocation is [var oneInterception])
{
return oneInterception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5207,13 +5207,34 @@ protected sealed override ISymbol GetDeclaredSymbolCore(SyntaxNode node, Cancell

CheckSyntaxNode(node);

if (node.GetInterceptableNameSyntax() is { } nameSyntax && Compilation.TryGetInterceptor(nameSyntax.GetLocation()) is (_, MethodSymbol interceptor))
if (node.GetInterceptableNameSyntax() is { } nameSyntax && Compilation.TryGetInterceptor(nameSyntax) is (_, MethodSymbol interceptor))
{
return interceptor.GetPublicSymbol();
}

return null;
}

#pragma warning disable RSEXPERIMENTAL002 // Internal usage of experimental API
public InterceptableLocation? GetInterceptableLocation(InvocationExpressionSyntax node, CancellationToken cancellationToken)
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
{
CheckSyntaxNode(node);
if (node.GetInterceptableNameSyntax() is not { } nameSyntax)
{
return null;
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
}

var tree = node.SyntaxTree;
var text = tree.GetText(cancellationToken);
var path = tree.FilePath;
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved
var checksum = text.GetContentHash();
RikkiGibson marked this conversation as resolved.
Show resolved Hide resolved

var lineSpan = nameSyntax.Location.GetLineSpan().Span.Start;
var lineNumberOneIndexed = lineSpan.Line + 1;
var characterNumberOneIndexed = lineSpan.Character + 1;
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved

return new InterceptableLocation1(checksum, path, nameSyntax.Position, lineNumberOneIndexed, characterNumberOneIndexed);
}
#nullable disable

protected static SynthesizedPrimaryConstructor TryGetSynthesizedPrimaryConstructor(TypeDeclarationSyntax node, NamedTypeSymbol type)
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2302,6 +2302,12 @@ internal enum ErrorCode

ERR_NoModifiersOnUsing = 9229,

ERR_InterceptsLocationDataInvalidFormat = 9230,
ERR_InterceptsLocationUnsupportedVersion = 9231,
ERR_InterceptsLocationDuplicateFile = 9232,
ERR_InterceptsLocationFileNotFound = 9233,
ERR_InterceptsLocationDataInvalidPosition = 9234,

#endregion

// Note: you will need to do the following after adding warnings:
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2431,6 +2431,11 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_ParamsCollectionExtensionAddMethod:
case ErrorCode.ERR_ParamsCollectionMissingConstructor:
case ErrorCode.ERR_NoModifiersOnUsing:
case ErrorCode.ERR_InterceptsLocationDataInvalidFormat:
case ErrorCode.ERR_InterceptsLocationUnsupportedVersion:
case ErrorCode.ERR_InterceptsLocationDuplicateFile:
case ErrorCode.ERR_InterceptsLocationFileNotFound:
case ErrorCode.ERR_InterceptsLocationDataInvalidPosition:
return false;
default:
// NOTE: All error codes must be explicitly handled in this switch statement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ private PEModuleBuilder? EmitModule
{
Debug.Assert(!nameofOperator.WasCompilerGenerated);
var nameofIdentiferSyntax = (IdentifierNameSyntax)((InvocationExpressionSyntax)nameofOperator.Syntax).Expression;
if (this._compilation.TryGetInterceptor(nameofIdentiferSyntax.Location) is not null)
if (this._compilation.TryGetInterceptor(nameofIdentiferSyntax) is not null)
{
this._diagnostics.Add(ErrorCode.ERR_InterceptorCannotInterceptNameof, nameofIdentiferSyntax.Location);
}
Expand Down
Loading
Loading