mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Validate object types
This commit is contained in:
parent
a0ba59af77
commit
d89f2356b4
11 changed files with 126 additions and 33 deletions
|
@ -57,7 +57,7 @@ class UI(AstNode):
|
||||||
|
|
||||||
@lazy_prop
|
@lazy_prop
|
||||||
def objects_by_id(self):
|
def objects_by_id(self):
|
||||||
return { obj.id: obj for obj in self.iterate_children_recursive() if hasattr(obj, "id") }
|
return { obj.tokens["id"]: obj for obj in self.iterate_children_recursive() if obj.tokens["id"] is not None }
|
||||||
|
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
|
@ -137,12 +137,12 @@ class Import(AstNode):
|
||||||
|
|
||||||
class Template(AstNode):
|
class Template(AstNode):
|
||||||
@validate("namespace", "class_name")
|
@validate("namespace", "class_name")
|
||||||
def gir_parent_exists(self):
|
def gir_class_exists(self):
|
||||||
if not self.tokens["ignore_gir"]:
|
if not self.tokens["ignore_gir"]:
|
||||||
self.root.gir.validate_class(self.tokens["class_name"], self.tokens["namespace"])
|
self.root.gir.validate_class(self.tokens["class_name"], self.tokens["namespace"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_parent(self):
|
def gir_class(self):
|
||||||
return self.root.gir.get_class(self.tokens["class_name"], self.tokens["namespace"])
|
return self.root.gir.get_class(self.tokens["class_name"], self.tokens["namespace"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,14 +152,14 @@ class Template(AstNode):
|
||||||
|
|
||||||
@docs("class_name")
|
@docs("class_name")
|
||||||
def class_docs(self):
|
def class_docs(self):
|
||||||
if self.gir_parent:
|
if self.gir_class:
|
||||||
return self.gir_parent.doc
|
return self.gir_class.doc
|
||||||
|
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.start_tag("template", **{
|
xml.start_tag("template", **{
|
||||||
"class": self.tokens["name"],
|
"class": self.tokens["name"],
|
||||||
"parent": self.gir_parent.glib_type_name if self.gir_parent else self.tokens["class_name"],
|
"parent": self.gir_class.glib_type_name if self.gir_class else self.tokens["class_name"],
|
||||||
})
|
})
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
child.emit_xml(xml)
|
child.emit_xml(xml)
|
||||||
|
@ -210,12 +210,7 @@ class Child(AstNode):
|
||||||
class ObjectContent(AstNode):
|
class ObjectContent(AstNode):
|
||||||
@property
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
if isinstance(self.parent, Template):
|
return self.parent.gir_class
|
||||||
return self.parent.gir_parent
|
|
||||||
elif isinstance(self.parent, Object):
|
|
||||||
return self.parent.gir_class
|
|
||||||
else:
|
|
||||||
raise CompilerBugError()
|
|
||||||
|
|
||||||
# @validate()
|
# @validate()
|
||||||
# def only_one_style_class(self):
|
# def only_one_style_class(self):
|
||||||
|
@ -233,13 +228,7 @@ class ObjectContent(AstNode):
|
||||||
class Property(AstNode):
|
class Property(AstNode):
|
||||||
@property
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
parent = self.parent.parent
|
return self.parent.parent.gir_class
|
||||||
if isinstance(parent, Template):
|
|
||||||
return parent.gir_parent
|
|
||||||
elif isinstance(parent, Object):
|
|
||||||
return parent.gir_class
|
|
||||||
else:
|
|
||||||
raise CompilerBugError()
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -273,6 +262,19 @@ class Property(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def obj_property_type(self):
|
||||||
|
if len(self.children[Object]) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
object = self.children[Object][0]
|
||||||
|
type = self.value_type
|
||||||
|
if object and type and object.gir_class and not object.gir_class.assignable_to(type):
|
||||||
|
raise CompileError(
|
||||||
|
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@docs("name")
|
@docs("name")
|
||||||
def property_docs(self):
|
def property_docs(self):
|
||||||
if self.gir_property is not None:
|
if self.gir_property is not None:
|
||||||
|
@ -323,13 +325,7 @@ class Signal(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
parent = self.parent.parent
|
return self.parent.parent.gir_class
|
||||||
if isinstance(parent, Template):
|
|
||||||
return parent.gir_parent
|
|
||||||
elif isinstance(parent, Object):
|
|
||||||
return parent.gir_class
|
|
||||||
else:
|
|
||||||
raise CompilerBugError()
|
|
||||||
|
|
||||||
|
|
||||||
@validate("name")
|
@validate("name")
|
||||||
|
@ -397,12 +393,14 @@ class IdentValue(Value):
|
||||||
@validate()
|
@validate()
|
||||||
def validate_for_type(self):
|
def validate_for_type(self):
|
||||||
type = self.parent.value_type
|
type = self.parent.value_type
|
||||||
|
|
||||||
if isinstance(type, gir.Enumeration):
|
if isinstance(type, gir.Enumeration):
|
||||||
if self.tokens["value"] not in type.members:
|
if self.tokens["value"] not in type.members:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"{self.tokens['value']} is not a member of {type.full_name}",
|
f"{self.tokens['value']} is not a member of {type.full_name}",
|
||||||
did_you_mean=type.members.keys(),
|
did_you_mean=type.members.keys(),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(type, gir.BoolType):
|
elif isinstance(type, gir.BoolType):
|
||||||
# would have been parsed as a LiteralValue if it was correct
|
# would have been parsed as a LiteralValue if it was correct
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
|
@ -410,6 +408,18 @@ class IdentValue(Value):
|
||||||
did_you_mean=["true", "false"],
|
did_you_mean=["true", "false"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
object = self.root.objects_by_id.get(self.tokens["value"])
|
||||||
|
if object is None:
|
||||||
|
raise CompileError(
|
||||||
|
f"Could not find object with ID {self.tokens['value']}",
|
||||||
|
did_you_mean=(self.tokens['value'], self.root.objects_by_id.keys()),
|
||||||
|
)
|
||||||
|
elif object.gir_class and not object.gir_class.assignable_to(type):
|
||||||
|
raise CompileError(
|
||||||
|
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@docs()
|
@docs()
|
||||||
def docs(self):
|
def docs(self):
|
||||||
|
|
|
@ -55,23 +55,47 @@ def get_namespace(namespace, version):
|
||||||
return _namespace_cache[filename]
|
return _namespace_cache[filename]
|
||||||
|
|
||||||
|
|
||||||
class BasicType:
|
class GirType:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class BasicType:
|
||||||
|
name: str = "unknown type"
|
||||||
|
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
class BoolType(BasicType):
|
class BoolType(BasicType):
|
||||||
pass
|
name = "bool"
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
return isinstance(other, BoolType)
|
||||||
|
|
||||||
class IntType(BasicType):
|
class IntType(BasicType):
|
||||||
pass
|
name = "int"
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
return isinstance(other, IntType) or isinstance(other, UIntType) or isinstance(other, FloatType)
|
||||||
|
|
||||||
class UIntType(BasicType):
|
class UIntType(BasicType):
|
||||||
pass
|
name = "uint"
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
return isinstance(other, IntType) or isinstance(other, UIntType) or isinstance(other, FloatType)
|
||||||
|
|
||||||
class FloatType(BasicType):
|
class FloatType(BasicType):
|
||||||
pass
|
name = "float"
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
return isinstance(other, FloatType)
|
||||||
|
|
||||||
|
class StringType(BasicType):
|
||||||
|
name = "string"
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
return isinstance(other, StringType)
|
||||||
|
|
||||||
_BASIC_TYPES = {
|
_BASIC_TYPES = {
|
||||||
"gboolean": BoolType,
|
"gboolean": BoolType,
|
||||||
|
"int": IntType,
|
||||||
"gint": IntType,
|
"gint": IntType,
|
||||||
"gint64": IntType,
|
"gint64": IntType,
|
||||||
"guint": UIntType,
|
"guint": UIntType,
|
||||||
|
@ -80,6 +104,7 @@ _BASIC_TYPES = {
|
||||||
"gdouble": FloatType,
|
"gdouble": FloatType,
|
||||||
"float": FloatType,
|
"float": FloatType,
|
||||||
"double": FloatType,
|
"double": FloatType,
|
||||||
|
"utf8": StringType,
|
||||||
}
|
}
|
||||||
|
|
||||||
class GirNode:
|
class GirNode:
|
||||||
|
@ -172,14 +197,23 @@ class Signal(GirNode):
|
||||||
return f"signal {self.container.name}.{self.name} ({args})"
|
return f"signal {self.container.name}.{self.name} ({args})"
|
||||||
|
|
||||||
|
|
||||||
class Interface(GirNode):
|
class Interface(GirNode, GirType):
|
||||||
def __init__(self, ns, xml: xml_reader.Element):
|
def __init__(self, ns, xml: xml_reader.Element):
|
||||||
super().__init__(ns, xml)
|
super().__init__(ns, xml)
|
||||||
self.properties = {child["name"]: Property(self, child) for child in xml.get_elements("property")}
|
self.properties = {child["name"]: Property(self, child) for child in xml.get_elements("property")}
|
||||||
self.signals = {child["name"]: Signal(self, child) for child in xml.get_elements("glib:signal")}
|
self.signals = {child["name"]: Signal(self, child) for child in xml.get_elements("glib:signal")}
|
||||||
|
self.prerequisites = [child["name"] for child in xml.get_elements("prerequisite")]
|
||||||
|
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
if self == other:
|
||||||
|
return True
|
||||||
|
for pre in self.prerequisites:
|
||||||
|
if self.get_containing(Namespace).lookup_type(pre).assignable_to(other):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Class(GirNode):
|
class Class(GirNode, GirType):
|
||||||
def __init__(self, ns, xml: xml_reader.Element):
|
def __init__(self, ns, xml: xml_reader.Element):
|
||||||
super().__init__(ns, xml)
|
super().__init__(ns, xml)
|
||||||
self._parent = xml["parent"]
|
self._parent = xml["parent"]
|
||||||
|
@ -211,6 +245,17 @@ class Class(GirNode):
|
||||||
return self.get_containing(Namespace).lookup_type(self._parent)
|
return self.get_containing(Namespace).lookup_type(self._parent)
|
||||||
|
|
||||||
|
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
if self == other:
|
||||||
|
return True
|
||||||
|
elif self.parent and self.parent.assignable_to(other):
|
||||||
|
return True
|
||||||
|
elif other in self.implements:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _enum_properties(self):
|
def _enum_properties(self):
|
||||||
yield from self.own_properties.values()
|
yield from self.own_properties.values()
|
||||||
|
|
||||||
|
|
6
tests/sample_errors/class_assign.blp
Normal file
6
tests/sample_errors/class_assign.blp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Box box {}
|
||||||
|
Label {
|
||||||
|
label: box;
|
||||||
|
}
|
1
tests/sample_errors/class_assign.err
Normal file
1
tests/sample_errors/class_assign.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
5,10,3,Cannot assign Gtk.Box to string
|
5
tests/sample_errors/obj_prop_type.blp
Normal file
5
tests/sample_errors/obj_prop_type.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Scale {
|
||||||
|
adjustment: Label {};
|
||||||
|
}
|
1
tests/sample_errors/obj_prop_type.err
Normal file
1
tests/sample_errors/obj_prop_type.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,3,21,Cannot assign Gtk.Label to Gtk.Adjustment
|
5
tests/sample_errors/object_dne.blp
Normal file
5
tests/sample_errors/object_dne.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: my_label;
|
||||||
|
}
|
1
tests/sample_errors/object_dne.err
Normal file
1
tests/sample_errors/object_dne.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4,10,8,Could not find object with ID my_label
|
7
tests/samples/id_prop.blp
Normal file
7
tests/samples/id_prop.blp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Scale {
|
||||||
|
adjustment: adj;
|
||||||
|
}
|
||||||
|
|
||||||
|
Adjustment adj {}
|
8
tests/samples/id_prop.ui
Normal file
8
tests/samples/id_prop.ui
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<object class="GtkScale">
|
||||||
|
<property name="adjustment">adj</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="adj"></object>
|
||||||
|
</interface>
|
|
@ -92,6 +92,7 @@ class TestSamples(unittest.TestCase):
|
||||||
self.assert_sample("binding")
|
self.assert_sample("binding")
|
||||||
self.assert_sample("child_type")
|
self.assert_sample("child_type")
|
||||||
self.assert_sample("flags")
|
self.assert_sample("flags")
|
||||||
|
self.assert_sample("id_prop")
|
||||||
self.assert_sample("layout")
|
self.assert_sample("layout")
|
||||||
self.assert_sample("menu")
|
self.assert_sample("menu")
|
||||||
self.assert_sample("object_prop")
|
self.assert_sample("object_prop")
|
||||||
|
@ -105,12 +106,15 @@ class TestSamples(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def test_sample_errors(self):
|
def test_sample_errors(self):
|
||||||
|
self.assert_sample_error("class_assign")
|
||||||
self.assert_sample_error("class_dne")
|
self.assert_sample_error("class_dne")
|
||||||
self.assert_sample_error("duplicate_obj_id")
|
self.assert_sample_error("duplicate_obj_id")
|
||||||
self.assert_sample_error("enum_member_dne")
|
self.assert_sample_error("enum_member_dne")
|
||||||
self.assert_sample_error("invalid_bool")
|
self.assert_sample_error("invalid_bool")
|
||||||
self.assert_sample_error("ns_not_imported")
|
self.assert_sample_error("ns_not_imported")
|
||||||
self.assert_sample_error("not_a_class")
|
self.assert_sample_error("not_a_class")
|
||||||
|
self.assert_sample_error("object_dne")
|
||||||
|
self.assert_sample_error("obj_prop_type")
|
||||||
self.assert_sample_error("property_dne")
|
self.assert_sample_error("property_dne")
|
||||||
self.assert_sample_error("signal_dne")
|
self.assert_sample_error("signal_dne")
|
||||||
self.assert_sample_error("two_templates")
|
self.assert_sample_error("two_templates")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue