Skip to content

Commit

Permalink
salsa-macros: handle invalid inputs in a way friendlier to rust-analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
jhgg committed Oct 24, 2024
1 parent 254c749 commit e0febd9
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 15 deletions.
5 changes: 3 additions & 2 deletions components/salsa-macros/src/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use proc_macro2::TokenStream;
use crate::{
hygiene::Hygiene,
options::{AllowedOptions, Options},
token_stream_with_error,
};

// #[salsa::accumulator(jar = Jar0)]
Expand All @@ -14,7 +15,7 @@ pub(crate) fn accumulator(
) -> proc_macro::TokenStream {
let hygiene = Hygiene::from1(&input);
let args = syn::parse_macro_input!(args as Options<Accumulator>);
let struct_item = syn::parse_macro_input!(input as syn::ItemStruct);
let struct_item = parse_macro_input!(input as syn::ItemStruct);
let ident = struct_item.ident.clone();
let m = StructMacro {
hygiene,
Expand All @@ -23,7 +24,7 @@ pub(crate) fn accumulator(
};
match m.try_expand() {
Ok(v) => crate::debug::dump_tokens(ident, v).into(),
Err(e) => e.to_compile_error().into(),
Err(e) => token_stream_with_error(input, e),
}
}

Expand Down
8 changes: 4 additions & 4 deletions components/salsa-macros/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use syn::parse::Nothing;

use crate::hygiene::Hygiene;
use crate::{hygiene::Hygiene, token_stream_with_error};

// Source:
//
Expand All @@ -16,11 +16,11 @@ pub(crate) fn db(
) -> proc_macro::TokenStream {
let _nothing = syn::parse_macro_input!(args as Nothing);
let hygiene = Hygiene::from1(&input);
let input = syn::parse_macro_input!(input as syn::Item);
let item = parse_macro_input!(input as syn::Item);
let db_macro = DbMacro { hygiene };
match db_macro.try_db(input) {
match db_macro.try_db(item) {
Ok(v) => crate::debug::dump_tokens("db", v).into(),
Err(e) => e.to_compile_error().into(),
Err(e) => token_stream_with_error(input, e),
}
}

Expand Down
5 changes: 3 additions & 2 deletions components/salsa-macros/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
hygiene::Hygiene,
options::Options,
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions},
token_stream_with_error,
};
use proc_macro2::TokenStream;

Expand All @@ -16,15 +17,15 @@ pub(crate) fn input(
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as InputArgs);
let hygiene = Hygiene::from1(&input);
let struct_item = syn::parse_macro_input!(input as syn::ItemStruct);
let struct_item = parse_macro_input!(input as syn::ItemStruct);
let m = Macro {
hygiene,
args,
struct_item,
};
match m.try_macro() {
Ok(v) => v.into(),
Err(e) => e.to_compile_error().into(),
Err(e) => token_stream_with_error(input, e),
}
}

Expand Down
6 changes: 3 additions & 3 deletions components/salsa-macros/src/interned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
db_lifetime,
hygiene::Hygiene,
options::Options,
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions},
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions}, token_stream_with_error,
};
use proc_macro2::TokenStream;

Expand All @@ -17,15 +17,15 @@ pub(crate) fn interned(
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as InternedArgs);
let hygiene = Hygiene::from1(&input);
let struct_item = syn::parse_macro_input!(input as syn::ItemStruct);
let struct_item = parse_macro_input!(input as syn::ItemStruct);
let m = Macro {
hygiene,
args,
struct_item,
};
match m.try_macro() {
Ok(v) => v.into(),
Err(e) => e.to_compile_error().into(),
Err(e) => token_stream_with_error(input, e),
}
}

Expand Down
23 changes: 21 additions & 2 deletions components/salsa-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ macro_rules! parse_quote {
}
}

/// Similar to `syn::parse_macro_input`, however, when a parse error is encountered, it will return
/// the input token stream in addition to the error. This will make it so that rust-analyzer can work
/// with incomplete code.
macro_rules! parse_macro_input {
($tokenstream:ident as $ty:ty) => {
match syn::parse::<$ty>($tokenstream.clone()) {
Ok(data) => data,
Err(err) => {
return $crate::token_stream_with_error($tokenstream, err);
}
}
};
}

mod accumulator;
mod db;
mod db_lifetime;
Expand Down Expand Up @@ -64,9 +78,14 @@ pub fn tracked(args: TokenStream, input: TokenStream) -> TokenStream {

#[proc_macro_derive(Update)]
pub fn update(input: TokenStream) -> TokenStream {
let item = syn::parse_macro_input!(input as syn::DeriveInput);
let item = parse_macro_input!(input as syn::DeriveInput);
match update::update_derive(item) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
Err(error) => token_stream_with_error(input, error),
}
}

