diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index 0487dac7031d6..2c278a0885127 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -7592,4 +7592,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Nullability of reference types in return type doesn't match interceptable method.
+
+ A nameof operator cannot be intercepted.
+
\ No newline at end of file
diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
index f1e821455f710..21ec5d24387f2 100644
--- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
+++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
@@ -2288,7 +2288,7 @@ internal void AddInterception(string filePath, int line, int character, Location
factoryArgument: (AttributeLocation: attributeLocation, Interceptor: interceptor));
}
- internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(Location? callLocation, BindingDiagnosticBag diagnostics)
+ internal (Location AttributeLocation, MethodSymbol Interceptor)? TryGetInterceptor(Location? callLocation)
{
if (_interceptions is null || callLocation is null)
{
@@ -2306,10 +2306,9 @@ internal void AddInterception(string filePath, int line, int character, Location
return oneInterception;
}
- // We don't normally reach this branch in batch compilation, because we would have already reported an error after the declaration phase.
- // One scenario where we may reach this is when validating used assemblies, which performs lowering of method bodies even if declaration errors would be reported.
- // See 'CSharpCompilation.GetCompleteSetOfUsedAssemblies'.
- diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, callLocation, this.SourceModule.Name, new LocalizableResourceString(nameof(CSharpResources.ERR_DuplicateInterceptor), CodeAnalysisResources.ResourceManager, typeof(CodeAnalysisResources)));
+ // Duplicate interceptors is an error in the declaration phase.
+ // This method is only expected to be called if no such errors are present.
+ throw ExceptionUtilities.Unreachable();
}
return null;
@@ -3274,8 +3273,6 @@ internal override bool CompileMethods(
bool hasDeclarationErrors = !FilterAndAppendDiagnostics(diagnostics, GetDiagnostics(CompilationStage.Declare, true, cancellationToken), excludeDiagnostics, cancellationToken);
excludeDiagnostics?.Free();
- hasDeclarationErrors |= CheckDuplicateInterceptions(diagnostics);
-
// TODO (tomat): NoPIA:
// EmbeddedSymbolManager.MarkAllDeferredSymbolsAsReferenced(this)
@@ -3416,8 +3413,8 @@ private bool CheckDuplicateFilePaths(DiagnosticBag diagnostics)
return visitor.CheckDuplicateFilePathsAndFree(SyntaxTrees, GlobalNamespace);
}
- /// if duplicate interceptors are present in the compilation. Otherwise, .
- private bool CheckDuplicateInterceptions(DiagnosticBag diagnostics)
+ /// if duplicate interceptions are present in the compilation. Otherwise, .
+ internal bool CheckDuplicateInterceptions(BindingDiagnosticBag diagnostics)
{
if (_interceptions is null)
{
diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
index 5f8259359fa82..655f8dbc89f6d 100644
--- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
+++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
@@ -120,10 +120,7 @@ public static void CompileMethodBodies(
Debug.Assert(diagnostics != null);
Debug.Assert(diagnostics.DiagnosticBag != null);
- // PROTOTYPE(ic):
- // - Move check for duplicate interceptions in here.
- // - Change lowering to throw on duplicates.
- // - Test no duplicate error given when emitting a ref assembly.
+ hasDeclarationErrors |= compilation.CheckDuplicateInterceptions(diagnostics);
if (compilation.PreviousSubmission != null)
{
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
index dd15a76e93da6..1837e9ca2ee51 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
@@ -2216,6 +2216,7 @@ internal enum ErrorCode
ERR_InterceptorLineCharacterMustBePositive = 27020,
WRN_NullabilityMismatchInReturnTypeOnInterceptor = 27021,
WRN_NullabilityMismatchInParameterTypeOnInterceptor = 27022,
+ ERR_InterceptorCannotInterceptNameof = 27023,
#endregion
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
index c78d60a2a6d3b..3575d881959c1 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
@@ -595,6 +595,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_InterceptorScopedMismatch:
case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor:
case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor:
+ case ErrorCode.ERR_InterceptorCannotInterceptNameof:
// Update src\EditorFeatures\CSharp\LanguageServer\CSharpLspBuildOnlyDiagnostics.cs
// whenever new values are added here.
return true;
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs
index 53cb6205fd676..7f22df8bfc3ba 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs
@@ -222,6 +222,16 @@ private PEModuleBuilder? EmitModule
private BoundExpression? VisitExpressionImpl(BoundExpression node)
{
+ if (node is BoundNameOfOperator nameofOperator)
+ {
+ Debug.Assert(!nameofOperator.WasCompilerGenerated);
+ var nameofIdentiferSyntax = (IdentifierNameSyntax)((InvocationExpressionSyntax)nameofOperator.Syntax).Expression;
+ if (this._compilation.TryGetInterceptor(nameofIdentiferSyntax.Location) is not null)
+ {
+ this._diagnostics.Add(ErrorCode.ERR_InterceptorCannotInterceptNameof, nameofIdentiferSyntax.Location);
+ }
+ }
+
ConstantValue? constantValue = node.ConstantValueOpt;
if (constantValue != null)
{
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs
index aa8f81bb2bc6b..f879d57060fbf 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs
@@ -145,7 +145,7 @@ private void InterceptCallAndAdjustArguments(
// Add assertions for the possible shapes of calls which could come through this method.
// When the BoundCall shape changes in the future, force developer to decide what to do here.
- if (this._compilation.TryGetInterceptor(interceptableLocation, _diagnostics) is not var (attributeLocation, interceptor))
+ if (this._compilation.TryGetInterceptor(interceptableLocation) is not var (attributeLocation, interceptor))
{
// The call was not intercepted.
return;
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
index e1fee3b2c46a9..1a2ddbb550c77 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
@@ -13,6 +13,7 @@
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Shared.Collections;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
@@ -988,9 +989,28 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments
}
var syntaxTrees = DeclaringCompilation.SyntaxTrees;
- // PROTOTYPE(ic): consider avoiding an array allocation here, on the assumption that 1 matching tree is the success (common) case, 0 is the most common error case, and 2 or more is much more rare.
- var matchingTrees = syntaxTrees.WhereAsArray(static (tree, filePath) => tree.FilePath == filePath, filePath);
- if (matchingTrees is [])
+ SyntaxTree? matchingTree = null;
+ // PROTOTYPE(ic): we need to resolve the paths before comparing (i.e. respect /pathmap).
+ // At that time, we should look at caching the resolved paths for the trees in a set (or maybe a Map>).
+ // so we can reduce the cost of these checks.
+ foreach (var tree in syntaxTrees)
+ {
+ if (tree.FilePath == filePath)
+ {
+ if (matchingTree == null)
+ {
+ matchingTree = tree;
+ // need to keep searching in case we find another tree with the same path
+ }
+ else
+ {
+ diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath);
+ return;
+ }
+ }
+ }
+
+ if (matchingTree == null)
{
var suffixMatch = syntaxTrees.FirstOrDefault(static (tree, filePath) => tree.FilePath.EndsWith(filePath), filePath);
if (suffixMatch != null)
@@ -1005,12 +1025,6 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments
return;
}
- if (matchingTrees is not [var matchingTree])
- {
- diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath);
- return;
- }
-
// Internally, line and character numbers are 0-indexed, but when they appear in code or diagnostic messages, they are 1-indexed.
int lineNumberZeroBased = lineNumberOneBased - 1;
int characterNumberZeroBased = characterNumberOneBased - 1;
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index 13fece9ffe045..de8778737ac1c 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
index cef6a34a19359..b06dfcfa5ea8e 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
index 4e45b5edf0431..cef2aff07161a 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
index 8cb87827290e7..d9e3c32c66cb0 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
index 3269ffea46e56..164c3ffff757d 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
index 3491846c32a27..1296bbcdc7855 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
index e3698a2235561..ade22e805992c 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
index c916fb98f72b2..6186fa364d4ae 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
index 3eca53390777d..1dc215f4cdb2b 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
index 7a1c548bfcedf..60719aca6ced7 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
index e30126427a68d..2c1b6686bd6f6 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
index df4783e6ee001..3547b0400b008 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
index 48915d7bb9113..1c6197b32db64 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
@@ -857,6 +857,11 @@
Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
+
+
+ A nameof operator cannot be intercepted.
+
+ The given line is '{0}' characters long, which is fewer than the provided character number '{1}'.
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
index 602f71d3280ad..b352f04e79e9d 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
@@ -5,6 +5,7 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
@@ -13,20 +14,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics;
public class InterceptorsTests : CSharpTestBase
{
- // PROTOTYPE(ic): Ensure that all `MethodSymbol.IsInterceptable` implementations have test coverage.
-
- // PROTOTYPE(ic): Possible test cases:
- //
- // * Intercept instance method with instance method in same class, base class, derived class
- // * Intercept with extern method
- // * Intercept an abstract or interface method
- // * Intercept a virtual or overridden method
- // * Intercept a non-extension call to a static method with a static method when one or both are extension methods
- // * Intercept a struct instance method with an extension method with by-value / by-ref this parameter
- // * An explicit interface implementation marked as interceptable
-
- // PROTOTYPE(ic): test intercepting an extension method with a non-extension method. Perhaps should be an error for simplicity even if calling in non-reduced form.
-
private static readonly (string, string) s_attributesSource = ("""
namespace System.Runtime.CompilerServices;
@@ -321,6 +308,38 @@ static class D
verifier.VerifyDiagnostics();
}
+ [Fact]
+ public void InterceptableExtensionMethod_InterceptorExtensionMethod_NormalForm()
+ {
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ interface I1 { }
+ class C : I1 { }
+
+ static class Program
+ {
+ [Interceptable]
+ public static I1 InterceptableMethod(this I1 i1, string param) { Console.Write("interceptable " + param); return i1; }
+
+ public static void Main()
+ {
+ var c = new C();
+ InterceptableMethod(c, "call site");
+ }
+ }
+
+ static class D
+ {
+ [InterceptsLocation("Program.cs", 15, 9)]
+ public static I1 Interceptor1(this I1 i1, string param) { Console.Write("interceptor " + param); return i1; }
+ }
+ """;
+ var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "interceptor call site");
+ verifier.VerifyDiagnostics();
+ }
+
[Fact]
public void InterceptableInstanceMethod_InterceptorExtensionMethod()
{
@@ -595,6 +614,264 @@ public static void M1() { }
Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 3, 3)").WithLocation(14, 6));
}
+ [Fact]
+ public void InterceptorVirtual_01()
+ {
+ // Intercept a method call with a call to a virtual method on the same type.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ c = new D();
+ c.M();
+
+ class C
+ {
+ [Interceptable]
+ public void M() => throw null!;
+
+ [InterceptsLocation("Program.cs", 5, 3)]
+ [InterceptsLocation("Program.cs", 8, 3)]
+ public virtual void Interceptor() => Console.Write("C");
+ }
+
+ class D : C
+ {
+ public override void Interceptor() => Console.Write("D");
+ }
+
+ namespace System.Runtime.CompilerServices
+ {
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class InterceptableAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+ public sealed class InterceptsLocationAttribute : Attribute
+ {
+ public InterceptsLocationAttribute(string filePath, int line, int character)
+ {
+ }
+ }
+ }
+ """;
+
+ var verifier = CompileAndVerify((source, "Program.cs"), expectedOutput: "CD");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void InterceptorVirtual_02()
+ {
+ // Intercept a call with a virtual method call on the base type.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ D d = new D();
+ d.M();
+
+ class C
+ {
+ [InterceptsLocation("Program.cs", 5, 3)]
+ public virtual void Interceptor() => throw null!;
+ }
+
+ class D : C
+ {
+ [Interceptable]
+ public void M() => throw null!;
+
+ public override void Interceptor() => throw null!;
+ }
+
+ namespace System.Runtime.CompilerServices
+ {
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class InterceptableAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+ public sealed class InterceptsLocationAttribute : Attribute
+ {
+ public InterceptsLocationAttribute(string filePath, int line, int character)
+ {
+ }
+ }
+ }
+ """;
+
+ var comp = CreateCompilation((source, "Program.cs"));
+ comp.VerifyEmitDiagnostics(
+ // Program.cs(9,6): error CS27011: Interceptor must have a 'this' parameter matching parameter 'D this' on 'D.M()'.
+ // [InterceptsLocation("Program.cs", 5, 3)]
+ Diagnostic(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, @"InterceptsLocation(""Program.cs"", 5, 3)").WithArguments("D this", "D.M()").WithLocation(9, 6));
+ }
+
+ [Fact]
+ public void InterceptorOverride_01()
+ {
+ // Intercept a call with a call to an override method on a derived type.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ D d = new D();
+ d.M();
+
+ class C
+ {
+ [Interceptable]
+ public void M() => throw null!;
+
+ public virtual void Interceptor() => throw null!;
+ }
+
+ class D : C
+ {
+ [InterceptsLocation("Program.cs", 5, 3)] // 1
+ public override void Interceptor() => throw null!;
+ }
+
+ namespace System.Runtime.CompilerServices
+ {
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class InterceptableAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+ public sealed class InterceptsLocationAttribute : Attribute
+ {
+ public InterceptsLocationAttribute(string filePath, int line, int character)
+ {
+ }
+ }
+ }
+ """;
+
+ var comp = CreateCompilation((source, "Program.cs"));
+ comp.VerifyEmitDiagnostics(
+ // Program.cs(17,6): error CS27011: Interceptor must have a 'this' parameter matching parameter 'C this' on 'C.M()'.
+ // [InterceptsLocation("Program.cs", 5, 3)] // 1
+ Diagnostic(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, @"InterceptsLocation(""Program.cs"", 5, 3)").WithArguments("C this", "C.M()").WithLocation(17, 6));
+ }
+
+ [Fact]
+ public void InterceptorOverride_02()
+ {
+ // Intercept a call with an override method on the same type.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ D d = new D();
+ d.M();
+
+ class C
+ {
+ public virtual void Interceptor() => throw null!;
+ }
+
+ class D : C
+ {
+ [Interceptable]
+ public void M() => throw null!;
+
+ [InterceptsLocation("Program.cs", 5, 3)]
+ public override void Interceptor() => Console.Write(1);
+ }
+
+ namespace System.Runtime.CompilerServices
+ {
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class InterceptableAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+ public sealed class InterceptsLocationAttribute : Attribute
+ {
+ public InterceptsLocationAttribute(string filePath, int line, int character)
+ {
+ }
+ }
+ }
+ """;
+
+ var verifier = CompileAndVerify((source, "Program.cs"), expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void EmitMetadataOnly_01()
+ {
+ // We can emit a ref assembly even though there are duplicate interceptions.
+ var source = """
+ using System.Runtime.CompilerServices;
+
+ class C
+ {
+ public static void Main()
+ {
+ C.M();
+ }
+
+ [Interceptable]
+ public static void M() { }
+ }
+
+ class D
+ {
+ [InterceptsLocation("Program.cs", 7, 11)]
+ public static void M1() { }
+
+ [InterceptsLocation("Program.cs", 7, 11)]
+ public static void M2() { }
+ }
+ """;
+
+ var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, emitOptions: EmitOptions.Default.WithEmitMetadataOnly(true));
+ verifier.VerifyDiagnostics();
+
+ var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource });
+ comp.VerifyEmitDiagnostics(
+ // Program.cs(16,6): error CS27016: The indicated call is intercepted multiple times.
+ // [InterceptsLocation("Program.cs", 7, 11)]
+ Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 7, 11)").WithLocation(16, 6),
+ // Program.cs(19,6): error CS27016: The indicated call is intercepted multiple times.
+ // [InterceptsLocation("Program.cs", 7, 11)]
+ Diagnostic(ErrorCode.ERR_DuplicateInterceptor, @"InterceptsLocation(""Program.cs"", 7, 11)").WithLocation(19, 6));
+ }
+
+ [Fact]
+ public void EmitMetadataOnly_02()
+ {
+ // We can't emit a ref assembly when a problem is found with an InterceptsLocationAttribute in the declaration phase.
+ // Strictly, we should perhaps allow this emit anyway, but it doesn't feel urgent to do so.
+ var source = """
+ using System.Runtime.CompilerServices;
+
+ C.M();
+
+ class C
+ {
+ [Interceptable]
+ public static void M() { }
+ }
+
+ class D
+ {
+ [InterceptsLocation("Program.cs", 3, 4)]
+ public static void M1() { }
+
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource });
+ comp.VerifyEmitDiagnostics(EmitOptions.Default.WithEmitMetadataOnly(true),
+ // Program.cs(13,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '('.
+ // [InterceptsLocation("Program.cs", 3, 4)]
+ Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 3, 4)").WithArguments("(").WithLocation(13, 6));
+ }
+
[Fact]
public void InterceptsLocationFromMetadata()
{
@@ -700,9 +977,92 @@ public static void Interceptor1(object param) { }
}
""";
var compilation = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource });
- // PROTOTYPE(ic): this is syntactically an invocation but doesn't result in a BoundCall.
- // we should produce an error here, probably during lowering.
compilation.VerifyEmitDiagnostics(
+ // Program.cs(7,13): error CS27023: A nameof operator cannot be intercepted.
+ // _ = nameof(Main);
+ Diagnostic(ErrorCode.ERR_InterceptorCannotInterceptNameof, "nameof").WithLocation(7, 13)
+ );
+ }
+
+ [Fact]
+ public void InterceptableNameof_MethodCall()
+ {
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ static class Program
+ {
+ public static void Main()
+ {
+ _ = nameof(F);
+ }
+
+ private static object F = 1;
+
+ [Interceptable]
+ public static string nameof(object param) => throw null!;
+ }
+
+ static class D
+ {
+ [InterceptsLocation("Program.cs", 8, 13)]
+ public static string Interceptor1(object param)
+ {
+ Console.Write(1);
+ return param.ToString();
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void InterceptableDoubleUnderscoreReservedIdentifiers()
+ {
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ static class Program
+ {
+ public static void Main()
+ {
+ M1(__arglist(1, 2, 3));
+
+ int i = 0;
+ TypedReference tr = __makeref(i);
+ ref int ri = ref __refvalue(tr, int);
+ Type t = __reftype(tr);
+ }
+
+ static void M1(__arglist) { }
+ }
+
+ static class D
+ {
+ [InterceptsLocation("Program.cs", 8, 12)] // __arglist
+ [InterceptsLocation("Program.cs", 11, 29)] // __makeref
+ [InterceptsLocation("Program.cs", 12, 26)] // __refvalue
+ [InterceptsLocation("Program.cs", 13, 18)] // __reftype
+ public static void Interceptor1(int x, int y, int z) { }
+ }
+ """;
+ var compilation = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource });
+ compilation.VerifyEmitDiagnostics(
+ // Program.cs(21,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '__arglist'.
+ // [InterceptsLocation("Program.cs", 8, 12)] // __arglist
+ Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 8, 12)").WithArguments("__arglist").WithLocation(21, 6),
+ // Program.cs(22,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '__makeref'.
+ // [InterceptsLocation("Program.cs", 11, 29)] // __makeref
+ Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 11, 29)").WithArguments("__makeref").WithLocation(22, 6),
+ // Program.cs(23,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '__refvalue'.
+ // [InterceptsLocation("Program.cs", 12, 26)] // __refvalue
+ Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 12, 26)").WithArguments("__refvalue").WithLocation(23, 6),
+ // Program.cs(24,6): error CS27004: The provided line and character number does not refer to an interceptable method name, but rather to token '__reftype'.
+ // [InterceptsLocation("Program.cs", 13, 18)] // __reftype
+ Diagnostic(ErrorCode.ERR_InterceptorPositionBadToken, @"InterceptsLocation(""Program.cs"", 13, 18)").WithArguments("__reftype").WithLocation(24, 6)
);
}
@@ -713,7 +1073,7 @@ public void InterceptableDelegateInvocation()
using System.Runtime.CompilerServices;
using System;
- C.M(() => Console.Write(0));
+ C.M(() => Console.Write(1));
static class C
{
@@ -726,13 +1086,11 @@ public static void M(Action action)
static class D
{
[InterceptsLocation("Program.cs", 10, 9)]
- public static void Interceptor1(this Action action) { Console.Write(1); }
+ public static void Interceptor1(this Action action) { action(); Console.Write(2); }
}
""";
- var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1");
- // PROTOTYPE(ic): perhaps give a more specific error here.
- // If/when we change "missing InterceptableAttribute" to an error, we might not need any specific error, because user cannot attribute the Invoke method.
- // I don't think we intend for delegate Invoke to be interceptable, but it doesn't seem harmful to allow it.
+ var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "12");
+ // PROTOTYPE(ic): drop warning when InterceptableAttribute is removed from the design.
verifier.VerifyDiagnostics(
// Program.cs(16,6): warning CS27000: Call to 'Action.Invoke()' is intercepted, but the method is not marked with 'System.Runtime.CompilerServices.InterceptableAttribute'.
// [InterceptsLocation("Program.cs", 10, 9)]
@@ -832,6 +1190,31 @@ static class D
Diagnostic(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, @"InterceptsLocation(""Program.cs"", 5, 3)").WithArguments("C c", "D.InterceptableMethod(C)").WithLocation(14, 6));
}
+ [Fact]
+ public void InterceptableExtensionMethod_InterceptorStaticMethod_NormalForm()
+ {
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ var c = new C();
+ D.InterceptableMethod(c);
+
+ class C { }
+
+ static class D
+ {
+ [Interceptable]
+ public static void InterceptableMethod(this C c) => throw null!;
+
+ [InterceptsLocation("Program.cs", 5, 3)]
+ public static void Interceptor1(C c) => Console.Write(1);
+ }
+ """;
+ var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
[Fact]
public void InterceptableStaticMethod_InterceptorInstanceMethod()
{
diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
index b4388fa679963..d4ba2a87665f8 100644
--- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
@@ -2951,6 +2951,7 @@ public void TestIsBuildOnlyDiagnostic()
case ErrorCode.ERR_InterceptorScopedMismatch:
case ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor:
case ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor:
+ case ErrorCode.ERR_InterceptorCannotInterceptNameof:
Assert.True(isBuildOnly, $"Check failed for ErrorCode.{errorCode}");
break;
diff --git a/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs b/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs
index 0211f83eddabc..3d15b39aae17b 100644
--- a/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs
+++ b/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs
@@ -362,7 +362,7 @@ public static ImmutableArray GetEmitDiagnostics(
IEnumerable manifestResources = null)
where TCompilation : Compilation
{
- var pdbStream = MonoHelpers.IsRunningOnMono() ? null : new MemoryStream();
+ var pdbStream = MonoHelpers.IsRunningOnMono() || options?.EmitMetadataOnly == true ? null : new MemoryStream();
return c.Emit(new MemoryStream(), pdbStream: pdbStream, options: options, manifestResources: manifestResources).Diagnostics;
}
diff --git a/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs b/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs
index 48cd732ab48dc..bd2d532127a09 100644
--- a/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs
+++ b/src/EditorFeatures/CSharp/LanguageServer/CSharpLspBuildOnlyDiagnostics.cs
@@ -53,7 +53,8 @@ namespace Microsoft.CodeAnalysis.CSharp.LanguageServer
"CS27018", // ErrorCode.ERR_InterceptorNotAccessible
"CS27019", // ErrorCode.ERR_InterceptorScopedMismatch
"CS27021", // ErrorCode.WRN_NullabilityMismatchInReturnTypeOnInterceptor
- "CS27022" // ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor
+ "CS27022", // ErrorCode.WRN_NullabilityMismatchInParameterTypeOnInterceptor
+ "CS27023" // ErrorCode.ERR_InterceptorCannotInterceptNameof
)]
internal sealed class CSharpLspBuildOnlyDiagnostics : ILspBuildOnlyDiagnostics
{