Validate object types

This commit is contained in:
James Westman 2021-11-05 00:44:33 -05:00
parent a0ba59af77
commit d89f2356b4
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
11 changed files with 126 additions and 33 deletions

View file

@ -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):

View file

@ -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()

View file

@ -0,0 +1,6 @@
using Gtk 4.0;
Box box {}
Label {
label: box;
}

View file

@ -0,0 +1 @@
5,10,3,Cannot assign Gtk.Box to string

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Scale {
adjustment: Label {};
}

View file

@ -0,0 +1 @@
4,3,21,Cannot assign Gtk.Label to Gtk.Adjustment

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label {
label: my_label;
}

View file

@ -0,0 +1 @@
4,10,8,Could not find object with ID my_label

View file

@ -0,0 +1,7 @@
using Gtk 4.0;
Scale {
adjustment: adj;
}
Adjustment adj {}

8
tests/samples/id_prop.ui Normal file
View 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>

View file

@ -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")