diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 822c7dc..f4bc92d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,7 +18,7 @@ build:
- ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests
- - git checkout c61f425d5df359261f1369722e791f947bc3ede1
+ - git checkout d14b95b6c1fc0cddd4b0ad21d224b05edee2d01f
- ./test.sh
- cd ..
coverage: '/TOTAL.*\s([.\d]+)%/'
diff --git a/NEWS.md b/NEWS.md
index bd533d6..0997afb 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -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
## Added
diff --git a/blueprintcompiler/gir.py b/blueprintcompiler/gir.py
index 3786078..b1ecf08 100644
--- a/blueprintcompiler/gir.py
+++ b/blueprintcompiler/gir.py
@@ -129,6 +129,11 @@ class StringType(BasicType):
def assignable_to(self, other) -> bool:
return isinstance(other, StringType)
+class TypeType(BasicType):
+ name = "GType"
+ def assignable_to(self, other) -> bool:
+ return isinstance(other, TypeType)
+
_BASIC_TYPES = {
"gboolean": BoolType,
"int": IntType,
@@ -141,6 +146,8 @@ _BASIC_TYPES = {
"float": FloatType,
"double": FloatType,
"utf8": StringType,
+ "gtype": TypeType,
+ "type": TypeType,
}
class GirNode:
@@ -213,7 +220,7 @@ class GirNode:
@property
def type(self):
- return self.get_containing(Namespace).lookup_type(self.type_name)
+ raise NotImplementedError()
class Property(GirNode):
@@ -464,6 +471,18 @@ class Enumeration(GirNode, GirType):
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):
def __init__(self, ns, tl: typelib.Typelib):
super().__init__(ns, tl)
@@ -491,6 +510,8 @@ class Namespace(GirNode):
self.entries[entry_name] = Class(self, entry_blob)
elif entry_type == typelib.BLOB_TYPE_INTERFACE:
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
def xml(self):
@@ -595,6 +616,8 @@ class Repository(GirNode):
return UIntType()
elif type_id == typelib.TYPE_UTF8:
return StringType()
+ elif type_id == typelib.TYPE_GTYPE:
+ return TypeType()
else:
raise CompilerBugError("Unknown type ID", type_id)
else:
diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py
index feeb301..1b27040 100644
--- a/blueprintcompiler/language/__init__.py
+++ b/blueprintcompiler/language/__init__.py
@@ -18,7 +18,7 @@ from .gtkbuilder_child import Child
from .gtkbuilder_template import Template
from .imports import GtkDirective, Import
from .ui import UI
-from .values import IdentValue, TranslatedStringValue, FlagsValue, LiteralValue
+from .values import TypeValue, IdentValue, TranslatedStringValue, FlagsValue, QuotedValue, NumberValue
from .common import *
@@ -43,8 +43,10 @@ OBJECT_CONTENT_HOOKS.children = [
]
VALUE_HOOKS.children = [
+ TypeValue,
TranslatedStringValue,
FlagsValue,
IdentValue,
- LiteralValue,
+ QuotedValue,
+ NumberValue,
]
diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py
index f39db53..44e28ba 100644
--- a/blueprintcompiler/language/values.py
+++ b/blueprintcompiler/language/values.py
@@ -19,6 +19,7 @@
from .common import *
+from .types import TypeName
class Value(AstNode):
@@ -55,11 +56,68 @@ class TranslatedStringValue(Value):
xml.put_text(self.tokens["value"])
-class LiteralValue(Value):
- grammar = AnyOf(
- UseNumber("value"),
- UseQuoted("value"),
- )
+class TypeValue(Value):
+ grammar = [
+ "typeof",
+ "(",
+ 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):
xml.put_text(self.tokens["value"])
@@ -87,23 +145,8 @@ class LiteralValue(Value):
except:
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:
- 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):
diff --git a/blueprintcompiler/typelib.py b/blueprintcompiler/typelib.py
index be1f366..e8b9152 100644
--- a/blueprintcompiler/typelib.py
+++ b/blueprintcompiler/typelib.py
@@ -26,6 +26,8 @@ import mmap, os
from .errors import CompilerBugError
+BLOB_TYPE_STRUCT = 3
+BLOB_TYPE_BOXED = 4
BLOB_TYPE_ENUM = 5
BLOB_TYPE_FLAGS = 6
BLOB_TYPE_OBJECT = 7
diff --git a/tests/sample_errors/a11y_prop_type.err b/tests/sample_errors/a11y_prop_type.err
index c8cb1d5..cc0b0db 100644
--- a/tests/sample_errors/a11y_prop_type.err
+++ b/tests/sample_errors/a11y_prop_type.err
@@ -1 +1 @@
-5,18,1,Cannot convert 1 to Gtk.Orientation
+5,18,1,Cannot convert number to Gtk.Orientation
diff --git a/tests/samples/parseable.blp b/tests/samples/parseable.blp
index 3bc3584..f4e8c2f 100644
--- a/tests/samples/parseable.blp
+++ b/tests/samples/parseable.blp
@@ -1,5 +1,14 @@
using Gtk 4.0;
+using Gio 2.0;
Gtk.Shortcut {
trigger: "Escape";
}
+
+Picture {
+ paintable: "/path/to/paintable";
+}
+
+ColorButton {
+ rgba: "rgb(0, 0, 0)";
+}
\ No newline at end of file
diff --git a/tests/samples/parseable.ui b/tests/samples/parseable.ui
index 78ceacb..231a25c 100644
--- a/tests/samples/parseable.ui
+++ b/tests/samples/parseable.ui
@@ -4,4 +4,10 @@
+
+
diff --git a/tests/samples/typeof.blp b/tests/samples/typeof.blp
new file mode 100644
index 0000000..5c5e2e5
--- /dev/null
+++ b/tests/samples/typeof.blp
@@ -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);
+}
\ No newline at end of file
diff --git a/tests/samples/typeof.ui b/tests/samples/typeof.ui
new file mode 100644
index 0000000..4c45869
--- /dev/null
+++ b/tests/samples/typeof.ui
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/tests/test_samples.py b/tests/test_samples.py
index 4fdb5c0..ba39ada 100644
--- a/tests/test_samples.py
+++ b/tests/test_samples.py
@@ -155,6 +155,7 @@ class TestSamples(unittest.TestCase):
self.assert_sample("template")
self.assert_sample("template_no_parent")
self.assert_sample("translated")
+ self.assert_sample("typeof")
self.assert_sample("uint")
self.assert_sample("unchecked_class")
self.assert_sample("using")