Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support relative paths in interceptors #71879

Merged
merged 19 commits into from
Feb 27, 2024

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Jan 30, 2024

See dotnet/csharplang#7835

  • When no /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.)
  • Include the base path for generated files in the SyntaxTree.FilePath
  • Resolve interceptors file paths relative to the containing file
  • Removed diagnostic 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.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Jan 30, 2024
@RikkiGibson RikkiGibson changed the base branch from main to features/interceptors February 6, 2024 18:30
@RikkiGibson RikkiGibson changed the title Use /generatedfilesout in generated SyntaxTree.FilePath Support relative paths in interceptors Feb 12, 2024
@RikkiGibson RikkiGibson changed the base branch from features/interceptors to main February 12, 2024 19:10
@@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis

public readonly bool TrackIncrementalGeneratorSteps;

internal string? BaseDirectory { get; init; }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It'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.

Copy link
Member

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

@RikkiGibson RikkiGibson marked this pull request as ready for review February 13, 2024 01:32
@RikkiGibson RikkiGibson requested a review from a team as a code owner February 13, 2024 01:32
@RikkiGibson RikkiGibson requested review from cston and jcouv February 13, 2024 03:10
@RikkiGibson
Copy link
Contributor Author

@cston @jcouv Please take a look when you have the chance, thanks.

docs/features/interceptors.md Outdated Show resolved Hide resolved
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.
Copy link
Member

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?

}
""";

// parent directory of the drive root is just the drive root
Copy link
Member

Choose a reason for hiding this comment

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

parent directory of the drive root is just the drive root

According to the documentation for Directory.GetParent(), the return value is:

[...] null if path is the root directory

Copy link
Contributor Author

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.
Copy link
Member

Choose a reason for hiding this comment

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

Unmapped file paths are not absolute. Relative path resolution is not performed

I don't understand this. It sounded like the goal of the change was to allow using relative paths. From the PR description:

After this change we expect generators to start primarily using relative paths [...]

Copy link
Contributor Author

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.

Copy link
Member

@jcouv jcouv left a 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)

Copy link
Member

@jcouv jcouv left a 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)

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.
Copy link
Member

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

Copy link
Contributor Author

@RikkiGibson RikkiGibson Feb 23, 2024

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.

Copy link
Member

@jaredpar jaredpar Feb 23, 2024

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

@@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis

public readonly bool TrackIncrementalGeneratorSteps;

internal string? BaseDirectory { get; init; }
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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.

Copy link
Member

@jaredpar jaredpar left a 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.

@RikkiGibson
Copy link
Contributor Author

RikkiGibson commented Feb 23, 2024

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 [InterceptsLocation(string locationSpecifier)] nor introduce a specification for it.


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:
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@RikkiGibson RikkiGibson requested a review from jaredpar February 26, 2024 20:18
@@ -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.
Copy link
Member

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

Copy link
Member

@jcouv jcouv left a 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)

@jaredpar jaredpar dismissed their stale review February 27, 2024 00:01

cause i want to ;)

@RikkiGibson RikkiGibson merged commit 1fae4e6 into dotnet:main Feb 27, 2024
24 checks passed
@RikkiGibson RikkiGibson deleted the generated-file-path branch February 27, 2024 00:41
@jjonescz jjonescz added this to the 17.10 P2 milestone Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants