From 88f5b4f1c7282b29836861ebecfb945e32e71792 Mon Sep 17 00:00:00 2001 From: James Westman Date: Tue, 28 Mar 2023 10:41:42 -0500 Subject: [PATCH] Fix template types --- blueprintcompiler/completions.py | 4 +- blueprintcompiler/gir.py | 64 ++++++++++++++++++- blueprintcompiler/language/common.py | 2 +- blueprintcompiler/language/expression.py | 2 +- .../language/gobject_property.py | 4 +- blueprintcompiler/language/gobject_signal.py | 4 +- .../language/gtkbuilder_template.py | 7 +- .../language/property_binding.py | 6 +- blueprintcompiler/language/types.py | 6 +- tests/samples/template_binding.blp | 5 ++ tests/samples/template_binding.ui | 13 ++++ tests/samples/template_binding_extern.blp | 5 ++ tests/samples/template_binding_extern.ui | 13 ++++ tests/test_samples.py | 6 ++ 14 files changed, 120 insertions(+), 21 deletions(-) create mode 100644 tests/samples/template_binding.blp create mode 100644 tests/samples/template_binding.ui create mode 100644 tests/samples/template_binding_extern.blp create mode 100644 tests/samples/template_binding_extern.ui diff --git a/blueprintcompiler/completions.py b/blueprintcompiler/completions.py index 9940055..6a944dd 100644 --- a/blueprintcompiler/completions.py +++ b/blueprintcompiler/completions.py @@ -120,7 +120,7 @@ def gtk_object_completer(ast_node, match_variables): matches=new_statement_patterns, ) def property_completer(ast_node, match_variables): - if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.UncheckedType): + if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType): for prop in ast_node.gir_class.properties: yield Completion(prop, CompletionItemKind.Property, snippet=f"{prop}: $0;") @@ -144,7 +144,7 @@ def prop_value_completer(ast_node, match_variables): matches=new_statement_patterns, ) def signal_completer(ast_node, match_variables): - if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.UncheckedType): + if ast_node.gir_class and not isinstance(ast_node.gir_class, gir.ExternType): for signal in ast_node.gir_class.signals: if not isinstance(ast_node.parent, language.Object): name = "on" diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py index a8831af..fc415fd 100644 --- a/blueprintcompiler/gir.py +++ b/blueprintcompiler/gir.py @@ -114,8 +114,12 @@ class GirType: """The name of the type in the GObject type system, suitable to pass to `g_type_from_name()`.""" raise NotImplementedError() + @property + def incomplete(self) -> bool: + return False -class UncheckedType(GirType): + +class ExternType(GirType): def __init__(self, name: str) -> None: super().__init__() self._name = name @@ -131,6 +135,10 @@ class UncheckedType(GirType): def glib_type_name(self) -> str: return self._name + @property + def incomplete(self) -> bool: + return True + class ArrayType(GirType): def __init__(self, inner: GirType) -> None: @@ -507,6 +515,60 @@ class Class(GirNode, GirType): yield from impl.signals.values() +class TemplateType(GirType): + def __init__(self, name: str, parent: T.Optional[Class]): + self._name = name + self.parent = parent + + @property + def name(self) -> str: + return self._name + + @property + def full_name(self) -> str: + return self._name + + @property + def glib_type_name(self) -> str: + return self._name + + @cached_property + def properties(self) -> T.Mapping[str, Property]: + if self.parent is None or isinstance(self.parent, ExternType): + return {} + else: + return self.parent.properties + + @cached_property + def signals(self) -> T.Mapping[str, Signal]: + if self.parent is None or isinstance(self.parent, ExternType): + return {} + else: + return self.parent.signals + + def assignable_to(self, other: "GirType") -> bool: + if self == other: + return True + elif isinstance(other, Interface): + # we don't know the template type's interfaces, assume yes + return True + elif self.parent is None or isinstance(self.parent, ExternType): + return isinstance(other, Class) + else: + return self.parent.assignable_to(other) + + @cached_property + def signature(self) -> str: + if self.parent is None: + return f"template {self.name}" + else: + return f"template {self.name} : {self.parent.full_name}" + + @property + def incomplete(self) -> bool: + return True + + class EnumMember(GirNode): def __init__(self, enum: "Enumeration", tl: typelib.Typelib) -> None: super().__init__(enum, tl) diff --git a/blueprintcompiler/language/common.py b/blueprintcompiler/language/common.py index 734e59b..082aaa4 100644 --- a/blueprintcompiler/language/common.py +++ b/blueprintcompiler/language/common.py @@ -37,7 +37,7 @@ from ..gir import ( FloatType, GirType, Enumeration, - UncheckedType, + ExternType, ) from ..lsp_utils import Completion, CompletionItemKind, SemanticToken, SemanticTokenType from ..parse_tree import * diff --git a/blueprintcompiler/language/expression.py b/blueprintcompiler/language/expression.py index fca7f0e..69323f4 100644 --- a/blueprintcompiler/language/expression.py +++ b/blueprintcompiler/language/expression.py @@ -141,7 +141,7 @@ class LookupOp(InfixExpr): ], ) - if isinstance(self.lhs.type, UncheckedType) or not self.lhs.type_complete: + if self.lhs.type.incomplete: return elif not isinstance(self.lhs.type, gir.Class) and not isinstance( diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py index b57f3b0..18b91ae 100644 --- a/blueprintcompiler/language/gobject_property.py +++ b/blueprintcompiler/language/gobject_property.py @@ -46,7 +46,7 @@ class Property(AstNode): @property def gir_property(self): - if self.gir_class is not None and not isinstance(self.gir_class, UncheckedType): + if self.gir_class is not None and not isinstance(self.gir_class, ExternType): return self.gir_class.properties.get(self.tokens["name"]) @context(ValueTypeCtx) @@ -75,7 +75,7 @@ class Property(AstNode): @validate("name") def property_exists(self): - if self.gir_class is None or isinstance(self.gir_class, UncheckedType): + if self.gir_class is None or self.gir_class.incomplete: # Objects that we have no gir data on should not be validated # This happens for classes defined by the app itself return diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py index 0c649b7..25d789b 100644 --- a/blueprintcompiler/language/gobject_signal.py +++ b/blueprintcompiler/language/gobject_signal.py @@ -72,7 +72,7 @@ class Signal(AstNode): @property def gir_signal(self): - if self.gir_class is not None and not isinstance(self.gir_class, UncheckedType): + if self.gir_class is not None and not isinstance(self.gir_class, ExternType): return self.gir_class.signals.get(self.tokens["name"]) @property @@ -90,7 +90,7 @@ class Signal(AstNode): @validate("name") def signal_exists(self): - if self.gir_class is None or isinstance(self.gir_class, UncheckedType): + if self.gir_class is None or self.gir_class.incomplete: # Objects that we have no gir data on should not be validated # This happens for classes defined by the app itself return diff --git a/blueprintcompiler/language/gtkbuilder_template.py b/blueprintcompiler/language/gtkbuilder_template.py index 40ac7f7..46ff6e6 100644 --- a/blueprintcompiler/language/gtkbuilder_template.py +++ b/blueprintcompiler/language/gtkbuilder_template.py @@ -50,11 +50,10 @@ class Template(Object): @property def gir_class(self): - # Templates might not have a parent class defined - if class_name := self.class_name: - return class_name.gir_type + if self.class_name is None: + return gir.TemplateType(self.id, None) else: - return gir.UncheckedType(self.id) + return gir.TemplateType(self.id, self.class_name.gir_type) @validate("id") def unique_in_parent(self): diff --git a/blueprintcompiler/language/property_binding.py b/blueprintcompiler/language/property_binding.py index 37a5c91..5314934 100644 --- a/blueprintcompiler/language/property_binding.py +++ b/blueprintcompiler/language/property_binding.py @@ -108,11 +108,7 @@ class PropertyBinding(AstNode): gir_class = self.source_obj.gir_class - if ( - isinstance(self.source_obj, Template) - or gir_class is None - or isinstance(gir_class, UncheckedType) - ): + if gir_class is None or gir_class.incomplete: # Objects that we have no gir data on should not be validated # This happens for classes defined by the app itself return diff --git a/blueprintcompiler/language/types.py b/blueprintcompiler/language/types.py index 702ed32..510dbc7 100644 --- a/blueprintcompiler/language/types.py +++ b/blueprintcompiler/language/types.py @@ -20,7 +20,7 @@ import typing as T from .common import * -from ..gir import Class, Interface +from ..gir import Class, ExternType, Interface class TypeName(AstNode): @@ -70,7 +70,7 @@ class TypeName(AstNode): self.tokens["class_name"], self.tokens["namespace"] ) - return gir.UncheckedType(self.tokens["class_name"]) + return gir.ExternType(self.tokens["class_name"]) @property def glib_type_name(self) -> str: @@ -95,7 +95,7 @@ class ClassName(TypeName): def gir_class_exists(self): if ( self.gir_type is not None - and not isinstance(self.gir_type, UncheckedType) + and not isinstance(self.gir_type, ExternType) and not isinstance(self.gir_type, Class) ): if isinstance(self.gir_type, Interface): diff --git a/tests/samples/template_binding.blp b/tests/samples/template_binding.blp new file mode 100644 index 0000000..fa4d53e --- /dev/null +++ b/tests/samples/template_binding.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +template MyTemplate : Box { + prop1: bind MyTemplate.prop2 as ($MyObject).prop3; +} \ No newline at end of file diff --git a/tests/samples/template_binding.ui b/tests/samples/template_binding.ui new file mode 100644 index 0000000..7c8b49d --- /dev/null +++ b/tests/samples/template_binding.ui @@ -0,0 +1,13 @@ + + + + + diff --git a/tests/samples/template_binding_extern.blp b/tests/samples/template_binding_extern.blp new file mode 100644 index 0000000..a8a42c3 --- /dev/null +++ b/tests/samples/template_binding_extern.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +template MyTemplate : $MyParentClass { + prop1: bind MyTemplate.prop2 as ($MyObject).prop3; +} \ No newline at end of file diff --git a/tests/samples/template_binding_extern.ui b/tests/samples/template_binding_extern.ui new file mode 100644 index 0000000..2bbc88f --- /dev/null +++ b/tests/samples/template_binding_extern.ui @@ -0,0 +1,13 @@ + + + + + diff --git a/tests/test_samples.py b/tests/test_samples.py index 823488b..8ee9015 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -187,6 +187,12 @@ class TestSamples(unittest.TestCase): self.assert_sample( "template", skip_run=True ) # The template class doesn't exist + self.assert_sample( + "template_binding", skip_run=True + ) # The template class doesn't exist + self.assert_sample( + "template_binding_extern", skip_run=True + ) # The template class doesn't exist self.assert_sample( "template_no_parent", skip_run=True ) # The template class doesn't exist