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

SyntaxTree.GetLineMappings #53752

Open
tmat opened this issue May 28, 2021 · 12 comments
Open

SyntaxTree.GetLineMappings #53752

tmat opened this issue May 28, 2021 · 12 comments
Labels
api-approved API was approved in API review, it can be implemented Area-Compilers blocking API needs to reviewed with priority to unblock work Concept-API This issue involves adding, removing, clarification, or modification of an API. Feature Request

Comments

@tmat
Copy link
Member

tmat commented May 28, 2021

Background and Motivation

EnC analyzer requires the ability to enumerate all #line mappings in a source file.

Proposed API

class SyntaxTree
{/// <summary>
        /// Returns empty sequence if there are no line mapping directives in the tree.
        /// Otherwise, returns a sequence of pairs of spans: each describing a mapping of a span of the tree between two consecutive #line directives.
        /// If the first directive is not on the first line the first pair describes mapping of the span preceding the first directive.
        /// The last pair of the sequence describes mapping of the span following the last #line directive.
        /// </summary>
        /// <returns>
        /// Empty sequence if the tree does not contain a line mapping directive.
        /// Otherwise a non-empty sequence of <see cref="LineMapping"/>.
        /// </returns>
        public abstract IEnumerable<LineMapping> GetLineMappings(CancellationToken cancellationToken = default);}
 
class CSharpSyntaxTree
{public override IEnumerable<LineMapping> GetLineMappings(CancellationToken cancellationToken = default);}
 
class VisualBasicSyntaxTree
{public override IEnumerable<LineMapping> GetLineMappings(CancellationToken cancellationToken = default);}
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Represents a line mapping defined by a single line mapping directive (<c>#line</c> in C# or <c>#ExternalSource</c> in VB).
    /// </summary>
    [DataContract]
    public readonly struct LineMapping : IEquatable<LineMapping>
    {
        /// <summary>
        /// The span in the syntax tree containing the line mapping directive.
        /// </summary>
        [DataMember(Order = 0)]
        public readonly LinePositionSpan Span { get; }
 
        /// <summary>
        /// If the line mapping directive maps the span into an explicitly specified file the <see cref="FileLinePositionSpan.HasMappedPath"/> is true.
        /// If the path is not mapped <see cref="FileLinePositionSpan.Path"/> is empty and <see cref="FileLinePositionSpan.HasMappedPath"/> is false.
        /// If the line mapping directive marks hidden code <see cref="FileLinePositionSpan.IsValid"/> is false.
        /// </summary>
        [DataMember(Order = 1)]
        public readonly FileLinePositionSpan MappedSpan { get; }
 
        public LineMapping(LinePositionSpan span, FileLinePositionSpan mappedSpan);
 
        public void Deconstruct(out LinePositionSpan span, out FileLinePositionSpan mappedSpan);
 
        /// <summary>
        /// True if the line mapping marks hidden code.
        /// </summary>
        public bool IsHidden { get; }
 
        public override bool Equals(object? obj);
        public bool Equals(LineMapping other);
        public override int GetHashCode();
        public static bool operator ==(LineMapping left, LineMapping right);
        public static bool operator !=(LineMapping left, LineMapping right);;
        public override string? ToString()
    }
}

Usage Examples

            foreach (var (unmappedSection, mappedSection) in oldTree.GetLineMappings(cancellationToken))
            {
               var targetPath = mappedSection.HasMappedPath ? mappedSection.Path : oldTree.FilePath;

                ...
            }

https://github.com/dotnet/roslyn/pull/53735/files#diff-5ee00551d4ea54b98dfc33510c5ea4be279cd1cc11d40a2329a9ad506e75681bR183

Alternative Designs

Risks

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels May 28, 2021
@tmat tmat added Feature Request Concept-API This issue involves adding, removing, clarification, or modification of an API. Area-Compilers Urgency-Soon blocking API needs to reviewed with priority to unblock work api-ready-for-review API is ready for review, it is NOT ready for implementation and removed Area-IDE Urgency-Soon labels May 28, 2021
@RikkiGibson
Copy link
Contributor

