diff --git a/blueprintcompiler/ast_utils.py b/blueprintcompiler/ast_utils.py index 7bfacfb..13f6eb1 100644 --- a/blueprintcompiler/ast_utils.py +++ b/blueprintcompiler/ast_utils.py @@ -175,7 +175,9 @@ class AstNode: for child in self.children: yield from child.get_semantic_tokens() - def validate_unique_in_parent(self, error, check=None): + def validate_unique_in_parent( + self, error: str, check: T.Optional[T.Callable[["AstNode"], bool]] = None + ): for child in self.parent.children: if child is self: break diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py index 6cc0c5f..f08168f 100644 --- a/blueprintcompiler/language/gobject_signal.py +++ b/blueprintcompiler/language/gobject_signal.py @@ -24,6 +24,23 @@ from .contexts import ScopeCtx from .common import * +class SignalFlag(AstNode): + grammar = AnyOf( + UseExact("flag", "swapped"), + UseExact("flag", "after"), + ) + + @property + def flag(self) -> str: + return self.tokens["flag"] + + @validate() + def unique(self): + self.validate_unique_in_parent( + f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag + ) + + class Signal(AstNode): grammar = Statement( UseIdent("name"), @@ -39,12 +56,7 @@ class Signal(AstNode): Match("(").expected("argument list"), Optional(UseIdent("object")).expected("object identifier"), Match(")").expected(), - ZeroOrMore( - AnyOf( - [Keyword("swapped"), UseLiteral("swapped", True)], - [Keyword("after"), UseLiteral("after", True)], - ) - ), + ZeroOrMore(SignalFlag), ) @property @@ -55,6 +67,13 @@ class Signal(AstNode): def detail_name(self) -> T.Optional[str]: return self.tokens["detail_name"] + @property + def full_name(self) -> str: + if self.detail_name is None: + return self.name + else: + return self.name + "::" + self.detail_name + @property def handler(self) -> str: return self.tokens["handler"] @@ -63,13 +82,17 @@ class Signal(AstNode): def object_id(self) -> T.Optional[str]: return self.tokens["object"] + @property + def flags(self) -> T.List[SignalFlag]: + return self.children[SignalFlag] + @property def is_swapped(self) -> bool: - return self.tokens["swapped"] or False + return any(x.flag == "swapped" for x in self.flags) @property def is_after(self) -> bool: - return self.tokens["after"] or False + return any(x.flag == "after" for x in self.flags) @property def gir_signal(self): diff --git a/blueprintcompiler/language/gtk_combo_box_text.py b/blueprintcompiler/language/gtk_combo_box_text.py index 5725041..275327a 100644 --- a/blueprintcompiler/language/gtk_combo_box_text.py +++ b/blueprintcompiler/language/gtk_combo_box_text.py @@ -38,6 +38,13 @@ class Item(AstNode): def value(self) -> StringValue: return self.children[StringValue][0] + @validate("name") + def unique_in_parent(self): + if self.name is not None: + self.validate_unique_in_parent( + f"Duplicate item '{self.name}'", lambda x: x.name == self.name + ) + class ExtComboBoxItems(AstNode): grammar = [ diff --git a/blueprintcompiler/language/gtk_file_filter.py b/blueprintcompiler/language/gtk_file_filter.py index daf0679..492f19e 100644 --- a/blueprintcompiler/language/gtk_file_filter.py +++ b/blueprintcompiler/language/gtk_file_filter.py @@ -46,6 +46,13 @@ class FilterString(AstNode): def item(self) -> str: return self.tokens["name"] + @validate() + def unique_in_parent(self): + self.validate_unique_in_parent( + f"Duplicate {self.tokens['tag_name']} '{self.item}'", + check=lambda child: child.item == self.item, + ) + def create_node(tag_name: str, singular: str): return Group( diff --git a/blueprintcompiler/language/gtk_list_item_factory.py b/blueprintcompiler/language/gtk_list_item_factory.py index cf1a415..bbb3bda 100644 --- a/blueprintcompiler/language/gtk_list_item_factory.py +++ b/blueprintcompiler/language/gtk_list_item_factory.py @@ -29,6 +29,10 @@ class ExtListItemFactory(AstNode): "sub-templates", ) + @validate("template") + def unique_in_parent(self): + self.validate_unique_in_parent("Duplicate template block") + @validate() def type_is_list_item(self): if self.type_name is not None: diff --git a/blueprintcompiler/language/gtk_menu.py b/blueprintcompiler/language/gtk_menu.py index b9de906..5698e12 100644 --- a/blueprintcompiler/language/gtk_menu.py +++ b/blueprintcompiler/language/gtk_menu.py @@ -69,6 +69,12 @@ class MenuAttribute(AstNode): def value_type(self) -> ValueTypeCtx: return ValueTypeCtx(None) + @validate("name") + def unique(self): + self.validate_unique_in_parent( + f"Duplicate attribute '{self.name}'", lambda x: x.name == self.name + ) + menu_child = AnyOf() diff --git a/blueprintcompiler/language/gtk_size_group.py b/blueprintcompiler/language/gtk_size_group.py index 235d92b..5ba4325 100644 --- a/blueprintcompiler/language/gtk_size_group.py +++ b/blueprintcompiler/language/gtk_size_group.py @@ -47,6 +47,12 @@ class Widget(AstNode): f"Cannot assign {object.gir_class.full_name} to {type.full_name}" ) + @validate("name") + def unique_in_parent(self): + self.validate_unique_in_parent( + f"Object '{self.name}' is listed twice", lambda x: x.name == self.name + ) + class ExtSizeGroupWidgets(AstNode): grammar = [ diff --git a/blueprintcompiler/language/gtk_styles.py b/blueprintcompiler/language/gtk_styles.py index 798773c..6591aa5 100644 --- a/blueprintcompiler/language/gtk_styles.py +++ b/blueprintcompiler/language/gtk_styles.py @@ -29,6 +29,12 @@ class StyleClass(AstNode): def name(self) -> str: return self.tokens["name"] + @validate("name") + def unique_in_parent(self): + self.validate_unique_in_parent( + f"Duplicate style class '{self.name}'", lambda x: x.name == self.name + ) + class ExtStyles(AstNode): grammar = [ diff --git a/blueprintcompiler/language/gtkbuilder_child.py b/blueprintcompiler/language/gtkbuilder_child.py index c3e1c47..4de13f2 100644 --- a/blueprintcompiler/language/gtkbuilder_child.py +++ b/blueprintcompiler/language/gtkbuilder_child.py @@ -112,6 +112,20 @@ class Child(AstNode): else: return None + @validate() + def internal_child_unique(self): + if self.annotation is not None: + if isinstance(self.annotation.child, ChildInternal): + internal_child = self.annotation.child.internal_child + self.validate_unique_in_parent( + f"Duplicate internal child '{internal_child}'", + lambda x: ( + x.annotation + and isinstance(x.annotation.child, ChildInternal) + and x.annotation.child.internal_child == internal_child + ), + ) + @decompiler("child") def decompile_child(ctx, gir, type=None, internal_child=None): diff --git a/blueprintcompiler/language/property_binding.py b/blueprintcompiler/language/property_binding.py index 13bec61..686d1e8 100644 --- a/blueprintcompiler/language/property_binding.py +++ b/blueprintcompiler/language/property_binding.py @@ -44,6 +44,12 @@ class PropertyBindingFlag(AstNode): actions=[CodeAction("remove 'sync-create'", "")], ) + @validate() + def unique(self): + self.validate_unique_in_parent( + f"Duplicate flag '{self.flag}'", lambda x: x.flag == self.flag + ) + class PropertyBinding(AstNode): grammar = AnyOf( diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py index cf88249..d0f3be5 100644 --- a/blueprintcompiler/language/values.py +++ b/blueprintcompiler/language/values.py @@ -229,6 +229,12 @@ class Flag(AstNode): did_you_mean=(self.tokens["value"], expected_type.members.keys()), ) + @validate() + def unique(self): + self.validate_unique_in_parent( + f"Duplicate flag '{self.name}'", lambda x: x.name == self.name + ) + class Flags(AstNode): grammar = [Flag, "|", Flag, ZeroOrMore(["|", Flag])] diff --git a/tests/sample_errors/duplicate_internal_child.blp b/tests/sample_errors/duplicate_internal_child.blp new file mode 100644 index 0000000..339ed0c --- /dev/null +++ b/tests/sample_errors/duplicate_internal_child.blp @@ -0,0 +1,8 @@ +using Gtk 4.0; + +$MyWidget { + [internal-child test] + Button {} + [internal-child test] + Button {} +} \ No newline at end of file diff --git a/tests/sample_errors/duplicate_internal_child.err b/tests/sample_errors/duplicate_internal_child.err new file mode 100644 index 0000000..8bb16e7 --- /dev/null +++ b/tests/sample_errors/duplicate_internal_child.err @@ -0,0 +1 @@ +6,3,33,Duplicate internal child 'test' \ No newline at end of file