Improve expression type checking

This commit is contained in:
James Westman 2023-03-12 15:35:05 -05:00
parent b636d9ed71
commit 98ba7d467a
11 changed files with 160 additions and 55 deletions

View file

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

View file

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

View file

@ -44,3 +44,4 @@ from ..parse_tree import *
OBJECT_CONTENT_HOOKS = AnyOf()
LITERAL = AnyOf()

View file

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

View file

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