-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Support relative paths in interceptors #71879
Conversation
916c290
to
7f9cce9
Compare
/generatedfilesout
in generated SyntaxTree.FilePath7f9cce9
to
26fe473
Compare
@@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis | |||
|
|||
public readonly bool TrackIncrementalGeneratorSteps; | |||
|
|||
internal string? BaseDirectory { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible that eventually we will want to make this API public to allow hosts outside the compiler to specify a value for it. However, I wanted to separate this decision from the initial work to get the "interceptors relative paths" scenarios working end-to-end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think it should be internal
until a generator can articulate specific scearios which justify us making it public
docs/features/interceptors.md
Outdated
1. 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`. | ||
2. 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*. | ||
3. If no syntax tree matched by the above comparison, then the `filePath` argument value is resolved relative to path of the containing syntax tree of the `[InterceptsLocation]` usage. Pathmap substitution is applied to the result of this relative resolution. If exactly one syntax tree matches this resolved and mapped path, that is the *referenced syntax tree*. | ||
4. If neither of the above methods of comparison matched exactly one syntax tree, an error occurs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this be summarized as
The
filePath
argument is interepreted the same as a path for a#line
directive in the same location?
If not in what ways are they different?
src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs
Outdated
Show resolved
Hide resolved
} | ||
"""; | ||
|
||
// parent directory of the drive root is just the drive root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the documentation for Directory.GetParent()
, the return value is:
[...]
null
ifpath
is the root directory
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try and make this comment more clear that this about relative path resolution.
More illustrative example might be: SharpLab
[Fact] | ||
public void RelativePaths_08() | ||
{ | ||
// Unmapped file paths are not absolute. Relative path resolution is not performed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path in the attribute is often relative, but we won't actually be able to resolve it when the containing syntax tree does not have an absolute path.
src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 10)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 11)
docs/features/interceptors.md
Outdated
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. | ||
Note that `#line` directives are not considered when determining the call referenced by an `[InterceptsLocation]` attribute. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The doc still says that we consider mapped path here, thought we decided it would just match #line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The term mapped path is used in reference to /pathmap
, not #line
mapping. This sentence is meant to say that presence or absence of #line
in source code doesn't affect what an InterceptsLocationAttribute
means (aside from the trailing newline affecting the line numbers of the subsequent lines).
I think this is something which will become much simpler conceptually as we phase out the "compat" lookup. Basically, there will no longer be any expectation for the user of [InterceptsLocation]
to perform any kind of mapping of the file path, line, or column; rather, to just get and use a relative path from the "current file" to the "referenced file", and to use the "unmapped" line and column numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point (2) still says that path map impacts how we resolve paths in InterceptsLocation
. That means the following paths do not necessarily mean the same thing.
#line /src/blah/code.cs
[Intercepts("/src/blah/code.cs")]
That feels very broken. Both of these features are about mapping a file path, relative or absolute, to another file in the compilation. Why should one use /pathmap
and the other not use it?
If I'm incorrect and #line
does respect path map for embedding I'm happy to be corrected on this point. If it does though why do we have such a complex section on how this mapping works. Why can't we just simply say:
The path inside
InterceptsLocation
is resolved the same way a#line
would be
src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
Outdated
Show resolved
Hide resolved
@@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis | |||
|
|||
public readonly bool TrackIncrementalGeneratorSteps; | |||
|
|||
internal string? BaseDirectory { get; init; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think it should be internal
until a generator can articulate specific scearios which justify us making it public
@@ -86,13 +91,17 @@ namespace Microsoft.CodeAnalysis | |||
|
|||
internal readonly SyntaxStore SyntaxStore; | |||
|
|||
private readonly GeneratorDriverOptions DriverOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private
fields should have the _driverOptions
syntax
@@ -223,7 +223,7 @@ internal static class FileUtilities | |||
return null; | |||
} | |||
|
|||
string? normalizedPath = TryNormalizeAbsolutePath(resolvedPath); | |||
string? normalizedPath = PathUtilities.IsAbsolute(resolvedPath) ? TryNormalizeAbsolutePath(resolvedPath) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you put the check here vs. putting the check inside TryNormalizeAbsolutePath
? Feels like one case it should handle is if it's not passed an absolute path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reason I put it here was because it reduced the need to examine the other call sites in order to decide whether those call sites should "fail fast" when the input is not an absolute path, as they currently do, versus propagating a null result.
However, if it doesn't feel useful to preserve that "fail fast" behavior for the other call sites, I'm happy to push the check inside of TryNormalizeAbsolutePath
and remove the debug assertion from within that method at the same time.
ParseOptions parseOptions, | ||
ImmutableArray<ISourceGenerator> generators, | ||
AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, | ||
ImmutableArray<AdditionalText> additionalTexts, | ||
DiagnosticBag generatorDiagnostics) | ||
{ | ||
Debug.Assert(baseDirectory is not null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think that we need a more descriptive name here. In the context of the compiler baseDirectory
can have a variety of interpretations. Feel like we need to disambiguate this a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The doc still says that we treat #line
and InterceptsLocation
paths differently which I don't understand. They should be the same for the new version of the attribute.
Are you asking for the doc to clarify that the "compat" behavior in this PR is a temporary state of affairs? This PR doesn't implement support for |
|
||
Temporarily, for compatibility purposes, we use the following rules: | ||
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's file a bug to track removing this work around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1042,6 +1046,9 @@ internal override int GetSyntaxTreeOrdinal(SyntaxTree tree) | |||
|
|||
internal OneOrMany<SyntaxTree> GetSyntaxTreesByMappedPath(string mappedPath) | |||
{ | |||
// This method supports a "compat" behavior for interceptor file path resolution. | |||
// It must be removed prior to stable release. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider adding link to [https://github.com/dotnet/roslyn/issues/72265
](https://github.com/dotnet/roslyn/issues/72265`) here as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 18)
See dotnet/csharplang#7835
/generatedfilesout
argument is specified, use the OutputDirectory (directory name of/out
argument) as the base path for generated files. (Generated files are still only written to disk when/generatedfilesout
is explicitly specified.)SyntaxTree.FilePath
ERR_InterceptorPathNotInCompilationWithUnmappedCandidate
. We don't want to suggest using a mapped path over a relative path, and in scenarios where files have absolute paths 99.9% of the time, we end up mapping the path after it is resolved relative to containing file, thus using an unmapped path ends up having the same effect as using a mapped path.After this change we expect generators to start primarily using relative paths, but for compat, we want to continue accepting the same paths we have already been accepting in the preview, at least until everybody has a chance to move to relative paths.