Ping @333fred @dotnet/roslyn-api-owners

@333fred
Copy link
Member

333fred commented Jun 2, 2021

Why make GetLineMappings return IEnumerable? I would expect them to return ImmutableArray.

@333fred 333fred added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jun 2, 2021
@333fred
Copy link
Member

333fred commented Jun 2, 2021

Also, API owners should be the ones applying ready-for-review, just as an FYI. @CyrusNajmabadi as the other API owner of syntax.

@tmat
Copy link
Member Author

tmat commented Jun 2, 2021

@333fred The caller might not necessarily need to enumerate all of the mappings if it only looks for the first one matching some criteria.

@333fred
Copy link
Member

333fred commented Jun 3, 2021

@333fred The caller might not necessarily need to enumerate all of the mappings if it only looks for the first one matching some criteria.

Is calculating the mappings expensive? I guess I don't see the benefits that IEnumerable gets us here.

@tmat
Copy link
Member Author

tmat commented Jun 3, 2021

There can be 1000s of #line directives in a file. Why allocate and calculate all that if the caller needs to find the first one, for example?

@333fred
Copy link
Member

333fred commented Jun 3, 2021

There can be 1000s of #line directives in a file. Why allocate and calculate all that if the caller needs to find the first one, for example?

Do we think that's a common use case? If needing to find just one span is going to be the thing people need to do, it seems to me like the appropriate API shape would instead be bool TryGetLineMapping(this CSharpSyntaxTree tree, LinePositionSpan originalSpan, out FileLinePositionSpan mappedSpan).

@tmat
Copy link
Member Author

tmat commented Jun 3, 2021

I don't see why IEnumerable is bad.

@CyrusNajmabadi
Copy link
Member

We have plenty of precedence here for using IEnumerable in our apis. Usually teh core question is: will this be iterated many times during expected consumption. In that case, we def want IMmutableArray to effectively make the later consumption 'effectively free'. In other words, no allocations to consume. No virtual indirecitons, etc.

However, for stuff that really may be computed lazily, and which is only intended to be read a tiny handful of times per user scenario, an IEnumerable is fine. So it comes down to how @tmat thinks this will be used for EnC. If it's just going ot be read once/twice while doing some mapping operation against other data, then that's fine. If we think this data will need to be read a lot though, then we should realize this as an ImmutableArray.

@tmat
Copy link
Member Author

tmat commented Jun 3, 2021

The method provides another view of data already stored in an array in the underlying implementation (essentially just a translation). EnC currently uses it in multiple ways: 1) enumerates all items 2) finds first mapping that contains given mapped span (note that this requires O(n) since the underlying array is sorted by unmapped location, not mapped), 3) checks if there are any significant line mappings by calling .Any().

I believe IEnumerable is good for all of these use cases.

@jaredpar jaredpar removed the untriaged Issues and PRs which have not yet been triaged by a lead label Jun 3, 2021
@333fred
Copy link
Member

333fred commented Jun 3, 2021

EnC currently uses it in multiple ways: 1) enumerates all items 2) finds first mapping that contains given mapped span (note that this requires O(n) since the underlying array is sorted by unmapped location, not mapped), 3) checks if there are any significant line mappings by calling .Any().

Thanks, this was the context I was missing. Provided @CyrusNajmabadi has no objections, I think this is fine to move ahead. We'll review in the next API review session, but implementation work doesn't need to be gated on it.

@333fred 333fred added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Jun 3, 2021
@333fred
Copy link
Member

333fred commented Jun 17, 2021

API Review

  • How will the new #line changes be incorporated?
    • Offset should be added before we ship.
  • Otherwise looks good.

@333fred 333fred added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jun 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented Area-Compilers blocking API needs to reviewed with priority to unblock work Concept-API This issue involves adding, removing, clarification, or modification of an API. Feature Request
Projects
None yet
Development

No branches or pull requests

5 participants