pub(crate) fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
tokens.extend(TokenStream::from(error.into_compile_error()));
tokens
}
6 changes: 4 additions & 2 deletions components/salsa-macros/src/tracked.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use syn::{spanned::Spanned, Item};

use crate::token_stream_with_error;

pub(crate) fn tracked(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = syn::parse_macro_input!(input as Item);
let item = parse_macro_input!(input as Item);
let res = match item {
syn::Item::Struct(item) => crate::tracked_struct::tracked_struct(args, item),
syn::Item::Fn(item) => crate::tracked_fn::tracked_fn(args, item),
Expand All @@ -16,6 +18,6 @@ pub(crate) fn tracked(
};
match res {
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
Err(err) => token_stream_with_error(input, err),
}
}
6 changes: 6 additions & 0 deletions tests/compile-fail/input_struct_incompatibles.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ error: `#[id]` cannot be used with `#[salsa::input]`
21 | / #[id]
22 | | field: u32,
| |______________^

error: cannot find attribute `id` in this scope
--> tests/compile-fail/input_struct_incompatibles.rs:21:7
|
21 | #[id]
| ^^
6 changes: 6 additions & 0 deletions tests/compile-fail/interned_struct_incompatibles.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ error: `#[id]` cannot be used with `#[salsa::interned]`
33 | / #[id]
34 | | field: u32,
| |______________^

error: cannot find attribute `id` in this scope
--> tests/compile-fail/interned_struct_incompatibles.rs:33:7
|
33 | #[id]
| ^^
31 changes: 31 additions & 0 deletions tests/compile-fail/tracked_fn_incompatibles.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ error: only a single lifetime parameter is accepted
67 | fn tracked_fn_with_multiple_lts<'db1, 'db2>(db: &'db1 dyn Db, interned: MyInterned<'db2>) -> u32 {
| ^^^^

error: `self` parameter is only allowed in associated functions
--> tests/compile-fail/tracked_fn_incompatibles.rs:27:55
|
27 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {}
| ^^^^^ not semantically valid as function parameter
|
= note: associated functions are those in `impl` or `trait` definitions

error[E0415]: identifier `input` is bound more than once in this parameter list
--> tests/compile-fail/tracked_fn_incompatibles.rs:33:5
|
33 | input: MyInput,
| ^^^^^ used as parameter more than once

error[E0106]: missing lifetime specifier
--> tests/compile-fail/tracked_fn_incompatibles.rs:61:15
|
Expand All @@ -64,3 +78,20 @@ error[E0308]: mismatched types
| ----------------- implicitly returns `()` as its body has no tail or `return` expression
24 | fn tracked_fn_with_one_input(db: &dyn Db) -> u32 {}
| ^^^ expected `u32`, found `()`

error[E0308]: mismatched types
--> tests/compile-fail/tracked_fn_incompatibles.rs:27:78
|
27 | fn tracked_fn_with_receiver_not_applied_to_impl_block(&self, db: &dyn Db) -> u32 {}
| -------------------------------------------------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_fn_incompatibles.rs:34:6
|
30 | fn tracked_fn_with_too_many_arguments_for_specify(
| ---------------------------------------------- implicitly returns `()` as its body has no tail or `return` expression
...
34 | ) -> u32 {
| ^^^ expected `u32`, found `()`
127 changes: 127 additions & 0 deletions tests/compile-fail/tracked_impl_incompatibles.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,69 @@ error: unexpected token
41 | #[salsa::tracked(constructor = Constructor)]
| ^^^^^^^^^^^

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:12:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
12 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:17:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
17 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:22:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
22 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:27:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
27 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:32:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
32 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:37:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
37 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0119]: conflicting implementations of trait `Default` for type `MyTracked<'_>`
--> tests/compile-fail/tracked_impl_incompatibles.rs:42:1
|
7 | impl<'db> std::default::Default for MyTracked<'db> {
| -------------------------------------------------- first implementation here
...
42 | impl<'db> std::default::Default for MyTracked<'db> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyTracked<'_>`

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> tests/compile-fail/tracked_impl_incompatibles.rs:47:1
|
Expand All @@ -57,6 +120,70 @@ error[E0117]: only traits defined in the current crate can be implemented for ar
|
= note: define and implement a trait or new type instead

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:8:21
|
8 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:13:21
|
13 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:18:21
|
18 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:23:21
|
23 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:28:21
|
28 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:33:21
|
33 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:38:21
|
38 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:43:21
|
43 | fn default() -> Self {}
| ------- ^^^^ expected `MyTracked<'_>`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
--> tests/compile-fail/tracked_impl_incompatibles.rs:48:21
|
Expand Down
Loading

0 comments on commit e0febd9

Please sign in to comment.