Skip to content

Instantly share code, notes, and snippets.

@petrochenkov
Created July 2, 2024 14:29
Show Gist options
  • Select an option

  • Save petrochenkov/bf468de04386e1dfd0bd1973c1ad2e9c to your computer and use it in GitHub Desktop.

Select an option

Save petrochenkov/bf468de04386e1dfd0bd1973c1ad2e9c to your computer and use it in GitHub Desktop.

Let's talk about expressions, the same approach generalizes to other nodes too.

Suppose we have an expression that is passed to a macro as an argument.

macro_rules! mac {
    ($expr:expr) => {
        $expr + 1
    }
}

mac!(a);
mac!(a + b);
block | expr | ident | item | lifetime | literal
meta | pat | pat_param | path | stmt | tt | ty | vis

We want to make a suggestion to add some code to the left or to the right of the expression. I'll assume that a piece of code has the same context for left and right insertions, and not separate contexts. Spans of tokens to the left/right are available during parsing,

  • tt - cannot add & to input, &a doesn't match tt
  • ident - cannot add & to input, &a doesn't match ident
  • path - cannot add & to input, &a doesn't match path
  • expr - can add & to input, &a still matches expr
  • (tt)* - can add & to input, &a still matches a tt sequence

Always can add & inside the macro body: &$expr, &$path, &$ident, &$tt will all work. Still not reliable though, the suggestion can make sense for one expansion of a macro, and not make sense for another expansion of the same macro.

Any code removal may generally have the same issues as code insertion, but to a less extent because you cannot remove anything from tt or ident, and removing something from expr or path still keep it an expr or path.

Need access to HIR ID to get surrounding span as a span of the HIR parent node. If HIR ID is unavailable the parent span needs to be passed from a place where it is available. Doesn't work before HIR is built, need to pass parent spans around manually.

Interesting example: suggestion to add unsafe(...) around unsafe attributes. Can only be inserteed into input if the fragment is passed as meta, can only be inserted into macro body if the fragment is passed as ident/tt. Hard to determine the parent context because there is currently no node to save the span of $meta in AST. Idea: can try to add it to the metavar span table, or just skip the suggestion.

Tokens from (tt)* sequences don't currently keep metavar spans. So span_in_ctxt will be the same as span. Typically used for passing arbitrarily large amounts of code that needs to be considered by itself, and not in context of a macro.

Which span to keep in AST/HIR

  • Exact span - better for showing any diagnostics
  • Span in context - better for suggesting changes to code

Strict alternative

Do not emit code insertion suggestions at all for ... what exactly?

Need to know both exact and in-context spans.

  • If span == span_in_ctxt.
    • If !from_expansion(span)
      • Can make a suggestion
    • If from_expansion(span)
      • Cannot make a suggestion
      • If the span is from something that we cannot modify (external macro, desugaring, maybe procedural macro), then we certainly cannot make a suggestion.
      • If the span is from a "good" macro (e.g. local macro_rules) then the suggestion will be unreliable due to the hazard of being specific to a single macro expansion but not others.
  • If span != span_in_ctxt
    • Cannot make a reliable suggestion

Less strict alternative

Emit the suggestions with Applicability::MaybeIncorrect or something like that for suspicious cases, e.g.

  • span == span_in_ctxt && in_good_macro(span)
  • TODO

A number of existing heuristics

Used randomly and inconsistently. find_ancestor_inside find_ancestor_in_same_ctxt find_ancestor_inside_same_ctxt find_oldest_ancestor_in_same_ctxt can_be_used_for_suggestions from_expansion in_derive_expansion is_desugaring in_external_macro is_from_async_await

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment