mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Improve expression type checking
This commit is contained in:
parent
b636d9ed71
commit
98ba7d467a
11 changed files with 160 additions and 55 deletions
|
@ -1,7 +1,15 @@
|
|||
from .attributes import BaseAttribute, BaseTypedAttribute
|
||||
from .binding import Binding
|
||||
from .contexts import ValueTypeCtx
|
||||
from .expression import CastExpr, ClosureExpr, Expr, ExprChain, IdentExpr, LookupOp
|
||||
from .expression import (
|
||||
CastExpr,
|
||||
ClosureArg,
|
||||
ClosureExpr,
|
||||
Expr,
|
||||
ExprChain,
|
||||
LiteralExpr,
|
||||
LookupOp,
|
||||
)
|
||||
from .gobject_object import Object, ObjectContent
|
||||
from .gobject_property import Property
|
||||
from .gobject_signal import Signal
|
||||
|
@ -50,3 +58,5 @@ OBJECT_CONTENT_HOOKS.children = [
|
|||
Strings,
|
||||
Child,
|
||||
]
|
||||
|
||||
LITERAL.children = [Literal]
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from .common import *
|
||||
from .expression import ExprChain, LookupOp, IdentExpr
|
||||
from .expression import ExprChain, LookupOp, LiteralExpr
|
||||
from .contexts import ValueTypeCtx
|
||||
|
||||
|
||||
|
@ -37,10 +37,14 @@ class Binding(AstNode):
|
|||
@property
|
||||
def simple_binding(self) -> T.Optional["SimpleBinding"]:
|
||||
if isinstance(self.expression.last, LookupOp):
|
||||
if isinstance(self.expression.last.lhs, IdentExpr):
|
||||
return SimpleBinding(
|
||||
self.expression.last.lhs.ident, self.expression.last.property_name
|
||||
)
|
||||
if isinstance(self.expression.last.lhs, LiteralExpr):
|
||||
from .values import IdentLiteral
|
||||
|
||||
if isinstance(self.expression.last.lhs.literal.value, IdentLiteral):
|
||||
return SimpleBinding(
|
||||
self.expression.last.lhs.literal.value.ident,
|
||||
self.expression.last.property_name,
|
||||
)
|
||||
return None
|
||||
|
||||
@validate("bind")
|
||||
|
|
|
@ -44,3 +44,4 @@ from ..parse_tree import *
|
|||
|
||||
|
||||
OBJECT_CONTENT_HOOKS = AnyOf()
|
||||
LITERAL = AnyOf()
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
|
||||
from .common import *
|
||||
from .contexts import ValueTypeCtx
|
||||
from .types import TypeName
|
||||
from .gtkbuilder_template import Template
|
||||
|
||||
|
@ -27,6 +28,13 @@ expr = Sequence()
|
|||
|
||||
|
||||
class Expr(AstNode):
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self) -> ValueTypeCtx:
|
||||
if rhs := self.rhs:
|
||||
return rhs.context[ValueTypeCtx]
|
||||
else:
|
||||
return self.parent.context[ValueTypeCtx]
|
||||
|
||||
@property
|
||||
def type(self) -> T.Optional[GirType]:
|
||||
raise NotImplementedError()
|
||||
|
@ -70,39 +78,45 @@ class InfixExpr(Expr):
|
|||
return children[children.index(self) - 1]
|
||||
|
||||
|
||||
class IdentExpr(Expr):
|
||||
grammar = UseIdent("ident")
|
||||
class LiteralExpr(Expr):
|
||||
grammar = LITERAL
|
||||
|
||||
@property
|
||||
def ident(self) -> str:
|
||||
return self.tokens["ident"]
|
||||
def is_object(self) -> bool:
|
||||
from .values import IdentLiteral
|
||||
|
||||
@validate()
|
||||
def exists(self):
|
||||
if self.root.objects_by_id.get(self.ident) is None:
|
||||
raise CompileError(
|
||||
f"Could not find object with ID {self.ident}",
|
||||
did_you_mean=(self.ident, self.root.objects_by_id.keys()),
|
||||
)
|
||||
return (
|
||||
isinstance(self.literal.value, IdentLiteral)
|
||||
and self.literal.value.ident in self.root.objects_by_id
|
||||
)
|
||||
|
||||
@property
|
||||
def literal(self):
|
||||
from .values import Literal
|
||||
|
||||
return self.children[Literal][0]
|
||||
|
||||
@property
|
||||
def type(self) -> T.Optional[GirType]:
|
||||
if object := self.root.objects_by_id.get(self.ident):
|
||||
return object.gir_class
|
||||
else:
|
||||
return None
|
||||
return self.literal.value.type
|
||||
|
||||
@property
|
||||
def type_complete(self) -> bool:
|
||||
if object := self.root.objects_by_id.get(self.ident):
|
||||
return not isinstance(object, Template)
|
||||
else:
|
||||
return True
|
||||
from .values import IdentLiteral
|
||||
|
||||
if isinstance(self.literal, IdentLiteral):
|
||||
if object := self.root.objects_by_id.get(self.ident):
|
||||
return not isinstance(object, Template)
|
||||
return True
|
||||
|
||||
|
||||
class LookupOp(InfixExpr):
|
||||
grammar = [".", UseIdent("property")]
|
||||
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self) -> ValueTypeCtx:
|
||||
return ValueTypeCtx(None)
|
||||
|
||||
@property
|
||||
def property_name(self) -> str:
|
||||
return self.tokens["property"]
|
||||
|
@ -119,11 +133,15 @@ class LookupOp(InfixExpr):
|
|||
|
||||
@validate("property")
|
||||
def property_exists(self):
|
||||
if (
|
||||
self.lhs.type is None
|
||||
or not self.lhs.type_complete
|
||||
or isinstance(self.lhs.type, UncheckedType)
|
||||
):
|
||||
if self.lhs.type is None:
|
||||
raise CompileError(
|
||||
f"Could not determine the type of the preceding expression",
|
||||
hints=[
|
||||
f"add a type cast so blueprint knows which type the property {self.property_name} belongs to"
|
||||
],
|
||||
)
|
||||
|
||||
if isinstance(self.lhs.type, UncheckedType):
|
||||
return
|
||||
|
||||
elif not isinstance(self.lhs.type, gir.Class) and not isinstance(
|
||||
|
@ -143,6 +161,10 @@ class LookupOp(InfixExpr):
|
|||
class CastExpr(InfixExpr):
|
||||
grammar = ["as", "(", TypeName, ")"]
|
||||
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self):
|
||||
return ValueTypeCtx(self.type)
|
||||
|
||||
@property
|
||||
def type(self) -> T.Optional[GirType]:
|
||||
return self.children[TypeName][0].gir_type
|
||||
|
@ -162,12 +184,24 @@ class CastExpr(InfixExpr):
|
|||
)
|
||||
|
||||
|
||||
class ClosureArg(AstNode):
|
||||
grammar = ExprChain
|
||||
|
||||
@property
|
||||
def expr(self) -> ExprChain:
|
||||
return self.children[ExprChain][0]
|
||||
|
||||
@context(ValueTypeCtx)
|
||||
def value_type(self) -> ValueTypeCtx:
|
||||
return ValueTypeCtx(None)
|
||||
|
||||
|
||||
class ClosureExpr(Expr):
|
||||
grammar = [
|
||||
Optional(["$", UseLiteral("extern", True)]),
|
||||
UseIdent("name"),
|
||||
"(",
|
||||
Delimited(ExprChain, ","),
|
||||
Delimited(ClosureArg, ","),
|
||||
")",
|
||||
]
|
||||
|
||||
|
@ -183,8 +217,8 @@ class ClosureExpr(Expr):
|
|||
return self.tokens["name"]
|
||||
|
||||
@property
|
||||
def args(self) -> T.List[ExprChain]:
|
||||
return self.children[ExprChain]
|
||||
def args(self) -> T.List[ClosureArg]:
|
||||
return self.children[ClosureArg]
|
||||
|
||||
@validate()
|
||||
def cast_to_return_type(self):
|
||||
|
@ -200,6 +234,6 @@ class ClosureExpr(Expr):
|
|||
|
||||
|
||||
expr.children = [
|
||||
AnyOf(ClosureExpr, IdentExpr, ["(", ExprChain, ")"]),
|
||||
AnyOf(ClosureExpr, LiteralExpr, ["(", ExprChain, ")"]),
|
||||
ZeroOrMore(AnyOf(LookupOp, CastExpr)),
|
||||
]
|
||||
|
|
|
@ -79,6 +79,10 @@ class TypeLiteral(AstNode):
|
|||
Match(")").expected(),
|
||||
]
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return gir.TypeType()
|
||||
|
||||
@property
|
||||
def type_name(self) -> TypeName:
|
||||
return self.children[TypeName][0]
|
||||
|
@ -97,6 +101,10 @@ class QuotedLiteral(AstNode):
|
|||
def value(self) -> str:
|
||||
return self.tokens["value"]
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return gir.StringType()
|
||||
|
||||
@validate()
|
||||
def validate_for_type(self) -> None:
|
||||
expected_type = self.context[ValueTypeCtx].value_type
|
||||
|
@ -171,10 +179,10 @@ class NumberLiteral(AstNode):
|
|||
elif isinstance(expected_type, gir.UIntType):
|
||||
if self.value < 0:
|
||||
raise CompileError(
|
||||
f"Cannot convert {self.group.tokens['value']} to unsigned integer"
|
||||
f"Cannot convert -{self.group.tokens['value']} to unsigned integer"
|
||||
)
|
||||
|
||||
elif expected_type is not None:
|
||||
elif not isinstance(expected_type, gir.FloatType) and expected_type is not None:
|
||||
raise CompileError(f"Cannot convert number to {expected_type.full_name}")
|
||||
|
||||
|
||||
|
@ -237,6 +245,18 @@ class IdentLiteral(AstNode):
|
|||
def ident(self) -> str:
|
||||
return self.tokens["value"]
|
||||
|
||||
@property
|
||||
def type(self) -> T.Optional[gir.GirType]:
|
||||
# If the expected type is known, then use that. Otherwise, guess.
|
||||
if expected_type := self.context[ValueTypeCtx].value_type:
|
||||
return expected_type
|
||||
elif self.ident in ["true", "false"]:
|
||||
return gir.BoolType()
|
||||
elif object := self.root.objects_by_id.get(self.ident):
|
||||
return object.gir_class
|
||||
else:
|
||||
return None
|
||||
|
||||
@validate()
|
||||
def validate_for_type(self) -> None:
|
||||
expected_type = self.context[ValueTypeCtx].value_type
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue