Skip to content

Interfaces errors emit better notes - #12772

Open
0x6e wants to merge 17 commits into
masterfrom
nathan/interface-notes
Open

Interfaces errors emit better notes#12772
0x6e wants to merge 17 commits into
masterfrom
nathan/interface-notes

Conversation

@0x6e

@0x6e 0x6e commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

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.

error: Cannot implement 'ValidInterface'.
       - 'value' must be a 'int' property (found a 'float' property)
   --> internal/compiler/tests/syntax/interfaces/implement_self.slint:92:15
    |
 92 |     implement ValidInterface <=> self;
    |               ^^^^^^^^^^^^^^^
note: 'ValidInterface' declares 'value' as 'in-out property <int> value;'
   --> internal/compiler/tests/syntax/interfaces/implement_self.slint:95:22
    |
 95 |     in-out property <float> value;
    |                      ^^^^^

In some cases, it is not possible to highlight the particular syntax causing the issue (e.g. missing pure keyword) so we fall back to emitting a note on the name identifier of the declarations:

error: Cannot implement 'Inverter'.
       - 'invert' must be 'pure'
   --> internal/compiler/tests/syntax/interfaces/implement_self.slint:125:15
    |
125 |     implement Inverter <=> self;
    |               ^^^^^^^^^
note: 'Inverter' declares 'invert' as 'pure callback invert(bool) -> bool;'
   --> internal/compiler/tests/syntax/interfaces/implement_self.slint:129:14
    |
129 |     callback invert(bool) -> bool;
    |              ^^^^^^

In some cases we may emit multiple notes for the same error. We aggregate errors for each implement statement into one error string, and emit notes for each part of the syntax that is incorrect:

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;
    |     ^^^^^^

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 implement statement, with specific notes for the problematic declarations).

@0x6e
0x6e requested a review from LeonMatthes August 3, 2026 16:39
0x6e and others added 11 commits August 5, 2026 08:14
… 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.

@LeonMatthes LeonMatthes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/compiler/langtype.rs Outdated
Comment thread internal/compiler/object_tree/interfaces.rs Outdated

component IncorrectType {
in-out property <float> value;
// > <note{'ValidInterface' declares 'value' as 'in-out property <int> value;'}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
// > <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 :)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 implement keyword (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.

Comment thread internal/compiler/object_tree/interfaces.rs Outdated
0x6e added 6 commits August 18, 2026 12:33
… 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.
@0x6e
0x6e force-pushed the nathan/interface-notes branch from f1c6334 to b2cf74d Compare August 19, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants