-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Add checks for attributes in types #144195
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
base: master
Are you sure you want to change the base?
Conversation
while self.token != token::CloseBracket && self.token != token::Eof { | ||
self.bump(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here is pretty strange.
Why wouldn't we just go through the normal parsing routine to parse an attr, then go through the normal parsing routine to parse a type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, honestly didn't know about "normal parsing routine" and just go manually parsing, do you mean "parse_outer_attributes"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah probably something like that. Look at how we parse item attrs for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's see if I'm on a right way, firstly implemented it for generic then just copied for regular type, based on tests it seems works fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about it now, maybe I could yeet that logic into a helper function and not duplicate code, but first need your opinion on logic itself, but I'm out for now
|
||
let span = lo_attr.to(self.prev_token.span); | ||
|
||
if self.token.is_ident() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specifically, this only works if the type is an ident. We have lots of other types to handle here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just try parsing an attr eagerly at the beginning of parse_ty_common
, and then there doesn't need to be a special branch in this if chain?
r? compiler-errors |
This comment has been minimized.
This comment has been minimized.
5502794
to
2b4000d
Compare
Well I added a branch to handle this (you can see it in review below) error[E0107]: struct takes 1 generic argument but 0 generic arguments were supplied
--> src/main.rs:4:12
|
4 | let _: Baz<#[cfg(any())]> = todo!();
| ^^^ expected 1 generic argument
|
note: struct defined here, with 1 generic parameter: `N`
--> src/main.rs:1:8
|
1 | struct Baz<const N: usize>(i32);
| ^^^ --------------
help: add missing generic argument
|
4 | let _: Baz<N#[cfg(any())]> = todo!();
| +
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0107`. |
e267a23
to
516db8d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've called out the potentially surprising bits of this implementation with explanations to help reviewers in mine this and above reviews
tests/ui/parser/recover/recover-where-clause-before-tuple-struct-body-0.fixed
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
@fmease, I spent significant time trying to address the secondary error you mentioned. Good news: I solved it for cases where there's something inside the generic. When we have But I hit a wall with empty generics like The problem is Whatever we pick will be wrong sometimes. If I default to I can pick a statistical default (types are most common), but some cases will still get secondary errors. Is that acceptable, or do you see another approach I'm missing? The architectural issue seems fundamental since parsing happens before we know what types expect what kinds of generics. |
Thanks for making an effort to address my comments, that's highly appreciated!
Instead of returning a poisoned type (so, an |
let guar = self.dcx().emit_err(AttributeOnEmptyGeneric { span: attr_span }); | ||
return Ok(Some(GenericArg::Type(self.mk_ty(attr_span, TyKind::Err(guar))))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning a poisoned type (which is undesirable as discussed), simply bail out.
let guar = self.dcx().emit_err(AttributeOnEmptyGeneric { span: attr_span }); | |
return Ok(Some(GenericArg::Type(self.mk_ty(attr_span, TyKind::Err(guar))))); | |
let diag = self.dcx().create_err(AttributeOnEmptyGeneric { span: attr_span }); | |
return Err(diag); |
This will probably lead to the parser stopping entirely after that which is slightly sad but still absolutely acceptable. By its very nature, it flat out prevents nonsensical consequential errors like arg kind mismatches.
I think you tried the non-fatal return Ok(None)
before (I might be misremembering) which led to "nonsensical" arg count mismatches, right? I don't think they're that nonsensical, after all the "slot" is basically empty. So I'd say return Ok(None)
would be a viable alternative to return Err(…)
(unless I'm missing something) if you prefer recovery over "surrender".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a problem with both approaches
return Err(diag)
stops parsing after the first error, which is slightly overkill because making 5 tests for each case is fundamental wrong
(first error was emitted correctly and now following ones below is not found because parser died :c)
tests/ui/parser/attribute-on-empty.rs:12:5: ERROR: attributes cannot be applied here
tests/ui/parser/attribute-on-empty.rs:15:5: ERROR: attributes cannot be applied here
tests/ui/parser/attribute-on-empty.rs:18:5: ERROR: attributes cannot be applied here
And it can't pass current test
return Ok(None)
is also wrong (which I've tried but worth to mention), it gives suggestion that is makes no sense
error[E0107]: struct takes 1 generic argument but 0 generic arguments were supplied
--> $DIR/attribute-on-empty.rs:12:12
|
LL | let _: Foo<#[cfg(not(wrong))]> = todo!();
| ^^^ expected 1 generic argument
|
note: struct defined here, with 1 generic parameter: `T`
--> $DIR/attribute-on-empty.rs:4:8
|
LL | struct Foo<T>(T);
| ^^^ -
help: add missing generic argument
|
LL | let _: Foo<T#[cfg(not(wrong))]> = todo!();
| +
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return Err(diag) stops parsing after the first error, which is slightly overkill because making 5 tests for each case is fundamental wrong
return Err(diag);
is the least hacky and the simplest solution. We can always iterate from here via follow-up PRs. Three test cases are basically identical:
let _: Baz<#[cfg(any())]> = todo!();
,let _: Foo<#[cfg(not(wrong))]> = todo!();
andlet _: Bar<#[cfg(any())]> = todo!();
.
They should just be combined into one. Lastly, you can move the resulting unified "empty gen arg" case to the end of the file, so rustc still emits the other errors before it bails out. No need for more test files.
To explain myself a bit more, the "empty gen args" case is a lot less likely to happen in practice compared to all the other cases where there's an arg present, so in my eyes it's absolutely justifiable that its logic is more conservative. We can rejoice, free of any headaches, since we know there won't be any invalid or weird consequential diagnostics and since the impl is simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So completly kill parser?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, indeed. That's exactly what happens for unexpected tokens, so it's completely normal. Parse error recovery is cool and all but if it's infeasible to do robustly it just doesn't make sense to force it. As I said, it can always be improved upon in the future (tweaking spans, intro'ing GenericArg::Error
, …).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, then I just keep this Err(diag)
and also check if keeping returning poisoned arg is fine there
If that's what we are fine with, then I'm pushing this last changes from @estebank and you and that's it I guess
return Ok(Some(match arg { | ||
GenericArg::Type(_) => GenericArg::Type(self.mk_ty(attr_span, TyKind::Err(guar))), | ||
GenericArg::Const(_) => { | ||
// Create error const expression | ||
let error_expr = self.mk_expr(attr_span, ExprKind::Err(guar)); | ||
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: error_expr }) | ||
} | ||
GenericArg::Lifetime(lt) => GenericArg::Lifetime(lt), // Less common case | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to taint or poison the arg just because it has attrs. Emitting an error is already sufficient for guaranteeing a failed compilation. This whole return
could be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprisingly it is worked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fully as wanted, check this #144195 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing a return with poisoned type now says this,
--- reported in JSON output but not expected in test file ---
tests/ui/parser/issues/issue-103143.rs:2:13: ERROR: cannot find type `y` in this scope [E0412]
tests/ui/parser/issues/issue-103143.rs:2:17: ERROR: cannot find type `z` in this scope [E0412]
---
Instead of just cannot find value x in this scope
which small but a regression I'd say
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more verbose for sure but personally I don't mind it. After all, these new diagnostics are correct. On the other hand, it's conceivable that #[a]
is meant to refer to an attribute proc macro that replaces the path with sth. that does resolve (but you typoed or forgot to import), so these extra diagnostics would be noise. I'm neutral 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If returning this dummy types is not considered as a bad practice I'd keep it just for consistency
@rustbot ready |
r? compiler
Add clearer error messages for invalid attribute usage in types or generic types
fixes #135017
fixes #144132