Support relative paths in interceptors#71879
Conversation
916c290 to
7f9cce9
Compare
/generatedfilesout in generated SyntaxTree.FilePath7f9cce9 to
26fe473
Compare
|
|
||
| public readonly bool TrackIncrementalGeneratorSteps; | ||
|
|
||
| internal string? BaseDirectory { get; init; } |
There was a problem hiding this comment.
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.
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.
Couldn't this be summarized as
The
filePathargument is interepreted the same as a path for a#linedirective 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.
According to the documentation for Directory.GetParent(), the return value is:
[...]
nullifpathis the root directory
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
jcouv
left a comment
There was a problem hiding this comment.
Done with review pass (iteration 10)
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.
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.
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.
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
InterceptsLocationis resolved the same way a#linewould be
src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs
Outdated
Show resolved
Hide resolved
|
|
||
| public readonly bool TrackIncrementalGeneratorSteps; | ||
|
|
||
| internal string? BaseDirectory { get; init; } |
There was a problem hiding this comment.
Think it should be internal until a generator can articulate specific scearios which justify us making it public
|
|
||
| internal readonly SyntaxStore SyntaxStore; | ||
|
|
||
| private readonly GeneratorDriverOptions DriverOptions; |
There was a problem hiding this comment.
private fields should have the _driverOptions syntax
| } | ||
|
|
||
| string? normalizedPath = TryNormalizeAbsolutePath(resolvedPath); | ||
| string? normalizedPath = PathUtilities.IsAbsolute(resolvedPath) ? TryNormalizeAbsolutePath(resolvedPath) : null; |
There was a problem hiding this comment.
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.
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.
| ImmutableArray<AdditionalText> additionalTexts, | ||
| DiagnosticBag generatorDiagnostics) | ||
| { | ||
| Debug.Assert(baseDirectory is not null); |
There was a problem hiding this comment.
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.
jaredpar
left a comment
There was a problem hiding this comment.
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 |
| `#line` directives are not considered when determining the call referenced by an `[InterceptsLocation]` attribute. In other words, the file path, line and column numbers used in `[InterceptsLocation]` are expected to refer to *unmapped* source locations. | ||
|
|
||
| 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.
Let's file a bug to track removing this work around.
| 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.
nit: consider adding link to [https://github.com/dotnet/roslyn/issues/72265](https://github.com/dotnet/roslyn/issues/72265`) here as well
See dotnet/csharplang#7835
/generatedfilesoutargument is specified, use the OutputDirectory (directory name of/outargument) as the base path for generated files. (Generated files are still only written to disk when/generatedfilesoutis explicitly specified.)SyntaxTree.FilePathERR_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.