Add ScopeCtx instead of root.objects_by_id

This allows us to introduce new scopes, such as in
GtkBuilderListItemFactory templates.
This commit is contained in:
James Westman 2023-04-28 20:16:27 -05:00
parent ff5fff7f4b
commit ec844b10ca
8 changed files with 65 additions and 40 deletions

View file

@ -33,7 +33,7 @@ class Children:
def __init__(self, children): def __init__(self, children):
self._children = children self._children = children
def __iter__(self): def __iter__(self) -> T.Iterator["AstNode"]:
return iter(self._children) return iter(self._children)
@T.overload @T.overload
@ -175,11 +175,6 @@ class AstNode:
for child in self.children: for child in self.children:
yield from child.get_semantic_tokens() yield from child.get_semantic_tokens()
def iterate_children_recursive(self) -> T.Iterator["AstNode"]:
yield self
for child in self.children:
yield from child.iterate_children_recursive()
def validate_unique_in_parent(self, error, check=None): def validate_unique_in_parent(self, error, check=None):
for child in self.parent.children: for child in self.parent.children:
if child is self: if child is self:
@ -270,7 +265,12 @@ class Context:
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
return self return self
return self.func(instance) if ctx := getattr(instance, "_context_" + self.type.__name__, None):
return ctx
else:
ctx = self.func(instance)
setattr(instance, "_context_" + self.type.__name__, ctx)
return ctx
def context(type: T.Type[TCtx]): def context(type: T.Type[TCtx]):

View file

@ -19,9 +19,44 @@
import typing as T import typing as T
from dataclasses import dataclass from dataclasses import dataclass
from functools import cached_property
from .common import * from .common import *
from .gobject_object import Object
@dataclass @dataclass
class ValueTypeCtx: class ValueTypeCtx:
value_type: T.Optional[GirType] value_type: T.Optional[GirType]
@dataclass
class ScopeCtx:
node: AstNode
@cached_property
def objects(self) -> T.Dict[str, Object]:
return {
obj.tokens["id"]: obj
for obj in self._iter_recursive(self.node)
if obj.tokens["id"] is not None
}
def validate_unique_ids(self) -> None:
passed = {}
for obj in self._iter_recursive(self.node):
if obj.tokens["id"] is None:
continue
if obj.tokens["id"] in passed:
token = obj.group.tokens["id"]
raise CompileError(
f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end
)
passed[obj.tokens["id"]] = obj
def _iter_recursive(self, node: AstNode):
yield node
for child in node.children:
if child.context[ScopeCtx] is self:
yield from self._iter_recursive(child)

View file

@ -19,7 +19,7 @@
from .common import * from .common import *
from .contexts import ValueTypeCtx from .contexts import ScopeCtx, ValueTypeCtx
from .types import TypeName from .types import TypeName
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
@ -87,7 +87,7 @@ class LiteralExpr(Expr):
return ( return (
isinstance(self.literal.value, IdentLiteral) isinstance(self.literal.value, IdentLiteral)
and self.literal.value.ident in self.root.objects_by_id and self.literal.value.ident in self.context[ScopeCtx].objects
) )
@property @property
@ -105,7 +105,7 @@ class LiteralExpr(Expr):
from .values import IdentLiteral from .values import IdentLiteral
if isinstance(self.literal.value, IdentLiteral): if isinstance(self.literal.value, IdentLiteral):
if object := self.root.objects_by_id.get(self.literal.value.ident): if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
return not isinstance(object, Template) return not isinstance(object, Template)
return True return True

View file

@ -20,6 +20,7 @@
import typing as T import typing as T
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .contexts import ScopeCtx
from .common import * from .common import *
@ -112,7 +113,7 @@ class Signal(AstNode):
if object_id is None: if object_id is None:
return return
if self.root.objects_by_id.get(object_id) is None: if self.context[ScopeCtx].objects.get(object_id) is None:
raise CompileError(f"Could not find object with ID '{object_id}'") raise CompileError(f"Could not find object with ID '{object_id}'")
@docs("name") @docs("name")

View file

