Skip to content

Commit b1d9c5a

Browse files
committed
compiler: Don't emit interface purity/visibility errors if the types don't match
For example, `public` or `protected` function visibility doesn't make much sense in a property context (expects `in`, `out`, `in-out`). In this case, we emit the full type that the interface expects so that the user can get it right in one iteration.
1 parent e665459 commit b1d9c5a

3 files changed

Lines changed: 32 additions & 14 deletions

File tree

internal/compiler/object_tree/interfaces.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::cell::RefCell;
77
use std::collections::BTreeMap;
88
use std::rc::Rc;
99

10-
use itertools::Itertools;
1110
use smol_str::{SmolStr, ToSmolStr};
1211

1312
use crate::diagnostics::BuildDiagnostics;
@@ -255,7 +254,7 @@ fn validate_interface_member_implementation(
255254

256255
let lookup_result = element.lookup_property(member_name);
257256
if lookup_result.property_type == Type::Invalid {
258-
return Some(missing_type_description(member_name, interface_member));
257+
return Some(missing_type_error(member_name, interface_member));
259258
}
260259

261260
let Err(conflicts) =
@@ -414,11 +413,11 @@ fn element_implements_interface(
414413
errors.is_empty()
415414
}
416415

417-
fn missing_type_description(name: &SmolStr, interface_declaration: &PropertyDeclaration) -> String {
416+
fn missing_type_description(interface_declaration: &PropertyDeclaration) -> String {
418417
let purity_description = |purity: &Option<bool>| {
419418
if purity.unwrap_or(false) { "pure " } else { "" }
420419
};
421-
let type_description = match interface_declaration.property_type {
420+
match interface_declaration.property_type {
422421
Type::Callback(..) => {
423422
format!(
424423
"a '{}{}'",
@@ -439,9 +438,11 @@ fn missing_type_description(name: &SmolStr, interface_declaration: &PropertyDecl
439438
interface_declaration.visibility, interface_declaration.property_type
440439
)
441440
}
442-
};
441+
}
442+
}
443443

444-
format!("- missing '{name}', {type_description}")
444+
fn missing_type_error(name: &SmolStr, interface_declaration: &PropertyDeclaration) -> String {
445+
format!("- missing '{name}', {}", missing_type_description(interface_declaration))
445446
}
446447

447448
/// [PartialEq] for [Function] means that the argument names must match. That is not required for a valid interface implementation.
@@ -464,7 +465,7 @@ fn property_matches_interface(
464465
child_id: Option<&SmolStr>,
465466
) -> Result<(), String> {
466467
if property.property_type == Type::Invalid {
467-
return Err(missing_type_description(name, interface_declaration));
468+
return Err(missing_type_error(name, interface_declaration));
468469
}
469470

470471
let mut errors = Vec::new();
@@ -476,6 +477,13 @@ fn property_matches_interface(
476477
&property.property_type,
477478
&interface_declaration.property_type,
478479
) {
480+
let is_same_type = match (&interface_declaration.property_type, &property.property_type) {
481+
(Type::Callback(..), Type::Callback(..)) | (Type::Function(..), Type::Function(..)) => {
482+
true
483+
}
484+
(lhs, rhs) => lhs.is_property_type() && rhs.is_property_type(),
485+
};
486+
479487
let type_description = |property_type: &Type| match property_type {
480488
Type::Callback(..) => {
481489
format!("a '{}'", property_type)
@@ -488,9 +496,19 @@ fn property_matches_interface(
488496
}
489497
};
490498

491-
let expected = type_description(&interface_declaration.property_type);
499+
let expected = if !is_same_type {
500+
missing_type_description(interface_declaration)
501+
} else {
502+
type_description(&interface_declaration.property_type)
503+
};
504+
492505
let actual = type_description(&property.property_type);
493506
errors.push(format!("- '{member_name}' must be {expected} (found {actual})"));
507+
508+
if !is_same_type {
509+
// Visibility and purity are unlikely to make sense, so return early in this case.
510+
return Err(errors.join("\n"));
511+
}
494512
}
495513

496514
if property.property_visibility != interface_declaration.visibility {
@@ -505,7 +523,7 @@ fn property_matches_interface(
505523
errors.push(format!("- '{member_name}' must be 'pure'"));
506524
}
507525

508-
if errors.is_empty() { Ok(()) } else { Err(errors.into_iter().join("\n")) }
526+
if errors.is_empty() { Ok(()) } else { Err(errors.join("\n")) }
509527
}
510528

511529
fn apply_uses_statement_function_binding(

internal/compiler/tests/syntax/interfaces/implement_child.slint

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ component CrossKindImpl {
122122

123123
export component ChildCrossKind {
124124
implement ValidInterface <=> impl;
125-
// > <error{Cannot implement 'ValidInterface' based on 'impl'.↵- 'impl.reset' must be a 'function() -> void' (found a 'int' property)↵- 'impl.reset' must be 'public' (found 'in-out')↵- 'impl.reset' must be 'pure'↵- 'impl.speak' must be a 'callback() -> void' (found a 'int' property)↵- 'impl.value' must be a 'int' property (found a 'callback() -> void')}
125+
// > <error{Cannot implement 'ValidInterface' based on 'impl'.↵- 'impl.reset' must be a 'public pure function() -> void' (found a 'int' property)↵- 'impl.speak' must be a 'callback() -> void' (found a 'int' property)↵- 'impl.value' must be an in-out 'int' property (found a 'callback() -> void')}
126126
impl := CrossKindImpl { }
127127
}
128128

internal/compiler/tests/syntax/interfaces/implement_self.slint

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,9 @@ export component CannotOverrideFunction {
209209
return a + b;
210210
}
211211
in property <string> format;
212-
// > <error{'Calculator' error.↵- 'format' must be a 'function(int) -> string' (found a 'string' property)↵- 'format' must be 'public' (found 'in')}
212+
// > <error{'Calculator' error.↵- 'format' must be a 'public function(int) -> string' (found a 'string' property)}
213213
callback reset();
214-
// > <error{'Calculator' error.↵- 'reset' must be a 'function() -> void' (found a 'callback() -> void')↵- 'reset' must be 'public' (found 'in-out')}
214+
// > <error{'Calculator' error.↵- 'reset' must be a 'public function() -> void' (found a 'callback() -> void')}
215215
}
216216

217217
// Conflicts with an inherited (non-local) member: property, callback and function variants.
@@ -246,11 +246,11 @@ export component CrossKindMismatch {
246246
implement ValidInterface <=> self;
247247

248248
callback value();
249-
// > <error{'ValidInterface' error.↵- 'value' must be a 'int' property (found a 'callback() -> void')}
249+
// > <error{'ValidInterface' error.↵- 'value' must be an in-out 'int' property (found a 'callback() -> void')}
250250
in-out property <int> speak;
251251
// > <error{'ValidInterface' error.↵- 'speak' must be a 'callback() -> void' (found a 'int' property)}
252252
in-out property <int> reset;
253-
// > <error{'ValidInterface' error.↵- 'reset' must be a 'function() -> void' (found a 'int' property)↵- 'reset' must be 'public' (found 'in-out')}
253+
// > <error{'ValidInterface' error.↵- 'reset' must be a 'public function() -> void' (found a 'int' property)}
254254
}
255255

256256
// A single member that violates several constraints at once, joined into one message.

0 commit comments

Comments
 (0)