Interfaces errors emit better notes - #12772
Conversation
… missing member error The error is already generated in `property_matches_interface`. DRY.
`Element::lookup_property` walks the base type chain, but the node that declares a member was only reachable for local declarations. Add a lookup that mirrors how `lookup_property` is split across `Element` and `ElementType`, and replace the two hand-rolled walks in the LSP with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…conflicting statements Previously this function only added notes for local declarations. Now we can use Element::property_declaration_node to get the declaration on the inherited node that causes the problem.
This is a private function that is used by both public functions `validate_self_implement_statements` and `apply_child_implement_statements`. This means that the interface validation logic for an element is consolidated into one path. As a consequence, the `apply_child_implement_statements` path now emits notes for declarations that conflict with an interface.
…conflicts on base components Emit a note on the base component when a declaration in the base component does not satisfy the interface in an `implement Interface <=> child-id;` statement.
…ented interface
Add a note that highlights where the conflict comes from. We end up with
multiple notes because we want one per "Cannot override" error:
```
error: Cannot override 'reset' from 'ValidInterface'
--> internal/compiler/tests/syntax/interfaces/implement_child.slint:163:26
|
163 | public pure function reset() {}
| ^^^^^
note: 'ValidInterface' declares 'pure public function reset() { }'
--> internal/compiler/tests/syntax/interfaces/implement_child.slint:153:15
|
153 | implement ValidInterface <=> base;
| ^^^^^^^^^^^^^^^
error: Cannot override 'speak' from 'ValidInterface'
--> internal/compiler/tests/syntax/interfaces/implement_child.slint:161:14
|
161 | callback speak();
| ^^^^^
note: 'ValidInterface' declares 'callback speak();'
--> internal/compiler/tests/syntax/interfaces/implement_child.slint:153:15
|
153 | implement ValidInterface <=> base;
| ^^^^^^^^^^^^^^^
error: Cannot override 'value' from 'ValidInterface'
--> internal/compiler/tests/syntax/interfaces/implement_child.slint:159:27
|
159 | in-out property <int> value;
| ^^^^^
note: 'ValidInterface' declares 'in-out property <int> value;'
--> internal/compiler/tests/syntax/interfaces/implement_child.slint:153:15
|
153 | implement ValidInterface <=> base;
|
```
The source now highlights the syntax that is incorrect, and the note
says what the interface declares. Coupled with the actual error message,
this should give the user the full picture of what the error is, where
the error originates and where to apply the fix.
In order to find the correct source location for the diagnostic we have
to hunt through the syntax nodes to find an identifier with the expected
keyword, as the SyntaxNodes do not provide enough granularity to extract
visibility or purity directly. If we can't find the expected anchor we
try to fall back to the name identifier, and worst case we refer to the
whole declaration (which does not happen in practise).
One side effect is that duplicate notes can be a bit repetitive:
```
error: Cannot implement 'OutOnlyInterface'.
- 'count' must be a 'int' property (found a 'float' property)
- 'count' must be 'out' (found 'in-out')
--> internal/compiler/tests/syntax/interfaces/implement_self.slint:286:15
|
286 | implement OutOnlyInterface <=> self;
| ^^^^^^^^^^^^^^^^^
note: 'OutOnlyInterface' declares 'count' as 'out property <int> count;'
--> internal/compiler/tests/syntax/interfaces/implement_self.slint:289:22
|
289 | in-out property <float> count;
| ^^^^^
note: 'OutOnlyInterface' declares 'count' as 'out property <int> count;'
--> internal/compiler/tests/syntax/interfaces/implement_self.slint:289:5
|
289 | in-out property <float> count;
| ^^^^^^
```
We considered having one "'Interface' declares 'member' as" error and
having multiple more specific notes, but the notes are not currently
visible in the IDE.
This allows us to add `syntax_for_lookup_result` in a future commit. We want to compare the syntax for PropertyDeclaration and PropertyLookupResult. This allows us to re-use the display code.
… functions and callbacks This means we don't get the `function() -> void` output for void returning callbacks/functions.
`-> void` in diagnostics can be unexpected because Slint provides the void return type by default.
…or Function Now that it doesn't print the void return type we can use it directly, instead of re-implementing it.
5ebc3a1 to
63461e9
Compare
LeonMatthes
left a comment
There was a problem hiding this comment.
The more detailed spans are great, but I find the new note diagnostics somewhat misleading.
But we can probably fix that with a bit of rewording.
|
|
||
| component IncorrectType { | ||
| in-out property <float> value; | ||
| // > <note{'ValidInterface' declares 'value' as 'in-out property <int> value;'} |
There was a problem hiding this comment.
Hm, I find this phrasing somewhat confusing.
This sounds like the diagnostic is pointing at the declaration within ValidInterface, but it is instead pointing to the conflicting implementation that we are checking against.
That is the right thing to point at, but the message should not suggest that it is showing the ValidInterface declaration.
Maybe a rewording could be:
| // > <note{'ValidInterface' declares 'value' as 'in-out property <int> value;'} | |
| // > <note{'value' declared here conflicts with 'ValidInterface' (expected 'in-out property <int> value;')} |
I'm still not entirely happy with it, so suggestions appreciated :)
There was a problem hiding this comment.
I wasn't super happy with this either, and agree that the wording probably could be improved. In particular I wondered if the note should also say what the problem is. However, the error diagnostic is what is visible to the user in the IDE, and must contain the error. The note is only visible in the compiler output (AFAIK) and always relates to the error, so we get:
- error: there is an error here, caused by the
implementkeyword (normally) and these are the problems; - notes: this is where the declaration that conflicts with the interface is, it is probably this particular part of the declaration, and this is what the interface declares/actually expects.
Maybe the note should say something more like: 'Foo' declares 'bar' here. 'Interface' expects 'in-out' property (found 'in'). Previously you said you wanted the code that the interface expects, but I'm not sure how best to fit that in as well. It could be part of the error.
… string A small review cleanup. `return_type` could be used directly in the format string, instead of as a parameter. This function now uses the same approach for all named parameters.
The list of static strings is likely to get out-of-date. Instead, we capture the PropertyVisibility to find in the DeclarationAnchor::Visibility variant and match on the display string it returns. We must take care to normalize `_` to `-`.
…e_interface_member_implementation` This doesn't change the number of errors reported, just the error message we get. The new message is more specific, containing the actual issue with the conflict (e.g. wrong property type) rather than just saying "there is a conflict". This also simplifies the implementation of `validate_interface_member_implementation`.
Allow us to verify the diagnostic behaviour of interface conflicts with properties on builtin elements. The Viewport interface has properties which conflict with Flickable, where the `viewport-x` and `viewport-y` properties are `length` types.
…ssages Previously `child_with_conflicts` would be referred to as `child-with-conflicts` in diagnostic messages, which does not match up with the id used in the .slint source.
The note diagnostics generally point to a location that conflicts with an interface member. The previous note made it sound like the diagnostic was pointing at the declaration within the interface. Now we make it clear that the enclosing element is declaring the conflicting member. This is done by naming either the component or the id of the child element that contains the conflicting member.
f1c6334 to
b2cf74d
Compare
When an interface is not satisfied by the implementing component we emit notes that attempt to highlight the particular syntax that is causing the problem.
In some cases, it is not possible to highlight the particular syntax causing the issue (e.g. missing
purekeyword) so we fall back to emitting a note on the name identifier of the declarations:In some cases we may emit multiple notes for the same error. We aggregate errors for each
implementstatement into one error string, and emit notes for each part of the syntax that is incorrect:This might appear a bit repetitive, but the error text must contain the information needed to identify the error - the note does not appear in the IDE (otherwise we might emit one generic error for the
implementstatement, with specific notes for the problematic declarations).