Introduce #[diagnostic::on_type_error(message)]#155200
Introduce #[diagnostic::on_type_error(message)]#155200Unique-Usman wants to merge 1 commit intorust-lang:mainfrom
Conversation
|
Some changes occurred in compiler/rustc_passes/src/check_attr.rs cc @jdonszelmann, @JonathanBrouwer Some changes occurred in compiler/rustc_attr_parsing cc @jdonszelmann, @JonathanBrouwer Some changes occurred in compiler/rustc_hir/src/attrs |
|
r? @davidtwco rustbot has assigned @davidtwco. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
r? estebank |
|
Before you sink more time and effort into this you should explain what you want One expectation I have is that it can specialize for different types. For example if you put this attribute on struct A but the typeerror finds a B or C instead you'll want to emit different messages for them, similar to how the filtering in I'm not convinced this attribute can be implemented in a satisfying way at this time. I'm open to being convinced otherwise though, and am happy to hear your thoughts. |
|
@mejrs two things: I agree that we want filtering in the same way as #[diagnostic::on_type_error(
note = "text",
)]
struct S<T>(T);with an eye for something along the lines of #[diagnostic::on_type_error(
on(expected="Self", found="crate::K", T = "i32", note = "a"),
on(expected="crate::K", found="Self", note = "b"),
note = "c",
)]
struct S<T>(T);some time later. I believe that having the minimal functionality at least lets crate authors include the "filtering information" in the text itself ("if this is the found type, then..." or "if type parameter T is blah, ..."), and that there are already several cases in the std of unconditional addition of notes during error reporting for given types, so I think the minimal functionality is already adding value. For the filtering I would like to share the same parser between on_type_error and on_unimplemented, as it is effectively the same functionality. |
| if let ty::Adt(_, _) = parent_ty.kind() { Some(parent_ty) } else { None } | ||
| } else { | ||
| None | ||
| }; |
There was a problem hiding this comment.
as discussed we shouldn't do this now, only deal with the simple case that can be gleaned from only the expected/found mismatch.
|
Did you two have a discussion about this beforehand? I'd like to read it.
I presume you are talking about this? rust/compiler/rustc_hir_typeck/src/method/suggest.rs Lines 3156 to 3345 in e8e4541 The bit about unwrapping Option/Result is actually really impactful for learners and the way you propose the attribute it should be able to do that sort of thing (minus the inline code suggestions, obviously). So 💯 from me. Let's go for a minimal version for now, like estebank said: #[diagnostic::on_type_error(
note = "text",
)]
struct S<T>(T);I'm a bit worried about this thing being too powerful and showing up in places where authors didn't quite expect or intend. There are after all quite a few ways and places to get type errors. I think the following semantics would be reasonably useful but also restrictive enough to not spook me or the lang people.
Then in the future, when we evolve the attribute forwards, we can specify something like "if you dont supply any filter then this wrapper kind of thing is all it can do". |
There was a discussion initally, it was through video meet though, but, nothing much more than whatever is here actually.
Agreed. |
c6cbaa7 to
5b090d2
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
6366fc0 to
924b894
Compare
This comment has been minimized.
This comment has been minimized.
924b894 to
2392d19
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
| generics.span, | ||
| errors::OnTypeErrorMultipleGenerics { count: generic_count }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
should this be exactly one ?
There was a problem hiding this comment.
Yes please :) Also can you rename the error type...to something like NotExactlyOneGeneric perhaps?
| if let ty::Adt(item_def, args) = found_ty.kind() { | ||
| if let Some(Some(directive)) = | ||
| find_attr!(self.tcx, item_def.did(), OnTypeError { directive, .. } => directive) | ||
| { | ||
| let notes = self.format_on_type_error_notes( | ||
| directive, | ||
| args, | ||
| item_def.clone(), | ||
| expected_ty, | ||
| found_ty, | ||
| ); | ||
| unique_notes.extend(notes); | ||
| } | ||
| } | ||
|
|
||
| // Check expected type for attribute | ||
| if let ty::Adt(item_def, args) = expected_ty.kind() { | ||
| if let Some(Some(directive)) = | ||
| find_attr!(self.tcx, item_def.did(), OnTypeError { directive, .. } => directive) | ||
| { | ||
| let notes = self.format_on_type_error_notes( | ||
| directive, | ||
| args, | ||
| item_def.clone(), | ||
| expected_ty, | ||
| found_ty, | ||
| ); | ||
| unique_notes.extend(notes); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
should we check both found and expected or just one or based on some condition ?
There was a problem hiding this comment.
Let's just do
if the annotated type (here, S) is found but the type parameter type (here, T) is expected, then the note is displayed.
for now, and not the other way around. Please also add a test for this showing that it does nothing.
There was a problem hiding this comment.
to get understand this better, this two is applicable, i.e this feature should be applied only when the annotated type is the found and not the expected which correctly apply for this two instances?
#![feature(diagnostic_on_type_error)]
#[diagnostic::on_type_error(note = "expected {Expected}, found {Found}")]
//~^ WARN unknown diagnostic attribute
#[derive(Debug)]
struct Foo<T>(T);
fn takes_foo(_: Foo<i32>) {}
fn main() {
let foo = Foo(String::new());
takes_foo(foo);
//~^ERROR mismatched types
}
and this
#![feature(diagnostic_on_type_error)]
#[diagnostic::on_type_error(note = "expected {Expected}, found {Found}")]
//~^ WARN unknown diagnostic attribute
#[derive(Debug)]
struct Foo<T>(T);
fn takes_foo(_: String) {}
fn main() {
let foo = Foo(String::new());
takes_foo(foo);
//~^ERROR mismatched types
}
There was a problem hiding this comment.
The first example doesn't really apply to this? It compares Foo<i32> and Foo<String>, we definitely don't want that (at least not now).
This goes back to what I said about:
showing up in places where authors didn't quite expect or intend
It's important that the note only goes "one way", because people will write things like this without thinking about it very much:
#[diagnostic::on_type_error(note = "call unwrap() to get the inner value")]
enum MyOption<T> {
Some(T),
None,
}But users will write code like this:
fn take_myoption_u8(_: MyOption<u8>) {}
take_myoption_u8(12_u8);With your current implementation they'll get a note saying that you can unwrap a u8 to get an option. Which is definitely wrong. That advice is only correct if you have a MyOption<u8> but you need a u8. This is why I think we should only show the note if it goes "inward".
| @@ -223,6 +224,7 @@ symbols! { | |||
| FnPtr, | |||
| Formatter, | |||
| Forward, | |||
| Found, // used for formating for diagnostic::on_type_error for diagnostic purposes | |||
There was a problem hiding this comment.
I think allowing Found and Expected is a good idea thoughts ?
This comment has been minimized.
This comment has been minimized.
2392d19 to
54106f8
Compare
This comment has been minimized.
This comment has been minimized.
Suggested-by: Esteban Küber <esteban@kuber.com.ar> Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
54106f8 to
b5b3b9e
Compare
There was a problem hiding this comment.
General feedback regarding the approach:
- Please do not use the generics machinery for
FoundandExpected. Take a look at how rustc_on_unimplemented'sThisworks; it can be parsed similarly (grep forsym::This), and you can supply it by making new field(s) in theFormatArgsstruct. - I'm on the fence on allowing
{Self}. It feels like one of those things where you think you know what it does but you might be wrong. That's what I like aboutFoundandExpectedas well; it forces you to consider what you actually want. If it's useful we can also enableThisto refer unambiguously to the annotated item, like for rustc_on_unimplemented. Thoughts? - In your tests you have a lot of strings like
note = "expected {Expected}, found {Found}", but at a glance it's really hard to tell if this is emitted by the compiler somewhere or if it's from this attribute. Please change these to something that unambiguously looks "testy". - We talked about implementing this for method calls as well. It's up to you if you implement that here (can be a follow up PR), but either way please add some tests now to document the current behavior.
- More testing in general:
-
- cross crate use
-
- lifetime and const generics.
-
- check that Expected and Found is rejected for other diagnostic attributes
-
- duplicate uses of the attribute on the same item (these should coalesce).
So far I haven't looked too deep at how this hooks into the existing diagnostics machinery as I'm not familiar with it. That's more @estebank's area of expertise anyway. Maybe I'll have more review about that later.
| (Mode::DiagnosticOnTypeError, sym::message) | ||
| | (Mode::DiagnosticOnTypeError, sym::label) => { | ||
| malformed!() | ||
| } |
There was a problem hiding this comment.
| (Mode::DiagnosticOnTypeError, sym::message) | |
| | (Mode::DiagnosticOnTypeError, sym::label) => { | |
| malformed!() | |
| } |
Keep in mind that the complexity of this entire match will grow over time as more attributes and options get added. Can you instead change the existing arms to match on the diagnostic attributes that do allow message and label?
With that, I mean:
match (mode, name) {
(Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | /* etc */ , sym::message) => {
// ...
}etc.
While it is more wordy, it is easier to understand from a control flow perspective. You can also do use Mode::*; inside this function to use the enum variant names by themselves.
| .try_report_from_nll() | ||
| .or_else(|| { | ||
| if let SubregionOrigin::Subtype(trace) = cause { | ||
| tracing::info!("borrow checker"); |
There was a problem hiding this comment.
| tracing::info!("borrow checker"); |
if it's useful to you then leave it for now, but it should be removed at some point
There was a problem hiding this comment.
it is noise, will remove it.
| declare_lint! { | ||
| /// The `on_type_error_multiple_generics` lint detects when | ||
| /// `#[diagnostic::on_type_error]` is used on items with more than one generic parameter. | ||
| /// | ||
| /// ### Example | ||
| /// | ||
| /// ```rust | ||
| /// #[diagnostic::on_type_error(note = "Expected {T}")] | ||
| /// struct Foo<T, U>(T, U); | ||
| /// ``` | ||
| /// | ||
| /// {{produces}} | ||
| /// | ||
| /// ### Explanation | ||
| /// | ||
| /// The `#[diagnostic::on_type_error]` attribute currently supports at most one | ||
| /// generic parameter. | ||
| /// | ||
| /// [reference]: https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#the-diagnostic-tool-attribute-namespace | ||
| pub ON_TYPE_ERROR_MULTIPLE_GENERICS, | ||
| Warn, | ||
| "detects use of #[diagnostic::on_type_error] with multiple generic parameters", | ||
| } | ||
|
|
There was a problem hiding this comment.
This lint feels like an overly specific case of "you're using the attribute wrong". I guess we don't really have a specific lint for that, maybe MALFORMED_DIAGNOSTIC_ATTRIBUTES?
It also looks like it isn't going to age well when we start expanding what on_type_error can do.
I propose we drop the lint and just use MALFORMED_DIAGNOSTIC_ATTRIBUTES for now. Adding a more general "you're using it wrong" lint is out of scope of this PR and also requires lang team discussion etc, so let's not do that here.
There was a problem hiding this comment.
Yeah, that is valid. Will remove.
| Eq, | ||
| Equal, | ||
| Err, | ||
| Expected, // used for formatting for diagnostic::on_type_error for diagnostic purposes |
There was a problem hiding this comment.
| Expected, // used for formatting for diagnostic::on_type_error for diagnostic purposes | |
| Expected, |
There's no need to explain this, people can just grep sym::Expected if they're curious. Same for Found below.
| } | ||
| } | ||
|
|
||
| // Order doesn't matter for notes, so we can allow the lint |
There was a problem hiding this comment.
The order does matter; this will cause instability in the test suite. The stderr output will randomly change and it will greatly annoy someone.
We should pick a consistent order here. I'd just put them into a vec and check for duplicates. Also, we should pick whether we want the attribute's notes displayed above or below the compiler's. I think below makes most sense?
There was a problem hiding this comment.
Yeah, I think there is also some data structure in the compiler that helps with ordering.
Attribute notes above could be better from the perspective that, I want the reader of the error to get the better and the specific notes which means the attribute note.
|
Reminder, once the PR becomes ready for a review, use |
Maybe we should just disallow
Noted.
Noted.
Noted. Thanks for the review. |
|
I didn't notice until now, but not all your tests have explicit |
| impl<S: Stage> AttributeParser<S> for OnTypeErrorParser { | ||
| const ATTRIBUTES: AcceptMapping<Self, S> = &[( | ||
| &[sym::diagnostic, sym::on_type_error], | ||
| template!(List: &[r#" note = "...""#]), |
There was a problem hiding this comment.
| template!(List: &[r#" note = "...""#]), | |
| template!(List: &[r#"note = "...""#]), |
|
☔ The latest upstream changes (presumably #155596) made this pull request unmergeable. Please resolve the merge conflicts. |
View all comments