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
|
||||
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()
|
||||
|
@ -137,12 +137,12 @@ class Import(AstNode):
|
|||
|
||||
class Template(AstNode):
|
||||
@validate("namespace", "class_name")
|
||||
def gir_parent_exists(self):
|
||||
def gir_class_exists(self):
|
||||
if not self.tokens["ignore_gir"]:
|
||||
self.root.gir.validate_class(self.tokens["class_name"], self.tokens["namespace"])
|
||||
|
||||
@property
|
||||
def gir_parent(self):
|
||||
def gir_class(self):
|
||||
return self.root.gir.get_class(self.tokens["class_name"], self.tokens["namespace"])
|
||||
|
||||
|
||||
|
@ -152,14 +152,14 @@ class Template(AstNode):
|
|||
|
||||
@docs("class_name")
|
||||
def class_docs(self):
|
||||
if self.gir_parent:
|
||||
return self.gir_parent.doc
|
||||
if self.gir_class:
|
||||
return self.gir_class.doc
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag("template", **{
|
||||
"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:
|
||||
child.emit_xml(xml)
|
||||
|
@ -210,12 +210,7 @@ class Child(AstNode):
|
|||
class ObjectContent(AstNode):
|
||||
@property
|
||||
def gir_class(self):
|
||||
if isinstance(self.parent, Template):
|
||||
return self.parent.gir_parent
|
||||
elif isinstance(self.parent, Object):
|
||||
return self.parent.gir_class
|
||||
else:
|
||||
raise CompilerBugError()
|
||||
return self.parent.gir_class
|
||||
|
||||
# @validate()
|
||||
# def only_one_style_class(self):
|
||||
|
@ -233,13 +228,7 @@ class ObjectContent(AstNode):
|
|||
class Property(AstNode):
|
||||
@property
|
||||
def gir_class(self):
|
||||
parent = self.parent.parent
|
||||
if isinstance(parent, Template):
|
||||
return parent.gir_parent
|
||||
elif isinstance(parent, Object):
|
||||
return parent.gir_class
|
||||
else:
|
||||
raise CompilerBugError()
|
||||
return self.parent.parent.gir_class
|
||||
|
||||
|
||||
@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")
|
||||
def property_docs(self):
|
||||
if self.gir_property is not None:
|
||||
|
@ -323,13 +325,7 @@ class Signal(AstNode):
|
|||
|
||||
@property
|
||||
def gir_class(self):
|
||||
parent = self.parent.parent
|
||||
if isinstance(parent, Template):
|
||||
return parent.gir_parent
|
||||
elif isinstance(parent, Object):
|
||||
return parent.gir_class
|
||||
else:
|
||||
raise CompilerBugError()
|
||||
return self.parent.parent.gir_class
|
||||
|
||||
|
||||
@validate("name")
|
||||
|
@ -397,12 +393,14 @@ class IdentValue(Value):
|
|||
@validate()
|
||||
def validate_for_type(self):
|
||||
type = self.parent.value_type
|
||||
|
||||
if isinstance(type, gir.Enumeration):
|
||||
if self.tokens["value"] not in type.members:
|
||||
raise CompileError(
|
||||
f"{self.tokens['value']} is not a member of {type.full_name}",
|
||||
did_you_mean=type.members.keys(),
|
||||
)
|
||||
|
||||
elif isinstance(type, gir.BoolType):
|
||||
# would have been parsed as a LiteralValue if it was correct
|
||||
raise CompileError(
|
||||
|
@ -410,6 +408,18 @@ class IdentValue(Value):
|
|||
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()
|
||||
def docs(self):
|
||||
|
|
|
@ -55,23 +55,47 @@ def get_namespace(namespace, version):
|
|||
return _namespace_cache[filename]
|
||||
|
||||
|
||||
class BasicType:
|
||||
class GirType:
|
||||
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):
|
||||
pass
|
||||
name = "bool"
|
||||
def assignable_to(self, other) -> bool:
|
||||
return isinstance(other, BoolType)
|
||||
|
||||
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):
|
||||
pass
|
||||
name = "uint"
|
||||
def assignable_to(self, other) -> bool:
|
||||
return isinstance(other, IntType) or isinstance(other, UIntType) or isinstance(other, FloatType)
|
||||
|
||||
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 = {
|
||||
"gboolean": BoolType,
|
||||
"int": IntType,
|
||||
"gint": IntType,
|
||||
"gint64": IntType,
|
||||
"guint": UIntType,
|
||||
|
@ -80,6 +104,7 @@ _BASIC_TYPES = {
|
|||
"gdouble": FloatType,
|
||||
"float": FloatType,
|
||||
"double": FloatType,
|
||||
"utf8": StringType,
|
||||
}
|
||||
|
||||
class GirNode:
|
||||
|
@ -172,14 +197,23 @@ class Signal(GirNode):
|
|||
return f"signal {self.container.name}.{self.name} ({args})"
|
||||
|
||||
|
||||
class Interface(GirNode):
|
||||
class Interface(GirNode, GirType):
|
||||
def __init__(self, ns, xml: xml_reader.Element):
|
||||
super().__init__(ns, xml)
|
||||
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.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):
|
||||
super().__init__(ns, xml)
|
||||
self._parent = xml["parent"]
|
||||
|
@ -211,6 +245,17 @@ class Class(GirNode):
|
|||
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):
|
||||
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("child_type")
|
||||
self.assert_sample("flags")
|
||||
self.assert_sample("id_prop")
|
||||
self.assert_sample("layout")
|
||||
self.assert_sample("menu")
|
||||
self.assert_sample("object_prop")
|
||||
|
@ -105,12 +106,15 @@ class TestSamples(unittest.TestCase):
|
|||
|
||||
|
||||
def test_sample_errors(self):
|
||||
self.assert_sample_error("class_assign")
|
||||
self.assert_sample_error("class_dne")
|
||||
self.assert_sample_error("duplicate_obj_id")
|
||||
self.assert_sample_error("enum_member_dne")
|
||||
self.assert_sample_error("invalid_bool")
|
||||
self.assert_sample_error("ns_not_imported")
|
||||
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("signal_dne")
|
||||
self.assert_sample_error("two_templates")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue