rust-lang / rust-analyzer

A Rust compiler front-end for IDEs
https://rust-analyzer.github.io/
Apache License 2.0
14.2k stars 1.59k forks source link

Quasy quoting for assists #2227

Open matklad opened 4 years ago

matklad commented 4 years ago

Assist (and, once we have more of those, fixits and refactorings) need to produce a lot of syntax trees.

This is done with the help of make module at the moment. Here's how a moderately-complex let statement is produced:

let match_expr = {
    let happy_arm = make::match_arm(
        once(
            make::tuple_struct_pat(
                path,
                once(make::bind_pat(make::name("it")).into()),
            )
            .into(),
        ),
        make::expr_path(make::path_from_name_ref(make::name_ref("it"))).into(),
    );

    let sad_arm = make::match_arm(
        once(make::placeholder_pat().into()),
        early_expression.into(),
    );

    make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
};

let let_stmt = make::let_stmt(
    make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
    Some(match_expr.into()),
);

It would be sweet if the above code could be condensed to the following:

let let_stmt = magic! {
    let $bound_ident = match $cond_expr {
        $path(it) => it,
        _ => $early_expression,
    }
};

There exists a trivial solution: use format!, produce a string, re-parse string back into an AST. This actually works great! This is the solution employed by IntelliJ Rust(example), and I don't remember having any problems with it.

However, adding more type-safety seems like a good thing to try! So, the magic! macro should ideally produce the code above, and not some formatting-based thing.

matklad commented 4 years ago

Random thoughts:

Veykril commented 3 years ago

I brainstormed a bit about this a week ago whether it would be possible to do this with macros-by-example, but I believe due to the nature of this trying to parse a tree as well as emitting a tree-ish expression this won't be possible to model with such a macro. How is RAs stance in regards to containing proc-macros as with such it should be fairly simple to implement I think?

matklad commented 3 years ago

I think we should start adding some proc macros, if only to nudge ourselves into supporting them better. Not entirely sure that this will be simple to implement though, but it’s definitelly is worth trying

Veykril commented 3 years ago

Not entirely sure that this will be simple to implement though

Ye my choice of words was a bit unfortunate, I did not intend to say it would be easy to do.

I'll try exploring this sometime.

DropDemBits commented 8 months ago
  • handing of whitespace is a known unknown

Whitespace handling could probably be done using an autoformatter, or more reasonably using an autoindenter plus having the make constructors include the necessary whitespace and newline bits

  • if we fail at fully type-safe solution, we might resort to a build string & reparse solution, with an additional runtime check that each interpolated value preserves it's kind after a reparse.

It'd be nice to not have to resort to the stringify & reparse solution for the sake of tracking nodes easily, but that's probably my experience with the structured snippet API talking (which relies on being able to find nodes in the final post-edit tree) 😅

On an unrelated note, I was curious if the make constructors could be generated (or mostly generated) from the rust.ungrammar file? I'm not 100% sure about the feasibility of this idea though, and it may just be easier to add make constructors as we incrementally cover more and more syntax.

Edit: I see I'm not the first to think about that idea (see https://github.com/rust-lang/rust-analyzer/issues/779#issuecomment-462184072)

Veykril commented 8 months ago

The make api should be autogenerated yes, I've been meaning to touch upon that part soonish as i have a similar set up for a toy language now. Having our own formatter is also something we want still, its just also not implemented yet.

DropDemBits commented 2 weeks ago

Should we have separate quasi-quoting macros for the different parsing entry points (e.g. ty, expr, pattern, item, stmt)? Not sure if that would be needed as you could generate a tree for a higher-level syntax construct and grab the specific AST node you need, though it'd also be nice to not have to dig for the node.