Merge pull request 'Avoid raising exceptions, report diagnostics instead' (#42) from fix/remove-raises into main
All checks were successful
Tests / tests (push) Successful in 6s
All checks were successful
Tests / tests (push) Successful in 6s
Reviewed-on: #42
This commit was merged in pull request #42.
This commit is contained in:
@@ -86,6 +86,9 @@ class ColumnGroupByMethodRegistry(MethodRegistry[Call]):
|
|||||||
|
|
||||||
# TODO: maybe better to filter arguments and pass some, in case the
|
# TODO: maybe better to filter arguments and pass some, in case the
|
||||||
# return type depends on them
|
# return type depends on them
|
||||||
|
|
||||||
|
# Don't catch UndefinedMethodException because all aggregation
|
||||||
|
# methods should be defined on columns too
|
||||||
returns: Type = self.typer.call_method(
|
returns: Type = self.typer.call_method(
|
||||||
location=call.location,
|
location=call.location,
|
||||||
call_expr=call.call_expr,
|
call_expr=call.call_expr,
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class FrameGroupByMethodRegistry(MethodRegistry[Call]):
|
|||||||
for column in call.groupby.frame.columns:
|
for column in call.groupby.frame.columns:
|
||||||
with self.reporter.with_context(f"in column '{column.name}'"):
|
with self.reporter.with_context(f"in column '{column.name}'"):
|
||||||
column_groupby: ColumnGroupBy = ColumnGroupBy(column=column.type)
|
column_groupby: ColumnGroupBy = ColumnGroupBy(column=column.type)
|
||||||
|
# Don't catch UndefinedMethodException because all aggregation
|
||||||
|
# methods should be defined on columns too
|
||||||
result_type: Type = self.typer.call_method(
|
result_type: Type = self.typer.call_method(
|
||||||
location=call.location,
|
location=call.location,
|
||||||
call_expr=call.call_expr,
|
call_expr=call.call_expr,
|
||||||
|
|||||||
@@ -170,7 +170,10 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
|
|||||||
type = GenericType(name=name, params=params, body=type)
|
type = GenericType(name=name, params=params, body=type)
|
||||||
else:
|
else:
|
||||||
type = DerivedType(name=name, type=type)
|
type = DerivedType(name=name, type=type)
|
||||||
self.types.define_type(name, type)
|
try:
|
||||||
|
self.types.define_type(name, type)
|
||||||
|
except ValueError:
|
||||||
|
self.reporter.error(stmt.location, f"Type {name} already defined")
|
||||||
self._local_variables.clear()
|
self._local_variables.clear()
|
||||||
self._current_name = None
|
self._current_name = None
|
||||||
|
|
||||||
@@ -178,7 +181,10 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
|
|||||||
name: str = stmt.name.lexeme
|
name: str = stmt.name.lexeme
|
||||||
self._current_name = name
|
self._current_name = name
|
||||||
type: Type = stmt.type.accept(self)
|
type: Type = stmt.type.accept(self)
|
||||||
self.types.define_type(name, type)
|
try:
|
||||||
|
self.types.define_type(name, type)
|
||||||
|
except ValueError:
|
||||||
|
self.reporter.error(stmt.location, f"Type {name} already defined")
|
||||||
self._current_name = None
|
self._current_name = None
|
||||||
|
|
||||||
def visit_member_stmt(self, stmt: m.MemberStmt) -> None: ...
|
def visit_member_stmt(self, stmt: m.MemberStmt) -> None: ...
|
||||||
@@ -201,6 +207,7 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
|
|||||||
)
|
)
|
||||||
|
|
||||||
def visit_predicate_stmt(self, stmt: m.PredicateStmt) -> None:
|
def visit_predicate_stmt(self, stmt: m.PredicateStmt) -> None:
|
||||||
|
name: str = stmt.name.lexeme
|
||||||
for spec in stmt.params:
|
for spec in stmt.params:
|
||||||
for param in spec.mixed:
|
for param in spec.mixed:
|
||||||
assert param.name is not None
|
assert param.name is not None
|
||||||
@@ -222,14 +229,17 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
|
|||||||
returns=type,
|
returns=type,
|
||||||
)
|
)
|
||||||
self._predicate_params = {}
|
self._predicate_params = {}
|
||||||
self.types.define_predicate(
|
try:
|
||||||
stmt.name.lexeme,
|
self.types.define_predicate(
|
||||||
Predicate(
|
name,
|
||||||
type=type,
|
Predicate(
|
||||||
body=stmt.body,
|
type=type,
|
||||||
alias=len(params) == 0,
|
body=stmt.body,
|
||||||
),
|
alias=len(params) == 0,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
self.reporter.error(stmt.location, f"Predicate {name} already defined")
|
||||||
|
|
||||||
def _is_valid_predicate(self, body: Type) -> bool:
|
def _is_valid_predicate(self, body: Type) -> bool:
|
||||||
"""Check whether the given type is valid as a predicate's body
|
"""Check whether the given type is valid as a predicate's body
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ TypedExpr = tuple[p.Expr, Type]
|
|||||||
|
|
||||||
|
|
||||||
class ReturnException(Exception):
|
class ReturnException(Exception):
|
||||||
pass
|
def __init__(self, stmt: p.Stmt):
|
||||||
|
super().__init__()
|
||||||
|
self.stmt: p.Stmt = stmt
|
||||||
|
|
||||||
|
|
||||||
class UndefinedMethodException(Exception):
|
class UndefinedMethodException(Exception):
|
||||||
@@ -116,7 +118,10 @@ class PythonTyper(
|
|||||||
self.judgements = []
|
self.judgements = []
|
||||||
self.evaluated_casts = []
|
self.evaluated_casts = []
|
||||||
|
|
||||||
self.check(stmts)
|
try:
|
||||||
|
self.check(stmts)
|
||||||
|
except ReturnException as e:
|
||||||
|
self.reporter.error(e.stmt.location, "Return statement outside of function")
|
||||||
|
|
||||||
return TypedAST(
|
return TypedAST(
|
||||||
stmts=stmts,
|
stmts=stmts,
|
||||||
@@ -576,7 +581,7 @@ class PythonTyper(
|
|||||||
def visit_return_stmt(self, stmt: p.ReturnStmt) -> None:
|
def visit_return_stmt(self, stmt: p.ReturnStmt) -> None:
|
||||||
type: Type = self.type_of(stmt.value) if stmt.value is not None else UnitType()
|
type: Type = self.type_of(stmt.value) if stmt.value is not None else UnitType()
|
||||||
self.env.return_types.append(type)
|
self.env.return_types.append(type)
|
||||||
raise ReturnException()
|
raise ReturnException(stmt)
|
||||||
|
|
||||||
def visit_if_stmt(self, stmt: p.IfStmt) -> None:
|
def visit_if_stmt(self, stmt: p.IfStmt) -> None:
|
||||||
# Not evaluated in sub-environment because assignments in the test leak out of the if
|
# Not evaluated in sub-environment because assignments in the test leak out of the if
|
||||||
@@ -599,7 +604,7 @@ class PythonTyper(
|
|||||||
else_returned: bool = self.process_block(stmt.orelse, env)
|
else_returned: bool = self.process_block(stmt.orelse, env)
|
||||||
self.env.return_types.extend(env.return_types)
|
self.env.return_types.extend(env.return_types)
|
||||||
if body_returned and else_returned:
|
if body_returned and else_returned:
|
||||||
raise ReturnException()
|
raise ReturnException(stmt)
|
||||||
|
|
||||||
def visit_pass(self, stmt: p.Pass) -> None:
|
def visit_pass(self, stmt: p.Pass) -> None:
|
||||||
pass
|
pass
|
||||||
@@ -627,7 +632,7 @@ class PythonTyper(
|
|||||||
self.env = outer_env
|
self.env = outer_env
|
||||||
|
|
||||||
if body_returned:
|
if body_returned:
|
||||||
raise ReturnException()
|
raise ReturnException(stmt)
|
||||||
|
|
||||||
def visit_import_stmt(self, stmt: p.ImportStmt) -> None:
|
def visit_import_stmt(self, stmt: p.ImportStmt) -> None:
|
||||||
self._visit_imports(stmt.location, stmt.imports)
|
self._visit_imports(stmt.location, stmt.imports)
|
||||||
@@ -772,14 +777,21 @@ class PythonTyper(
|
|||||||
match expr.callee:
|
match expr.callee:
|
||||||
case p.GetExpr(object=obj, name=method):
|
case p.GetExpr(object=obj, name=method):
|
||||||
obj_type: Type = self.type_of(obj)
|
obj_type: Type = self.type_of(obj)
|
||||||
return self.call_method(
|
try:
|
||||||
location=expr.location,
|
return self.call_method(
|
||||||
call_expr=expr,
|
location=expr.location,
|
||||||
obj=(obj, obj_type),
|
call_expr=expr,
|
||||||
method_name=method,
|
obj=(obj, obj_type),
|
||||||
positional=positional,
|
method_name=method,
|
||||||
keywords=keywords,
|
positional=positional,
|
||||||
)
|
keywords=keywords,
|
||||||
|
)
|
||||||
|
except UndefinedMethodException:
|
||||||
|
self.reporter.error(
|
||||||
|
expr.location,
|
||||||
|
f"Unknown method {method} on type {obj_type}",
|
||||||
|
)
|
||||||
|
return UnknownType()
|
||||||
|
|
||||||
callee: Type = self.type_of(expr.callee)
|
callee: Type = self.type_of(expr.callee)
|
||||||
result: CallResult = self.dispatcher.get_result(
|
result: CallResult = self.dispatcher.get_result(
|
||||||
@@ -1001,7 +1013,11 @@ class PythonTyper(
|
|||||||
|
|
||||||
if len(node.args) != 0:
|
if len(node.args) != 0:
|
||||||
args: list[Type] = [self.resolve_type_expr(arg) for arg in node.args]
|
args: list[Type] = [self.resolve_type_expr(arg) for arg in node.args]
|
||||||
return self.types.apply_generic(base, args)
|
try:
|
||||||
|
return self.types.apply_generic(base, args)
|
||||||
|
except Exception as e:
|
||||||
|
self.reporter.error(node.location, f"Cannot apply generic type: {e}")
|
||||||
|
return UnknownType()
|
||||||
return base
|
return base
|
||||||
|
|
||||||
def visit_frame_column(self, node: p.FrameColumn) -> ColumnType:
|
def visit_frame_column(self, node: p.FrameColumn) -> ColumnType:
|
||||||
@@ -1078,10 +1094,13 @@ class PythonTyper(
|
|||||||
bound: Optional[Type] = None
|
bound: Optional[Type] = None
|
||||||
variance: Variance = Variance.INVARIANT
|
variance: Variance = Variance.INVARIANT
|
||||||
if "bound" in call.keywords:
|
if "bound" in call.keywords:
|
||||||
bound_type: p.MidasType = self._parse_type_from_expr(
|
try:
|
||||||
call.keywords["bound"]
|
bound_type: p.MidasType = self._parse_type_from_expr(
|
||||||
)
|
call.keywords["bound"]
|
||||||
bound = self.resolve_type_expr(bound_type)
|
)
|
||||||
|
bound = self.resolve_type_expr(bound_type)
|
||||||
|
except NotImplementedError:
|
||||||
|
bound = UnknownType()
|
||||||
|
|
||||||
if is_kw_true("covariant"):
|
if is_kw_true("covariant"):
|
||||||
variance = Variance.COVARIANT
|
variance = Variance.COVARIANT
|
||||||
@@ -1096,7 +1115,13 @@ class PythonTyper(
|
|||||||
else:
|
else:
|
||||||
variance = Variance.CONTRAVARIANT
|
variance = Variance.CONTRAVARIANT
|
||||||
var: TypeVar = TypeVar(name=name, bound=bound, variance=variance)
|
var: TypeVar = TypeVar(name=name, bound=bound, variance=variance)
|
||||||
self.types.define_type(name, var)
|
try:
|
||||||
|
self.types.define_type(name, var)
|
||||||
|
except ValueError:
|
||||||
|
self.reporter.error(
|
||||||
|
call.location,
|
||||||
|
f"A type or type variable with the name {name} is already defined",
|
||||||
|
)
|
||||||
return var
|
return var
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
@@ -1127,6 +1152,14 @@ class PythonTyper(
|
|||||||
return parser._parse_type(node.body)
|
return parser._parse_type(node.body)
|
||||||
case p.VariableExpr(name=name):
|
case p.VariableExpr(name=name):
|
||||||
return p.BaseType(location=location, base=name, args=())
|
return p.BaseType(location=location, base=name, args=())
|
||||||
|
case p.SubscriptExpr(object=p.VariableExpr(name=name), index=arg):
|
||||||
|
args: tuple[p.MidasType, ...] = (
|
||||||
|
tuple(self._parse_type_from_expr(a) for a in arg.items)
|
||||||
|
if isinstance(arg, p.TupleExpr)
|
||||||
|
else (self._parse_type_from_expr(arg),)
|
||||||
|
)
|
||||||
|
return p.BaseType(location=location, base=name, args=args)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|||||||
@@ -362,9 +362,6 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
|||||||
case UnknownType() | UnitType():
|
case UnknownType() | UnitType():
|
||||||
return type
|
return type
|
||||||
|
|
||||||
case TopType() | GenericType():
|
|
||||||
raise NotImplementedError(f"Unsupported type {type}")
|
|
||||||
|
|
||||||
# Ensure exhaustiveness
|
# Ensure exhaustiveness
|
||||||
case _:
|
case _:
|
||||||
assert_never(type)
|
assert_never(type)
|
||||||
|
|||||||
Reference in New Issue
Block a user