mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-05 16:09:07 -04:00
WIP: Add gir.PartialClass
This commit is contained in:
parent
632e9d7df6
commit
6fdb12fd5d
17 changed files with 231 additions and 147 deletions
|
@ -126,9 +126,8 @@ class LookupOp(InfixExpr):
|
|||
@property
|
||||
def gir_type(self):
|
||||
if parent_type := self.lhs.gir_type:
|
||||
if isinstance(parent_type, gir.Class) or isinstance(parent_type, gir.Interface):
|
||||
if prop := parent_type.properties.get(self.tokens["property"]):
|
||||
return prop.type
|
||||
if prop := parent_type.lookup_property(self.tokens["property"]):
|
||||
return prop.type
|
||||
|
||||
@property
|
||||
def glib_type_name(self):
|
||||
|
@ -137,15 +136,11 @@ class LookupOp(InfixExpr):
|
|||
|
||||
@validate("property")
|
||||
def property_exists(self):
|
||||
if parent_type := self.lhs.gir_type:
|
||||
if not (isinstance(parent_type, gir.Class) or isinstance(parent_type, gir.Interface)):
|
||||
raise CompileError(f"Type {parent_type.full_name} does not have properties")
|
||||
elif self.tokens["property"] not in parent_type.properties:
|
||||
raise CompileError(
|
||||
f"{parent_type.full_name} does not have a property called {self.tokens['property']}",
|
||||
hints=["Do you need to cast the previous expression?"],
|
||||
did_you_mean=(self.tokens['property'], parent_type.properties.keys()),
|
||||
)
|
||||
try:
|
||||
self.gir_type
|
||||
except CompileError as e:
|
||||
e.hints.append("Do you need to cast the previous expression?")
|
||||
raise e
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
if isinstance(self.lhs, IdentExpr) and self.lhs.is_this:
|
||||
|
|
|
@ -48,14 +48,7 @@ class Object(AstNode):
|
|||
|
||||
@property
|
||||
def gir_class(self):
|
||||
class_names = self.children[ClassName]
|
||||
if len(class_names) > 0:
|
||||
if isinstance(class_names[0].gir_type, Class):
|
||||
return class_names[0].gir_type
|
||||
|
||||
@property
|
||||
def glib_type_name(self) -> str:
|
||||
return self.children[ClassName][0].glib_type_name
|
||||
return self.children[ClassName][0].gir_type
|
||||
|
||||
@docs("namespace")
|
||||
def namespace_docs(self):
|
||||
|
@ -83,7 +76,7 @@ class Object(AstNode):
|
|||
|
||||
def emit_start_tag(self, xml: XmlEmitter):
|
||||
xml.start_tag("object", **{
|
||||
"class": self.glib_type_name,
|
||||
"class": self.gir_class,
|
||||
"id": self.tokens["id"],
|
||||
})
|
||||
|
||||
|
|
|
@ -64,37 +64,16 @@ class Property(AstNode):
|
|||
def gir_class(self):
|
||||
return self.parent.parent.gir_class
|
||||
|
||||
|
||||
@property
|
||||
@validate("name")
|
||||
def gir_property(self):
|
||||
if self.gir_class is not None:
|
||||
return self.gir_class.properties.get(self.tokens["name"])
|
||||
|
||||
return self.gir_class.lookup_property(self.tokens["name"])
|
||||
|
||||
@property
|
||||
def value_type(self):
|
||||
if self.gir_property is not None:
|
||||
return self.gir_property.type
|
||||
|
||||
|
||||
@validate("name")
|
||||
def property_exists(self):
|
||||
if self.gir_class is None:
|
||||
# Objects that we have no gir data on should not be validated
|
||||
# This happens for classes defined by the app itself
|
||||
return
|
||||
|
||||
if isinstance(self.parent.parent, Template):
|
||||
# If the property is part of a template, it might be defined by
|
||||
# the application and thus not in gir
|
||||
return
|
||||
|
||||
if self.gir_property is None:
|
||||
raise CompileError(
|
||||
f"Class {self.gir_class.full_name} does not contain a property called {self.tokens['name']}",
|
||||
did_you_mean=(self.tokens["name"], self.gir_class.properties.keys())
|
||||
)
|
||||
|
||||
@validate("bind")
|
||||
def property_bindable(self):
|
||||
if self.tokens["bind"] and self.gir_property is not None and self.gir_property.construct_only:
|
||||
|
|
|
@ -27,17 +27,12 @@ class Filters(AstNode):
|
|||
def container_is_file_filter(self):
|
||||
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
|
||||
|
||||
@validate()
|
||||
def unique_in_parent(self):
|
||||
# The token argument to validate() needs to be calculated based on
|
||||
# the instance, hence wrapping it like this.
|
||||
@validate(self.tokens["tag_name"])
|
||||
def wrapped_validator(self):
|
||||
self.validate_unique_in_parent(
|
||||
f"Duplicate {self.tokens['tag_name']} block",
|
||||
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
||||
)
|
||||
wrapped_validator(self)
|
||||
@validate("tag")
|
||||
def wrapped_validator(self):
|
||||
self.validate_unique_in_parent(
|
||||
f"Duplicate {self.tokens['tag_name']} block",
|
||||
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
||||
)
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag(self.tokens["tag_name"])
|
||||
|
@ -57,7 +52,7 @@ def create_node(tag_name: str, singular: str):
|
|||
return Group(
|
||||
Filters,
|
||||
[
|
||||
Keyword(tag_name),
|
||||
Keyword(tag_name, "tag"),
|
||||
UseLiteral("tag_name", tag_name),
|
||||
"[",
|
||||
Delimited(
|
||||
|
|
|
@ -49,6 +49,10 @@ class Child(AstNode):
|
|||
if gir_class.assignable_to(parent_type):
|
||||
break
|
||||
else:
|
||||
if gir_class.is_partial:
|
||||
# we don't know if the class implements Gtk.Buildable or not
|
||||
return
|
||||
|
||||
hints=["only Gio.ListStore or Gtk.Buildable implementors can have children"]
|
||||
if "child" in gir_class.properties:
|
||||
hints.append("did you mean to assign this object to the 'child' property?")
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from .gobject_object import Object, ObjectContent
|
||||
from .common import *
|
||||
|
@ -34,11 +35,14 @@ class Template(Object):
|
|||
ObjectContent,
|
||||
]
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def gir_class(self):
|
||||
# Templates might not have a parent class defined
|
||||
parent = None
|
||||
if len(self.children[ClassName]):
|
||||
return self.children[ClassName][0].gir_type
|
||||
parent = self.children[ClassName][0].gir_type
|
||||
|
||||
return gir.PartialClass(self.tokens["id"], parent)
|
||||
|
||||
@validate("id")
|
||||
def unique_in_parent(self):
|
||||
|
|
|
@ -25,7 +25,7 @@ from .common import *
|
|||
class GtkDirective(AstNode):
|
||||
grammar = Statement(
|
||||
Match("using").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
||||
Match("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
||||
Keyword("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
||||
UseNumberText("version").expected("a version number for GTK"),
|
||||
)
|
||||
|
||||
|
@ -33,15 +33,23 @@ class GtkDirective(AstNode):
|
|||
def gtk_version(self):
|
||||
version = self.tokens["version"]
|
||||
if version not in ["4.0"]:
|
||||
err = CompileError("Only GTK 4 is supported")
|
||||
err = CompileError("Only GTK 4 is supported", fatal=True)
|
||||
if version and version.startswith("4"):
|
||||
err.hint("Expected the GIR version, not an exact version number. Use 'using Gtk 4.0;'.")
|
||||
else:
|
||||
err.hint("Expected 'using Gtk 4.0;'")
|
||||
raise err
|
||||
|
||||
return version
|
||||
|
||||
@validate()
|
||||
def gir_namespace(self):
|
||||
if self.gtk_version is None:
|
||||
# use that error message instead
|
||||
return
|
||||
|
||||
try:
|
||||
gir.get_namespace("Gtk", version)
|
||||
return gir.get_namespace("Gtk", self.gtk_version)
|
||||
except CompileError as e:
|
||||
raise CompileError(
|
||||
"Could not find GTK 4 introspection files. Is gobject-introspection installed?",
|
||||
|
@ -52,18 +60,9 @@ class GtkDirective(AstNode):
|
|||
)
|
||||
|
||||
|
||||
@property
|
||||
def gir_namespace(self):
|
||||
# validate the GTK version first to make sure the more specific error
|
||||
# message is emitted
|
||||
self.gtk_version()
|
||||
return gir.get_namespace("Gtk", self.tokens["version"])
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.put_self_closing("requires", lib="gtk", version=self.tokens["version"])
|
||||
|
||||
|
||||
class Import(AstNode):
|
||||
grammar = Statement(
|
||||
"using",
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
|
||||
|
||||
import typing as T
|
||||
from functools import cached_property
|
||||
from .common import *
|
||||
from ..gir import Class, Interface
|
||||
from ..gir import GirClass, Class, Interface
|
||||
|
||||
|
||||
class TypeName(AstNode):
|
||||
|
@ -53,18 +54,16 @@ class TypeName(AstNode):
|
|||
if not self.tokens["ignore_gir"]:
|
||||
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def gir_type(self) -> T.Optional[gir.Class]:
|
||||
if self.tokens["class_name"] and not self.tokens["ignore_gir"]:
|
||||
if self.tokens["ignore_gir"]:
|
||||
return gir.PartialClass(self.tokens["class_name"])
|
||||
else:
|
||||
return self.root.gir.get_type(self.tokens["class_name"], self.tokens["namespace"])
|
||||
return None
|
||||
|
||||
@property
|
||||
def glib_type_name(self) -> str:
|
||||
if gir_type := self.gir_type:
|
||||
return gir_type.glib_type_name
|
||||
else:
|
||||
return self.tokens["class_name"]
|
||||
return self.gir_type.glib_type_name
|
||||
|
||||
@docs("namespace")
|
||||
def namespace_docs(self):
|
||||
|
@ -83,7 +82,7 @@ class TypeName(AstNode):
|
|||
class ClassName(TypeName):
|
||||
@validate("namespace", "class_name")
|
||||
def gir_class_exists(self):
|
||||
if self.gir_type is not None and not isinstance(self.gir_type, Class):
|
||||
if self.gir_type is not None and not isinstance(self.gir_type, GirClass):
|
||||
if isinstance(self.gir_type, Interface):
|
||||
raise CompileError(f"{self.gir_type.full_name} is an interface, not a class")
|
||||
else:
|
||||
|
|
|
@ -42,7 +42,8 @@ class UI(AstNode, Scope):
|
|||
self._gir_errors = []
|
||||
|
||||
try:
|
||||
gir_ctx.add_namespace(self.children[GtkDirective][0].gir_namespace)
|
||||
if ns := self.children[GtkDirective][0].gir_namespace:
|
||||
gir_ctx.add_namespace(ns)
|
||||
except CompileError as e:
|
||||
self._gir_errors.append(e)
|
||||
|
||||
|
@ -70,7 +71,7 @@ class UI(AstNode, Scope):
|
|||
xml.end_tag()
|
||||
|
||||
return {
|
||||
id: ScopeVariable(id, obj.gir_class, lambda xml, id=id: emit_xml(xml, id), obj.glib_type_name)
|
||||
id: ScopeVariable(id, obj.gir_class, lambda xml, id=id: emit_xml(xml, id))
|
||||
for id, obj in self.objects_by_id.items()
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue