Repository: slint-ui/slint (language: rust)

Issue:
Unexpected warning about a component being neither used nor exported
### Bug Description

I get a warning in my slint code that I do not expect:

```
Component is neither used nor exported [Ln 1, Col 1]
```

### Reproducible Code (if applicable)

```slint
component Foo /* Foo 1 */ { }

export component FooBaz {
    Foo /* <- TEST_ME_1 */ { }
}

component Foo /* Foo 2 */ { }

export component Baz {
    Foo /* <- TEST_ME_2 */ { }
}
```


### Environment Details

- Slint Version: master
- Platform/OS: Linux
- Programming Language: Not applicable
- Backend/Renderer: Not applicable


### Product Impact

Tiny :-)


Discussion / hints:
I guess it should be an error that `Foo` is already defined?
Funny thing is that if we do NOT use Foo 1 (by commenting the line with TEST_ME_1), then we don't get that warning.

Fix patch:
diff --git a/internal/compiler/object_tree.rs b/internal/compiler/object_tree.rs
index feae872f3cf..87be05f9493 100644
--- a/internal/compiler/object_tree.rs
+++ b/internal/compiler/object_tree.rs
@@ -86,7 +86,9 @@ impl Document {
              diag: &mut BuildDiagnostics,
              local_registry: &mut TypeRegister| {
                 let compo = Component::from_node(n, diag, local_registry);
-                local_registry.add(compo.clone());
+                if !local_registry.add(compo.clone()) {
+                    diag.push_warning(format!("Component '{}' is replacing a previously defined component with the same name", compo.id), &syntax_nodes::Component::from(compo.node.clone().unwrap()).DeclaredIdentifier());
+                }
                 inner_components.push(compo);
             };
         let process_struct = |n: syntax_nodes::StructDeclaration,
@@ -102,7 +104,14 @@ impl Document {
                 parser::identifier_text(&n.DeclaredIdentifier()),
             );
             assert!(matches!(ty, Type::Struct(_)));
-            local_registry.insert_type(ty.clone());
+            if !local_registry.insert_type(ty.clone()) {
+                diag.push_warning(
+                    format!(
+                        "Struct '{ty}' is replacing a previously defined type with the same name"
+                    ),
+                    &n.DeclaredIdentifier(),
+                );
+            }
             inner_types.push(ty);
         };
         let process_enum = |n: syntax_nodes::EnumDeclaration,
@@ -132,9 +141,17 @@ impl Document {
                     }
                 })
                 .collect();
-            let en = Enumeration { name: name.clone(), values, default_value: 0, node: Some(n) };
+            let en =
+                Enumeration { name: name.clone(), values, default_value: 0, node: Some(n.clone()) };
             let ty = Type::Enumeration(Rc::new(en));
-            local_registry.insert_type_with_name(ty.clone(), name);
+            if !local_registry.insert_type_with_name(ty.clone(), name.clone()) {
+                diag.push_warning(
+                    format!(
+                        "Enum '{name}' is replacing a previously defined type with the same name"
+                    ),
+                    &n.DeclaredIdentifier(),
+                );
+            }
             inner_types.push(ty);
         };
 
@@ -226,9 +243,7 @@ impl Document {
             if local_compo.is_global() {
                 continue;
             }
-            // First ref count is in the type registry, the second one in inner_components. Any use of the element
-            // would have resulted in another strong reference.
-            if Rc::strong_count(local_compo) == 2 {
+            if !local_compo.used.get() {
                 diag.push_warning(
                     "Component is neither used nor exported".into(),
                     &local_compo.node,
@@ -377,6 +392,9 @@ pub struct Component {
     /// if it is a global singleton and exported.
     pub exported_global_names: RefCell<Vec<ExportedName>>,
 
+    /// True if this component is used as a sub-component by at least one other component.
+    pub used: Cell<bool>,
+
     /// The list of properties (name and type) declared as private in the component.
     /// This is used to issue better error in the generated code if the property is used.
     pub private_properties: RefCell<Vec<(SmolStr, Type)>>,
@@ -962,6 +980,9 @@ impl Element {
         };
         // This isn't truly qualified yet, the enclosing component is added at the end of Component::from_node
         let qualified_id = (!id.is_empty()).then(|| id.clone());
+        if let ElementType::Component(c) = &base_type {
+            c.used.set(true);
+        }
         let type_name = base_type
             .type_name()
             .filter(|_| base_type != tr.empty_type())
diff --git a/internal/compiler/passes/inlining.rs b/internal/compiler/passes/inlining.rs
index 052f72ec0bc..0d0a57b8e61 100644
--- a/internal/compiler/passes/inlining.rs
+++ b/internal/compiler/passes/inlining.rs
@@ -403,6 +403,7 @@ fn duplicate_sub_component(
         popup_windows: Default::default(),
         timers: component_to_duplicate.timers.clone(),
         exported_global_names: component_to_duplicate.exported_global_names.clone(),
+        used: component_to_duplicate.used.clone(),
         private_properties: Default::default(),
         inherits_popup_window: core::cell::Cell::new(false),
     };
diff --git a/internal/compiler/typeloader.rs b/internal/compiler/typeloader.rs
index 54a64d5f28f..ab39547f587 100644
--- a/internal/compiler/typeloader.rs
+++ b/internal/compiler/typeloader.rs
@@ -350,6 +350,7 @@ impl Snapshotter {
                 exported_global_names: RefCell::new(
                     component.exported_global_names.borrow().clone(),
                 ),
+                used: component.used.clone(),
                 init_code: RefCell::new(component.init_code.borrow().clone()),
                 inherits_popup_window: std::cell::Cell::new(component.inherits_popup_window.get()),
                 optimized_elements,
@@ -1455,7 +1456,7 @@ impl TypeLoader {
                 itertools::Either::Right(ty) => registry_to_populate
                     .borrow_mut()
                     .insert_type_with_name(ty, import_name.internal_name),
-            }
+            };
         }
     }
 
diff --git a/internal/compiler/typeregister.rs b/internal/compiler/typeregister.rs
index 72d5428ffff..27b37388f2c 100644
--- a/internal/compiler/typeregister.rs
+++ b/internal/compiler/typeregister.rs
@@ -356,12 +356,17 @@ impl TypeRegister {
         }
     }
 
-    /// FIXME: same as 'add' ?
-    pub fn insert_type(&mut self, t: Type) {
-        self.types.insert(t.to_smolstr(), t);
+    /// Insert a type into the type register with its builtin type name.
+    ///
+    /// Returns false if a it replaced an existing type.
+    pub fn insert_type(&mut self, t: Type) -> bool {
+        self.types.insert(t.to_smolstr(), t).is_none()
     }
-    pub fn insert_type_with_name(&mut self, t: Type, name: SmolStr) {
-        self.types.insert(name, t);
+    /// Insert a type into the type register with a specified name.
+    ///
+    /// Returns false if a it replaced an existing type.
+    pub fn insert_type_with_name(&mut self, t: Type, name: SmolStr) -> bool {
+        self.types.insert(name, t).is_none()
     }
 
     fn builtin_internal() -> Self {
@@ -642,12 +647,18 @@ impl TypeRegister {
         self.lookup(qualified[0].as_ref())
     }
 
-    pub fn add(&mut self, comp: Rc<Component>) {
-        self.add_with_name(comp.id.clone(), comp);
+    /// Add the component with it's defined name
+    ///
+    /// Returns false if there was already an element with the same name
+    pub fn add(&mut self, comp: Rc<Component>) -> bool {
+        self.add_with_name(comp.id.clone(), comp)
     }
 
-    pub fn add_with_name(&mut self, name: SmolStr, comp: Rc<Component>) {
-        self.elements.insert(name, ElementType::Component(comp));
+    /// Add the component with a specified name
+    ///
+    /// Returns false if there was already an element with the same name
+    pub fn add_with_name(&mut self, name: SmolStr, comp: Rc<Component>) -> bool {
+        self.elements.insert(name, ElementType::Component(comp)).is_none()
     }
 
     pub fn add_builtin(&mut self, builtin: Rc<BuiltinElement>) {


Test patch:
diff --git a/internal/compiler/tests/syntax/lookup/name_reuse.slint b/internal/compiler/tests/syntax/lookup/name_reuse.slint
new file mode 100644
index 00000000000..d52da4b0a6d
--- /dev/null
+++ b/internal/compiler/tests/syntax/lookup/name_reuse.slint
@@ -0,0 +1,47 @@
+// Copyright © SixtyFPS GmbH <info@slint.dev>
+// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
+
+component Foo /* Foo 1 */ { }
+
+export component FooBaz {
+    Foo /* <- TEST_ME_1 */ { }
+}
+
+component Foo /* Foo 2 */ { }
+//        ^warning{Component 'Foo' is replacing a previously defined component with the same name}
+
+export component Baz {
+    Foo /* <- TEST_ME_2 */ { }
+}
+
+
+struct Type1 { xxx: int }
+enum Type2 { AAA, BBB }
+
+  component Test1 {}
+//^warning{Component is neither used nor exported}
+
+export component Test1 {
+//               ^warning{Component 'Test1' is replacing a previously defined component with the same name}
+    property <Type1> t1: { xxx: 42 };
+    property <Type2> t2: Type2.AAA;
+//                       ^error{Cannot access id 'Type2'} // because in the lookup phase it was already erased
+
+    init => {
+        debug(Type1.CCC);         // This is allowed because the resolving phase has full view on the document
+        debug(Type2.AAA);
+//            ^error{Cannot access id 'Type2'}
+    }
+}
+
+struct Type2 { yyy: int }
+//     ^warning{Struct 'Type2' is replacing a previously defined type with the same name}
+enum Type1 { CCC, DDD }
+//   ^warning{Enum 'Type1' is replacing a previously defined type with the same name}
+export component Test2 {
+    property <Type1> t1: Type1.DDD;
+    property <Type2> t2: { yyy: 42 };
+    property <Type1> error: Type2.AAA;
+    //                      ^error{Cannot access id 'Type2'}
+}
+