diff --git a/docs/features/interceptors.md b/docs/features/interceptors.md
index 515b95e36d712..83ec2dd26a944 100644
--- a/docs/features/interceptors.md
+++ b/docs/features/interceptors.md
@@ -69,11 +69,20 @@ https://github.com/dotnet/roslyn/issues/67079 is a bug which causes file-local s
#### File paths
-File paths used in `[InterceptsLocation]` must exactly match the paths on the syntax trees they refer to by ordinal comparison. `SyntaxTree.FilePath` has already applied `/pathmap` substitution, so the paths used in the attribute will be less environment-specific in many projects.
+File paths used in `[InterceptsLocation]` are expected to have `/pathmap` substitution already applied. Generators should accomplish this by locally recreating the file path transformation performed by the compiler:
-The compiler does not map `#line` directives when determining if an `[InterceptsLocation]` attribute intercepts a particular call in syntax.
+```cs
+using Microsoft.CodeAnalysis;
+
+string GetInterceptorFilePath(SyntaxTree tree, Compilation compilation)
+{
+ return compilation.Options.SourceReferenceResolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
+}
+```
-PROTOTYPE(ic): editorconfig support matches paths in cross-platform fashion (e.g. normalizing slashes). We should revisit how that works and consider if the same matching strategy should be used instead of ordinal comparison.
+The file path given in the attribute must be equal by ordinal comparison to the value given by the above function.
+
+The compiler does not map `#line` directives when determining if an `[InterceptsLocation]` attribute intercepts a particular call in syntax.
#### Position
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index ccb6d95a036d4..d9470ca18f2e0 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -7517,6 +7517,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
The given file has '{0}' lines, which is fewer than the provided line number '{1}'.
@@ -7541,9 +7544,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Signatures of interceptable and interceptor methods do not match.
-
- An interceptable method must be an ordinary member method.
-
An interceptor method must be an ordinary member method.
diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
index b2d5fd50f03ee..0930b80e07b8c 100644
--- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
+++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
@@ -154,6 +154,9 @@ internal Conversions Conversions
///
private readonly ConcurrentCache _typeToNullableVersion = new ConcurrentCache(size: 100);
+ /// Lazily caches SyntaxTrees by their mapped path. Used to look up the syntax tree referenced by an interceptor.
+ private ImmutableSegmentedDictionary> _mappedPathToSyntaxTree;
+
public override string Language
{
get
@@ -1037,6 +1040,33 @@ internal override int GetSyntaxTreeOrdinal(SyntaxTree tree)
}
}
+ internal OneOrMany GetSyntaxTreesByMappedPath(string mappedPath)
+ {
+ // We could consider storing this on SyntaxAndDeclarationManager instead, and updating it incrementally.
+ // However, this would make it more difficult for it to be "pay-for-play",
+ // i.e. only created in compilations where interceptors are used.
+ var mappedPathToSyntaxTree = _mappedPathToSyntaxTree;
+ if (mappedPathToSyntaxTree.IsDefault)
+ {
+ RoslynImmutableInterlocked.InterlockedInitialize(ref _mappedPathToSyntaxTree, computeMappedPathToSyntaxTree());
+ mappedPathToSyntaxTree = _mappedPathToSyntaxTree;
+ }
+
+ return mappedPathToSyntaxTree.TryGetValue(mappedPath, out var value) ? value : OneOrMany.Empty;
+
+ ImmutableSegmentedDictionary> computeMappedPathToSyntaxTree()
+ {
+ var builder = ImmutableSegmentedDictionary.CreateBuilder>();
+ var resolver = Options.SourceReferenceResolver;
+ foreach (var tree in SyntaxTrees)
+ {
+ var path = resolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
+ builder[path] = builder.ContainsKey(path) ? builder[path].Add(tree) : OneOrMany.Create(tree);
+ }
+ return builder.ToImmutable();
+ }
+ }
+
#endregion
#region References
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
index 24da3889ccb85..07d22acb3f380 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
@@ -2201,7 +2201,7 @@ internal enum ErrorCode
ERR_InterceptorLineOutOfRange = 27005,
ERR_InterceptorCharacterOutOfRange = 27006,
ERR_InterceptorSignatureMismatch = 27007,
- ERR_InterceptableMethodMustBeOrdinary = 27008,
+ ERR_InterceptorPathNotInCompilationWithUnmappedCandidate = 27008,
ERR_InterceptorMethodMustBeOrdinary = 27009,
ERR_InterceptorMustReferToStartOfTokenPosition = 27010,
ERR_InterceptorMustHaveMatchingThisParameter = 27011,
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
index 68ead9b8db547..4929639467e01 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
@@ -2328,10 +2328,10 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_InterceptorCannotBeGeneric:
case ErrorCode.ERR_InterceptorPathNotInCompilation:
case ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate:
+ case ErrorCode.ERR_InterceptorPathNotInCompilationWithUnmappedCandidate:
case ErrorCode.ERR_InterceptorPositionBadToken:
case ErrorCode.ERR_InterceptorLineOutOfRange:
case ErrorCode.ERR_InterceptorCharacterOutOfRange:
- case ErrorCode.ERR_InterceptableMethodMustBeOrdinary:
case ErrorCode.ERR_InterceptorMethodMustBeOrdinary:
case ErrorCode.ERR_InterceptorMustReferToStartOfTokenPosition:
case ErrorCode.ERR_InterceptorFilePathCannotBeNull:
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs
index 9d164bd43ab0f..b0b42303f4cc0 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs
@@ -218,7 +218,7 @@ private void InterceptCallAndAdjustArguments(
{
// Special case when intercepting an extension method call in reduced form with a non-extension.
this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, method.Parameters[0], method);
- // PROTOYPE(ic): use a symbol display format which includes the 'this' modifier?
+ // PROTOTYPE(ic): use a symbol display format which includes the 'this' modifier?
//this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, new FormattedSymbol(method.Parameters[0], SymbolDisplayFormat.CSharpErrorMessageFormat), method);
return;
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
index 25a9f2cb59fe9..18e3285aeadb2 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
@@ -967,8 +967,8 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments
return;
}
- var filePath = (string?)attributeArguments[0].Value;
- if (filePath is null)
+ var attributeFilePath = (string?)attributeArguments[0].Value;
+ if (attributeFilePath is null)
{
diagnostics.Add(ErrorCode.ERR_InterceptorFilePathCannotBeNull, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax));
return;
@@ -987,42 +987,54 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments
}
var syntaxTrees = DeclaringCompilation.SyntaxTrees;
- 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)
+ var matchingTrees = DeclaringCompilation.GetSyntaxTreesByMappedPath(attributeFilePath);
+ if (matchingTrees.Count == 0)
{
- if (tree.FilePath == filePath)
+ var referenceResolver = DeclaringCompilation.Options.SourceReferenceResolver;
+ // if we expect '/_/Program.cs':
+
+ // we might get: 'C:\Project\Program.cs' <-- path not mapped
+ var unmappedMatch = syntaxTrees.FirstOrDefault(static (tree, filePath) => tree.FilePath == filePath, attributeFilePath);
+ if (unmappedMatch != null)
{
- 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;
- }
+ diagnostics.Add(
+ ErrorCode.ERR_InterceptorPathNotInCompilationWithUnmappedCandidate,
+ attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax),
+ attributeFilePath,
+ mapPath(referenceResolver, unmappedMatch));
+ return;
}
- }
- if (matchingTree == null)
- {
- var suffixMatch = syntaxTrees.FirstOrDefault(static (tree, filePath) => tree.FilePath.EndsWith(filePath), filePath);
+ // we might get: '\_\Program.cs' <-- slashes not normalized
+ // we might get: '\_/Program.cs' <-- slashes don't match
+ // we might get: 'Program.cs' <-- suffix match
+ // Force normalization of all '\' to '/', but when we recommend a path in the diagnostic message, ensure it will match what we expect if the user decides to use it.
+ var suffixMatch = syntaxTrees.FirstOrDefault(static (tree, pair)
+ => mapPath(pair.referenceResolver, tree)
+ .Replace('\\', '/')
+ .EndsWith(pair.attributeFilePath),
+ (referenceResolver, attributeFilePath: attributeFilePath.Replace('\\', '/')));
if (suffixMatch != null)
{
- diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath, suffixMatch.FilePath);
- }
- else
- {
- diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilation, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), filePath);
+ diagnostics.Add(
+ ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate,
+ attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax),
+ attributeFilePath,
+ mapPath(referenceResolver, suffixMatch));
+ return;
}
+ diagnostics.Add(ErrorCode.ERR_InterceptorPathNotInCompilation, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), attributeFilePath);
+
+ return;
+ }
+ else if (matchingTrees.Count > 1)
+ {
+ diagnostics.Add(ErrorCode.ERR_InterceptorNonUniquePath, attributeData.GetAttributeArgumentSyntaxLocation(filePathParameterIndex, attributeSyntax), attributeFilePath);
return;
}
+ SyntaxTree? matchingTree = matchingTrees[0];
// 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;
@@ -1079,7 +1091,12 @@ private void DecodeInterceptsLocationAttribute(DecodeWellKnownAttributeArguments
return;
}
- DeclaringCompilation.AddInterception(filePath, lineNumberZeroBased, characterNumberZeroBased, attributeLocation, this);
+ DeclaringCompilation.AddInterception(matchingTree.FilePath, lineNumberZeroBased, characterNumberZeroBased, attributeLocation, this);
+
+ static string mapPath(SourceReferenceResolver? referenceResolver, SyntaxTree tree)
+ {
+ return referenceResolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
+ }
}
private void DecodeUnmanagedCallersOnlyAttribute(ref DecodeWellKnownAttributeArguments arguments)
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index 0f1f4c00da32e..7be2469288d8d 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -847,11 +847,6 @@
Vlastnosti instance v rozhraních nemůžou mít inicializátory.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
index 952490ece3e92..c8e5655adca3c 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
@@ -847,11 +847,6 @@
Instanzeigenschaften in Schnittstellen können keine Initialisierer aufweisen.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
index bf3cc092fda87..5a0b77e2b904b 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
@@ -847,11 +847,6 @@
Las propiedades de la instancia en las interfaces no pueden tener inicializadores.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
index 42ee72dbe49a6..23db7e580c329 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
@@ -847,11 +847,6 @@
Les propriétés d'instance dans les interfaces ne peuvent pas avoir d'initialiseurs.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
index 4f76e002b5946..e4362aa55924e 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
@@ -847,11 +847,6 @@
Le proprietà di istanza nelle interfacce non possono avere inizializzatori.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
index 85cb05f4332d8..ec86ff32e4232 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
@@ -847,11 +847,6 @@
インターフェイス内のインスタンス プロパティは初期化子を持つことができません。
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
index 7d40d808fff83..d442b56855029 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
@@ -847,11 +847,6 @@
인터페이스의 인스턴스 속성은 이니셜라이저를 사용할 수 없습니다.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
index 025fadbadcd6b..0671ad1e4c5a3 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
@@ -847,11 +847,6 @@
Właściwości wystąpienia w interfejsach nie mogą mieć inicjatorów.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
index 817089bfb458b..089e659d105d7 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
@@ -847,11 +847,6 @@
As propriedades da instância nas interfaces não podem ter inicializadores.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
index c22c662868529..5f2e3fdb81831 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
@@ -847,11 +847,6 @@
Свойства экземпляра в интерфейсах не могут иметь инициализаторы.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
index 2c2fc249c8054..3915cc9716c0d 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
@@ -847,11 +847,6 @@
Arabirimlerdeki örnek özelliklerinin başlatıcıları olamaz.
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
index 9905cd20f5548..a15ab4d0f95d4 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
@@ -847,11 +847,6 @@
接口中的实例属性不能具有初始值设定项。
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
index 938180a55d930..99eea9e6b277c 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
@@ -847,11 +847,6 @@
介面中的執行個體屬性不可有初始設定式。
-
- An interceptable method must be an ordinary member method.
- An interceptable method must be an ordinary member method.
-
- Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.Method '{0}' cannot be used as an interceptor because it or its containing type has type parameters.
@@ -927,6 +922,11 @@
Cannot intercept: compilation does not contain a file with path '{0}'. Did you mean to use path '{1}'?
+
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+ Cannot intercept: Path '{0}' is unmapped. Expected mapped path '{1}'.
+
+ The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.The provided line and character number does not refer to an interceptable method name, but rather to token '{0}'.
diff --git a/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs b/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs
index 27b99ac19c0b8..d2846aaf8a565 100644
--- a/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs
+++ b/src/Compilers/CSharp/Test/EndToEnd/EndToEndTests.cs
@@ -318,5 +318,73 @@ static void runTest(int n)
});
}
}
+
+ [Fact]
+ public void Interceptors()
+ {
+ const int numberOfInterceptors = 10000;
+
+ // write a program which has many intercepted calls.
+ // each interceptor is in a different file.
+ var files = ArrayBuilder<(string source, string path)>.GetInstance();
+
+ // Build a top-level-statements main like:
+ // C.M();
+ // C.M();
+ // C.M();
+ // ...
+ var builder = new StringBuilder();
+ for (int i = 0; i < numberOfInterceptors; i++)
+ {
+ builder.AppendLine("C.M();");
+ }
+
+ files.Add((builder.ToString(), "Program.cs"));
+
+ files.Add(("""
+ class C
+ {
+ public static void M() => throw null!;
+ }
+
+ namespace System.Runtime.CompilerServices
+ {
+ public class InterceptsLocationAttribute : Attribute
+ {
+ public InterceptsLocationAttribute(string path, int line, int column) { }
+ }
+ }
+ """, "C.cs"));
+
+ for (int i = 0; i < numberOfInterceptors; i++)
+ {
+ files.Add(($$"""
+ using System;
+ using System.Runtime.CompilerServices;
+
+ class C{{i}}
+ {
+ [InterceptsLocation("Program.cs", {{i + 1}}, 3)]
+ public static void M()
+ {
+ Console.WriteLine({{i}});
+ }
+ }
+ """, $"C{i}.cs"));
+ }
+
+ var verifier = CompileAndVerify(files.ToArrayAndFree(), parseOptions: TestOptions.Regular.WithFeature("InterceptorsPreview"), expectedOutput: makeExpectedOutput());
+ verifier.VerifyDiagnostics();
+
+ string makeExpectedOutput()
+ {
+ builder.Clear();
+ for (int i = 0; i < numberOfInterceptors; i++)
+ {
+ builder.AppendLine($"{i}");
+ }
+ return builder.ToString();
+ }
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
index 4377eee83fb77..ecd0e98fa763e 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
@@ -2,12 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
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 Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics;
@@ -3420,4 +3424,479 @@ class C : I
var verifier = CompileAndVerify(new[] { (source, "Program.cs"), s_attributesSource }, parseOptions: RegularWithInterceptors, expectedOutput: "1");
verifier.VerifyDiagnostics();
}
+
+ [Fact]
+ public void InterceptGetEnumerator()
+ {
+ var source = """
+ using System.Collections;
+ using System.Runtime.CompilerServices;
+
+ var myEnumerable = new MyEnumerable();
+ foreach (var item in myEnumerable)
+ {
+ }
+
+ class MyEnumerable : IEnumerable
+ {
+ public IEnumerator GetEnumerator() => throw null!;
+ }
+
+ static class MyEnumerableExt
+ {
+ [InterceptsLocation("Program.cs", 5, 22)] // 1
+ public static IEnumerator GetEnumerator1(this MyEnumerable en) => throw null!;
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, parseOptions: RegularWithInterceptors);
+ comp.VerifyEmitDiagnostics(
+ // Program.cs(16,6): error CS27014: Possible method name 'myEnumerable' cannot be intercepted because it is not being invoked.
+ // [InterceptsLocation("Program.cs", 5, 22)] // 1
+ Diagnostic(ErrorCode.ERR_InterceptorNameNotInvoked, @"InterceptsLocation(""Program.cs"", 5, 22)").WithArguments("myEnumerable").WithLocation(16, 6));
+ }
+
+ [Fact]
+ public void InterceptDispose()
+ {
+ var source = """
+ using System;
+ using System.Runtime.CompilerServices;
+
+ var myDisposable = new MyDisposable();
+ using (myDisposable)
+ {
+ }
+
+ class MyDisposable : IDisposable
+ {
+ public void Dispose() => throw null!;
+ }
+
+ static class MyDisposeExt
+ {
+ [InterceptsLocation("Program.cs", 5, 8)] // 1
+ public static void Dispose1(this MyDisposable md) => throw null!;
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, parseOptions: RegularWithInterceptors);
+ comp.VerifyEmitDiagnostics(
+ // Program.cs(16,6): error CS27014: Possible method name 'myDisposable' cannot be intercepted because it is not being invoked.
+ // [InterceptsLocation("Program.cs", 5, 8)] // 1
+ Diagnostic(ErrorCode.ERR_InterceptorNameNotInvoked, @"InterceptsLocation(""Program.cs"", 5, 8)").WithArguments("myDisposable").WithLocation(16, 6)
+ );
+ }
+
+ [Fact]
+ public void InterceptDeconstruct()
+ {
+ var source = """
+ using System;
+ using System.Runtime.CompilerServices;
+
+ var myDeconstructable = new MyDeconstructable();
+ var (x, y) = myDeconstructable;
+
+ class MyDeconstructable
+ {
+ public void Deconstruct(out int x, out int y) => throw null!;
+ }
+
+ static class MyDeconstructableExt
+ {
+ [InterceptsLocation("Program.cs", 5, 14)] // 1
+ public static void Deconstruct1(this MyDeconstructable md, out int x, out int y) => throw null!;
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { (source, "Program.cs"), s_attributesSource }, parseOptions: RegularWithInterceptors);
+ comp.VerifyEmitDiagnostics(
+ // Program.cs(14,6): error CS27014: Possible method name 'myDeconstructable' cannot be intercepted because it is not being invoked.
+ // [InterceptsLocation("Program.cs", 5, 14)] // 1
+ Diagnostic(ErrorCode.ERR_InterceptorNameNotInvoked, @"InterceptsLocation(""Program.cs"", 5, 14)").WithArguments("myDeconstructable").WithLocation(14, 6)
+ );
+ }
+
+ [Fact]
+ public void PathMapping_01()
+ {
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ class C
+ {
+ public void M() => throw null!;
+
+ [InterceptsLocation("/_/Program.cs", 5, 3)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+ var pathPrefix = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path\""" : "/My/Machine/Specific/Path/";
+ var path = pathPrefix + "Program.cs";
+ var pathMap = ImmutableArray.Create(new KeyValuePair(pathPrefix, "/_/"));
+
+ var verifier = CompileAndVerify(
+ new[] { (source, path), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugExe.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)),
+ expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void PathMapping_02()
+ {
+ // Attribute uses a physical path, but we expected a mapped path.
+ // Diagnostic can suggest using the mapped path instead.
+ var pathPrefix = PlatformInformation.IsWindows ? @"C:\My\Machine\Specific\Path\" : "/My/Machine/Specific/Path/";
+ var path = pathPrefix + "Program.cs";
+ var source = $$"""
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ class C
+ {
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"{{path}}", 5, 3)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+ var pathMap = ImmutableArray.Create(new KeyValuePair(pathPrefix, "/_/"));
+
+ var comp = CreateCompilation(
+ new[] { (source, path), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugExe.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)));
+ comp.VerifyEmitDiagnostics(
+ // C:\My\Machine\Specific\Path\Program.cs(11,25): error CS27008: Cannot intercept: Path 'C:\My\Machine\Specific\Path\Program.cs' is unmapped. Expected mapped path '/_/Program.cs'.
+ // [InterceptsLocation(@"C:\My\Machine\Specific\Path\Program.cs", 5, 3)]
+ Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithUnmappedCandidate, $@"@""{path}""").WithArguments(path, "/_/Program.cs").WithLocation(11, 25)
+ );
+ }
+
+ [Fact]
+ public void PathMapping_03()
+ {
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ class C
+ {
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"\_\Program.cs", 5, 3)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+ var pathPrefix = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path\""" : "/My/Machine/Specific/Path/";
+ var path = pathPrefix + "Program.cs";
+ var pathMap = ImmutableArray.Create(new KeyValuePair(pathPrefix, "/_/"));
+
+ var comp = CreateCompilation(
+ new[] { (source, path), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugExe.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)));
+ comp.VerifyEmitDiagnostics(
+ // C:\My\Machine\Specific\Path\Program.cs(11,25): error CS27003: Cannot intercept: compilation does not contain a file with path '\_\Program.cs'. Did you mean to use path '/_/Program.cs'?
+ // [InterceptsLocation(@"\_\Program.cs", 5, 3)]
+ Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, @"@""\_\Program.cs""").WithArguments(@"\_\Program.cs", "/_/Program.cs").WithLocation(11, 25));
+ }
+
+ [Fact]
+ public void PathMapping_04()
+ {
+ // Test when unmapped file paths are distinct, but mapped paths are equal.
+ var source1 = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ namespace NS1;
+
+ class C
+ {
+ public static void M0()
+ {
+ C c = new C();
+ c.M();
+ }
+
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"/_/Program.cs", 11, 9)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+
+ var source2 = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ namespace NS2;
+
+ class C
+ {
+ public static void M0()
+ {
+ C c = new C();
+ c.M();
+ }
+
+ public void M() => throw null!;
+ }
+ """;
+
+ var pathPrefix1 = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path1\""" : "/My/Machine/Specific/Path1/";
+ var pathPrefix2 = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path2\""" : "/My/Machine/Specific/Path2/";
+ var path1 = pathPrefix1 + "Program.cs";
+ var path2 = pathPrefix2 + "Program.cs";
+ var pathMap = ImmutableArray.Create(
+ new KeyValuePair(pathPrefix1, "/_/"),
+ new KeyValuePair(pathPrefix2, "/_/")
+ );
+
+ var comp = CreateCompilation(
+ new[] { (source1, path1), (source2, path2), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugDll.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)));
+ comp.VerifyEmitDiagnostics(
+ // C:\My\Machine\Specific\Path1\Program.cs(16,25): error CS27015: Cannot intercept a call in file with path '/_/Program.cs' because multiple files in the compilation have this path.
+ // [InterceptsLocation(@"/_/Program.cs", 11, 9)]
+ Diagnostic(ErrorCode.ERR_InterceptorNonUniquePath, @"@""/_/Program.cs""").WithArguments("/_/Program.cs").WithLocation(16, 25));
+ }
+
+ [Fact]
+ public void PathMapping_05()
+ {
+ // Pathmap replacement contains backslashes, and attribute path contains backslashes.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ class C
+ {
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"\_\Program.cs", 5, 3)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+ var pathPrefix = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path\""" : "/My/Machine/Specific/Path/";
+ var path = pathPrefix + "Program.cs";
+ var pathMap = ImmutableArray.Create(new KeyValuePair(pathPrefix, @"\_\"));
+
+ var verifier = CompileAndVerify(
+ new[] { (source, path), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugExe.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)),
+ expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void PathMapping_06()
+ {
+ // Pathmap mixes slashes and backslashes, attribute path is normalized to slashes
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ class C
+ {
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"/_/Program.cs", 5, 3)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+ var pathPrefix = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path\""" : "/My/Machine/Specific/Path/";
+ var path = pathPrefix + "Program.cs";
+ var pathMap = ImmutableArray.Create(new KeyValuePair(pathPrefix, @"\_/"));
+
+ var comp = CreateCompilation(
+ new[] { (source, path), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugExe.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)));
+ comp.VerifyEmitDiagnostics(
+ // C:\My\Machine\Specific\Path\Program.cs(11,25): error CS27003: Cannot intercept: compilation does not contain a file with path '/_/Program.cs'. Did you mean to use path '\_/Program.cs'?
+ // [InterceptsLocation(@"/_/Program.cs", 5, 3)]
+ Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, @"@""/_/Program.cs""").WithArguments("/_/Program.cs", @"\_/Program.cs").WithLocation(11, 25));
+ }
+
+ [Fact]
+ public void PathMapping_07()
+ {
+ // Pathmap replacement mixes slashes and backslashes, attribute path matches it
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ C c = new C();
+ c.M();
+
+ class C
+ {
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"\_/Program.cs", 5, 3)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+ var pathPrefix = PlatformInformation.IsWindows ? """C:\My\Machine\Specific\Path\""" : "/My/Machine/Specific/Path/";
+ var path = pathPrefix + "Program.cs";
+ var pathMap = ImmutableArray.Create(new KeyValuePair(pathPrefix, @"\_/"));
+
+ var verifier = CompileAndVerify(
+ new[] { (source, path), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ options: TestOptions.DebugExe.WithSourceReferenceResolver(
+ new SourceFileResolver(ImmutableArray.Empty, null, pathMap)),
+ expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void PathNormalization_01()
+ {
+ // No pathmap is present and slashes in the attribute match the FilePath on the syntax tree.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ class C
+ {
+ public static void Main()
+ {
+ C c = new C();
+ c.M();
+ }
+
+ public void M() => throw null!;
+
+ [InterceptsLocation("src/Program.cs", 9, 11)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+
+ var verifier = CompileAndVerify(
+ new[] { (source, "src/Program.cs"), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void PathNormalization_02()
+ {
+ // No pathmap is present and backslashes in the attribute match the FilePath on the syntax tree.
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ class C
+ {
+ public static void Main()
+ {
+ C c = new C();
+ c.M();
+ }
+
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"src\Program.cs", 9, 11)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+
+ var verifier = CompileAndVerify(
+ new[] { (source, @"src\Program.cs"), s_attributesSource },
+ parseOptions: RegularWithInterceptors,
+ expectedOutput: "1");
+ verifier.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void PathNormalization_03()
+ {
+ // Relative paths do not have slashes normalized when pathmap is not present
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ class C
+ {
+ public static void Main()
+ {
+ C c = new C();
+ c.M();
+ }
+
+ public void M() => throw null!;
+
+ [InterceptsLocation(@"src/Program.cs", 9, 11)]
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { (source, @"src\Program.cs"), s_attributesSource }, parseOptions: RegularWithInterceptors);
+ comp.VerifyEmitDiagnostics(
+ // src\Program.cs(14,25): error CS27003: Cannot intercept: compilation does not contain a file with path 'src/Program.cs'. Did you mean to use path 'src\Program.cs'?
+ // [InterceptsLocation(@"src/Program.cs", 9, 11)]
+ Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, @"@""src/Program.cs""").WithArguments("src/Program.cs", @"src\Program.cs").WithLocation(14, 25));
+ }
+
+ [Fact]
+ public void PathNormalization_04()
+ {
+ // Absolute paths do not have slashes normalized when no pathmap is present
+ // Note that any such normalization step would be specific to Windows
+ var source = """
+ using System.Runtime.CompilerServices;
+ using System;
+
+ class C
+ {
+ public static void Main()
+ {
+ C c = new C();
+ c.M();
+ }
+
+ public void M() => throw null!;
+
+ [InterceptsLocation("C:/src/Program.cs", 9, 11)] // 1
+ public void Interceptor() => Console.Write(1);
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { (source, @"C:\src\Program.cs"), s_attributesSource }, parseOptions: RegularWithInterceptors);
+ comp.VerifyEmitDiagnostics(
+ // C:\src\Program.cs(14,25): error CS27003: Cannot intercept: compilation does not contain a file with path 'C:/src/Program.cs'. Did you mean to use path 'C:\src\Program.cs'?
+ // [InterceptsLocation("C:/src/Program.cs", 9, 11)] // 1
+ Diagnostic(ErrorCode.ERR_InterceptorPathNotInCompilationWithCandidate, @"""C:/src/Program.cs""").WithArguments("C:/src/Program.cs", @"C:\src\Program.cs").WithLocation(14, 25));
+ }
}