@ -20,6 +20,7 @@
from .gobject_object import ObjectContent, validate_parent_type from .gobject_object import ObjectContent, validate_parent_type
from .common import * from .common import *
from .contexts import ScopeCtx
class Widget(AstNode): class Widget(AstNode):
@ -27,12 +28,15 @@ class Widget(AstNode):
@validate("name") @validate("name")
def obj_widget(self): def obj_widget(self):
object = self.root.objects_by_id.get(self.tokens["name"]) object = self.context[ScopeCtx].objects.get(self.tokens["name"])
type = self.root.gir.get_type("Widget", "Gtk") type = self.root.gir.get_type("Widget", "Gtk")
if object is None: if object is None:
raise CompileError( raise CompileError(
f"Could not find object with ID {self.tokens['name']}", f"Could not find object with ID {self.tokens['name']}",
did_you_mean=(self.tokens["name"], self.root.objects_by_id.keys()), did_you_mean=(
self.tokens["name"],
self.context[ScopeCtx].objects.keys(),
),
) )
elif object.gir_class and not object.gir_class.assignable_to(type): elif object.gir_class and not object.gir_class.assignable_to(type):
raise CompileError( raise CompileError(

View file

@ -18,7 +18,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
from .common import * from .common import *
from .contexts import ValueTypeCtx from .contexts import ScopeCtx
from .gobject_object import Object from .gobject_object import Object
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
@ -71,7 +71,7 @@ class PropertyBinding(AstNode):
@property @property
def source_obj(self) -> T.Optional[Object]: def source_obj(self) -> T.Optional[Object]:
return self.root.objects_by_id.get(self.source) return self.context[ScopeCtx].objects.get(self.source)
@property @property
def property_name(self) -> str: def property_name(self) -> str:
@ -98,7 +98,7 @@ class PropertyBinding(AstNode):
if self.source_obj is None: if self.source_obj is None:
raise CompileError( raise CompileError(
f"Could not find object with ID {self.source}", f"Could not find object with ID {self.source}",
did_you_mean=(self.source, self.root.objects_by_id.keys()), did_you_mean=(self.source, self.context[ScopeCtx].objects.keys()),
) )
@validate("property") @validate("property")

View file

@ -17,14 +17,13 @@
# #
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
from functools import cached_property
from .. import gir from .. import gir
from .imports import GtkDirective, Import from .imports import GtkDirective, Import
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .gobject_object import Object from .gobject_object import Object
from .gtk_menu import menu, Menu from .gtk_menu import menu, Menu
from .common import * from .common import *
from .contexts import ScopeCtx
class UI(AstNode): class UI(AstNode):
@ -85,13 +84,9 @@ class UI(AstNode):
or isinstance(child, Menu) or isinstance(child, Menu)
] ]
@cached_property @context(ScopeCtx)
def objects_by_id(self): def scope_ctx(self) -> ScopeCtx:
return { return ScopeCtx(node=self)
obj.tokens["id"]: obj
for obj in self.iterate_children_recursive()
if obj.tokens["id"] is not None
}
@validate() @validate()
def gir_errors(self): def gir_errors(self):
@ -102,14 +97,4 @@ class UI(AstNode):
@validate() @validate()
def unique_ids(self): def unique_ids(self):
passed = {} self.context[ScopeCtx].validate_unique_ids()
for obj in self.iterate_children_recursive():
if obj.tokens["id"] is None:
continue
if obj.tokens["id"] in passed:
token = obj.group.tokens["id"]
raise CompileError(
f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end
)
passed[obj.tokens["id"]] = obj

View file

@ -24,7 +24,7 @@ from .types import TypeName
from .property_binding import PropertyBinding from .property_binding import PropertyBinding
from .binding import Binding from .binding import Binding
from .gobject_object import Object from .gobject_object import Object
from .contexts import ValueTypeCtx from .contexts import ScopeCtx, ValueTypeCtx
class TranslatedWithoutContext(AstNode): class TranslatedWithoutContext(AstNode):
@ -274,7 +274,7 @@ class IdentLiteral(AstNode):
return expected_type return expected_type
elif self.ident in ["true", "false"]: elif self.ident in ["true", "false"]:
return gir.BoolType() return gir.BoolType()
elif object := self.root.objects_by_id.get(self.ident): elif object := self.context[ScopeCtx].objects.get(self.ident):
return object.gir_class return object.gir_class
else: else:
return None return None
@ -294,11 +294,11 @@ class IdentLiteral(AstNode):
) )
elif expected_type is not None: elif expected_type is not None:
object = self.root.objects_by_id.get(self.ident) object = self.context[ScopeCtx].objects.get(self.ident)
if object is None: if object is None:
raise CompileError( raise CompileError(
f"Could not find object with ID {self.ident}", f"Could not find object with ID {self.ident}",
did_you_mean=(self.ident, self.root.objects_by_id.keys()), did_you_mean=(self.ident, self.context[ScopeCtx].objects.keys()),
) )
elif object.gir_class and not object.gir_class.assignable_to(expected_type): elif object.gir_class and not object.gir_class.assignable_to(expected_type):
raise CompileError( raise CompileError(