language: Support boxed types and GType

- Add support for type checking boxed types
- Remove support for converting string and number literals
- Add the `typeof()` operator for GType literals
This commit is contained in:
James Westman 2022-09-16 22:33:49 -05:00
parent ee2b9b2950
commit c0c40b1577
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
12 changed files with 140 additions and 26 deletions

View file

@ -18,7 +18,7 @@ build:
- ninja -C _build docs/en - ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git - git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests - cd blueprint-regression-tests
- git checkout c61f425d5df359261f1369722e791f947bc3ede1 - git checkout d14b95b6c1fc0cddd4b0ad21d224b05edee2d01f
- ./test.sh - ./test.sh
- cd .. - cd ..
coverage: '/TOTAL.*\s([.\d]+)%/' coverage: '/TOTAL.*\s([.\d]+)%/'

View file

@ -1,3 +1,10 @@
# v0.6.0 (unreleased)
## Breaking Changes
- Quoted and numeric literals are no longer interchangeable (e.g. `"800"` is
no longer an accepted value for an integer type).
- Boxed types are now type checked.
# v0.4.0 # v0.4.0
## Added ## Added

View file

@ -129,6 +129,11 @@ class StringType(BasicType):
def assignable_to(self, other) -> bool: def assignable_to(self, other) -> bool:
return isinstance(other, StringType) return isinstance(other, StringType)
class TypeType(BasicType):
name = "GType"
def assignable_to(self, other) -> bool:
return isinstance(other, TypeType)
_BASIC_TYPES = { _BASIC_TYPES = {
"gboolean": BoolType, "gboolean": BoolType,
"int": IntType, "int": IntType,
@ -141,6 +146,8 @@ _BASIC_TYPES = {
"float": FloatType, "float": FloatType,
"double": FloatType, "double": FloatType,
"utf8": StringType, "utf8": StringType,
"gtype": TypeType,
"type": TypeType,
} }
class GirNode: class GirNode:
@ -213,7 +220,7 @@ class GirNode:
@property @property
def type(self): def type(self):
return self.get_containing(Namespace).lookup_type(self.type_name) raise NotImplementedError()
class Property(GirNode): class Property(GirNode):
@ -464,6 +471,18 @@ class Enumeration(GirNode, GirType):
return type == self return type == self
class Boxed(GirNode, GirType):
def __init__(self, ns, tl: typelib.Typelib):
super().__init__(ns, tl)
@property
def signature(self):
return f"boxed {self.full_name}"
def assignable_to(self, type):
return type == self
class Bitfield(Enumeration): class Bitfield(Enumeration):
def __init__(self, ns, tl: typelib.Typelib): def __init__(self, ns, tl: typelib.Typelib):
super().__init__(ns, tl) super().__init__(ns, tl)
@ -491,6 +510,8 @@ class Namespace(GirNode):
self.entries[entry_name] = Class(self, entry_blob) self.entries[entry_name] = Class(self, entry_blob)
elif entry_type == typelib.BLOB_TYPE_INTERFACE: elif entry_type == typelib.BLOB_TYPE_INTERFACE:
self.entries[entry_name] = Interface(self, entry_blob) self.entries[entry_name] = Interface(self, entry_blob)
elif entry_type == typelib.BLOB_TYPE_BOXED or entry_type == typelib.BLOB_TYPE_STRUCT:
self.entries[entry_name] = Boxed(self, entry_blob)
@cached_property @cached_property
def xml(self): def xml(self):
@ -595,6 +616,8 @@ class Repository(GirNode):
return UIntType() return UIntType()
elif type_id == typelib.TYPE_UTF8: elif type_id == typelib.TYPE_UTF8:
return StringType() return StringType()
elif type_id == typelib.TYPE_GTYPE:
return TypeType()
else: else:
raise CompilerBugError("Unknown type ID", type_id) raise CompilerBugError("Unknown type ID", type_id)
else: else:

View file

@ -18,7 +18,7 @@ from .gtkbuilder_child import Child
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .imports import GtkDirective, Import from .imports import GtkDirective, Import
from .ui import UI from .ui import UI
from .values import IdentValue, TranslatedStringValue, FlagsValue, LiteralValue from .values import TypeValue, IdentValue, TranslatedStringValue, FlagsValue, QuotedValue, NumberValue
from .common import * from .common import *
@ -43,8 +43,10 @@ OBJECT_CONTENT_HOOKS.children = [
] ]
VALUE_HOOKS.children = [ VALUE_HOOKS.children = [
TypeValue,
TranslatedStringValue, TranslatedStringValue,
FlagsValue, FlagsValue,
IdentValue, IdentValue,
LiteralValue, QuotedValue,
NumberValue,
] ]

View file

@ -19,6 +19,7 @@
from .common import * from .common import *
from .types import TypeName
class Value(AstNode): class Value(AstNode):
@ -55,11 +56,68 @@ class TranslatedStringValue(Value):
xml.put_text(self.tokens["value"]) xml.put_text(self.tokens["value"])
class LiteralValue(Value): class TypeValue(Value):
grammar = AnyOf( grammar = [
UseNumber("value"), "typeof",
UseQuoted("value"), "(",
) to_parse_node(TypeName).expected("type name"),
Match(")").expected(),
]
@property
def type_name(self):
return self.children[TypeName][0]
def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.type_name.glib_type_name)
@validate()
def validate_for_type(self):
type = self.parent.value_type
if type is not None and not isinstance(type, gir.TypeType):
raise CompileError(f"Cannot convert GType to {type.full_name}")
class QuotedValue(Value):
grammar = UseQuoted("value")
def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.tokens["value"])
@validate()
def validate_for_type(self):
type = self.parent.value_type
if isinstance(type, gir.IntType) or isinstance(type, gir.UIntType) or isinstance(type, gir.FloatType):
raise CompileError(f"Cannot convert string to number")
elif isinstance(type, gir.StringType):
pass
elif isinstance(type, gir.Class) or isinstance(type, gir.Interface) or isinstance(type, gir.Boxed):
parseable_types = [
"Gdk.Paintable",
"Gdk.Texture",
"Gdk.Pixbuf",
"GLib.File",
"Gtk.ShortcutTrigger",
"Gtk.ShortcutAction",
"Gdk.RGBA",
"Gdk.ContentFormats",
"Gsk.Transform",
"GLib.Variant",
]
if type.full_name not in parseable_types:
hints = []
if isinstance(type, gir.TypeType):
hints.append(f"use the typeof operator: 'typeof({self.tokens('value')})'")
raise CompileError(f"Cannot convert string to {type.full_name}", hints=hints)
elif type is not None:
raise CompileError(f"Cannot convert string to {type.full_name}")
class NumberValue(Value):
grammar = UseNumber("value")
def emit_xml(self, xml: XmlEmitter): def emit_xml(self, xml: XmlEmitter):
xml.put_text(self.tokens["value"]) xml.put_text(self.tokens["value"])
@ -87,23 +145,8 @@ class LiteralValue(Value):
except: except:
raise CompileError(f"Cannot convert {self.group.tokens['value']} to float") raise CompileError(f"Cannot convert {self.group.tokens['value']} to float")
elif isinstance(type, gir.StringType):
pass
elif isinstance(type, gir.Class) or isinstance(type, gir.Interface):
parseable_types = [
"Gdk.Paintable",
"Gdk.Texture",
"Gdk.Pixbuf",
"GLib.File",
"Gtk.ShortcutTrigger",
"Gtk.ShortcutAction",
]
if type.full_name not in parseable_types:
raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")
elif type is not None: elif type is not None:
raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}") raise CompileError(f"Cannot convert number to {type.full_name}")
class Flag(AstNode): class Flag(AstNode):

View file

@ -26,6 +26,8 @@ import mmap, os
from .errors import CompilerBugError from .errors import CompilerBugError
BLOB_TYPE_STRUCT = 3
BLOB_TYPE_BOXED = 4
BLOB_TYPE_ENUM = 5 BLOB_TYPE_ENUM = 5
BLOB_TYPE_FLAGS = 6 BLOB_TYPE_FLAGS = 6
BLOB_TYPE_OBJECT = 7 BLOB_TYPE_OBJECT = 7

View file

@ -1 +1 @@
5,18,1,Cannot convert 1 to Gtk.Orientation 5,18,1,Cannot convert number to Gtk.Orientation

View file

@ -1,5 +1,14 @@
using Gtk 4.0; using Gtk 4.0;
using Gio 2.0;
Gtk.Shortcut { Gtk.Shortcut {
trigger: "Escape"; trigger: "Escape";
} }
Picture {
paintable: "/path/to/paintable";
}
ColorButton {
rgba: "rgb(0, 0, 0)";
}

View file

@ -4,4 +4,10 @@
<object class="GtkShortcut"> <object class="GtkShortcut">
<property name="trigger">Escape</property> <property name="trigger">Escape</property>
</object> </object>
<object class="GtkPicture">
<property name="paintable">/path/to/paintable</property>
</object>
<object class="GtkColorButton">
<property name="rgba">rgb(0, 0, 0)</property>
</object>
</interface> </interface>

11
tests/samples/typeof.blp Normal file
View file

@ -0,0 +1,11 @@
using Gtk 4.0;
using GObject 2.0;
using Gio 2.0;
Gio.ListStore {
item-type: typeof(GObject.Object);
}
Gio.ListStore {
item-type: typeof(.MyObject);
}

10
tests/samples/typeof.ui Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GListStore">
<property name="item-type">GObject</property>
</object>
<object class="GListStore">
<property name="item-type">MyObject</property>
</object>
</interface>

View file

@ -155,6 +155,7 @@ class TestSamples(unittest.TestCase):
self.assert_sample("template") self.assert_sample("template")
self.assert_sample("template_no_parent") self.assert_sample("template_no_parent")
self.assert_sample("translated") self.assert_sample("translated")
self.assert_sample("typeof")
self.assert_sample("uint") self.assert_sample("uint")
self.assert_sample("unchecked_class") self.assert_sample("unchecked_class")
self.assert_sample("using") self.assert_sample("using")