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 @@
+
+
+
+
+
+
+
+ MyTemplate
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ MyTemplate
+
+
+
+
+
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