WIP: Add gir.PartialClass

This commit is contained in:
James Westman 2022-07-19 20:25:46 -05:00
parent 632e9d7df6
commit 6fdb12fd5d
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
17 changed files with 231 additions and 147 deletions

View file

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

View file

@ -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"],
})

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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