feat(types): add human-friendly string rep
add `__str__` methods on type structures to improve readability of diagnostics
This commit is contained in:
@@ -9,3 +9,6 @@ diff_y = p2.y - p1.y
|
||||
dist = diff_x + diff_y
|
||||
|
||||
p2.x += cast(Meter, 1)
|
||||
p2.y = True
|
||||
p2.z = 3
|
||||
p2.x.a = 3
|
||||
|
||||
@@ -273,7 +273,7 @@ class PythonTyper(
|
||||
if not self.is_subtype(value_type, var_type):
|
||||
self.reporter.error(
|
||||
location,
|
||||
f"Cannot assign {value_type} to {name} of type {var_type}",
|
||||
f"Cannot assign {value_type} to variable '{name}' of type {var_type}",
|
||||
)
|
||||
|
||||
def _assign_attr(self, location: Location, target: p.GetExpr, value_type: Type):
|
||||
@@ -283,7 +283,7 @@ class PythonTyper(
|
||||
case ComplexType(properties=properties):
|
||||
if target.name not in properties:
|
||||
self.reporter.error(
|
||||
target.location, f"Unknown property '{target.name} on {object}"
|
||||
target.location, f"Unknown property '{object}.{target.name}'"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -291,7 +291,7 @@ class PythonTyper(
|
||||
if not self.is_subtype(value_type, prop_type):
|
||||
self.reporter.error(
|
||||
location,
|
||||
f"Cannot assign {value_type} to property '{target.name}' of type {prop_type} on {object}",
|
||||
f"Cannot assign {value_type} to property '{object}.{target.name}' of type {prop_type}",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -301,7 +301,7 @@ class PythonTyper(
|
||||
case _:
|
||||
self.reporter.error(
|
||||
target.location,
|
||||
f"Cannot assign {value_type} to unknown property '{target.name}' on {object}",
|
||||
f"Cannot assign {value_type} to unknown property '{object}.{target.name}'",
|
||||
)
|
||||
|
||||
def visit_return_stmt(self, stmt: p.ReturnStmt) -> None:
|
||||
@@ -365,6 +365,9 @@ class PythonTyper(
|
||||
if i == j:
|
||||
continue
|
||||
sig2: Operation.CallSignature = op2.signature
|
||||
|
||||
# If op1 is not a full overload of op2 (i.e. operands of op1 are subtypes of op2's)
|
||||
# ambiguity -> not best match
|
||||
if not self.is_subtype(sig1.left, sig2.left) or not self.is_subtype(
|
||||
sig1.right, sig2.right
|
||||
):
|
||||
@@ -374,13 +377,9 @@ class PythonTyper(
|
||||
if best_match:
|
||||
return op1.result
|
||||
|
||||
overloads: list[str] = [
|
||||
f"({op.signature.left} {op.signature.method} {op.signature.right}) -> {op.result}"
|
||||
for op in valid_operations
|
||||
]
|
||||
self.reporter.error(
|
||||
expr.location,
|
||||
f"Ambiguous operation {method} between {left} and {right}, multiple matching overloads: {', '.join(overloads)}",
|
||||
f"Ambiguous operation {method} between {left} and {right}, multiple matching overloads: {', '.join(map(str, valid_operations))}",
|
||||
)
|
||||
return UnknownType()
|
||||
|
||||
|
||||
@@ -8,21 +8,29 @@ from typing import Optional
|
||||
class BaseType:
|
||||
name: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AliasType:
|
||||
name: str
|
||||
type: Type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class UnknownType:
|
||||
pass
|
||||
def __str__(self) -> str:
|
||||
return "<Unknown>"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class UnitType:
|
||||
pass
|
||||
def __str__(self) -> str:
|
||||
return "None"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -33,6 +41,23 @@ class Function:
|
||||
kw_args: list[Argument]
|
||||
returns: Type
|
||||
|
||||
def __str__(self) -> str:
|
||||
args: list[str] = []
|
||||
if len(self.pos_args) != 0:
|
||||
args += list(map(str, self.pos_args))
|
||||
if len(self.args) + len(self.kw_args) != 0:
|
||||
args.append("/")
|
||||
|
||||
if len(self.args) != 0:
|
||||
args += list(map(str, self.args))
|
||||
|
||||
if len(self.kw_args) != 0:
|
||||
if len(args) != 0:
|
||||
args.append("*")
|
||||
args += list(map(str, self.kw_args))
|
||||
|
||||
return f"{self.name}({', '.join(args)}) -> {self.returns}"
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class Argument:
|
||||
pos: int
|
||||
@@ -40,29 +65,48 @@ class Function:
|
||||
type: Type
|
||||
required: bool
|
||||
|
||||
def __str__(self) -> str:
|
||||
opt: str = "" if self.required else "?"
|
||||
return f"{self.name}: {self.type}{opt}"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ComplexType:
|
||||
properties: dict[str, Type]
|
||||
|
||||
def __str__(self) -> str:
|
||||
props: list[str] = [f"{name}: {type}" for name, type in self.properties.items()]
|
||||
return f"{{{', '.join(props)}}}"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class Operation:
|
||||
signature: CallSignature
|
||||
result: Type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.signature} -> {self.result}"
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class CallSignature:
|
||||
left: Type
|
||||
method: str
|
||||
right: Type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.method}({self.left}, {self.right})"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TypeVar:
|
||||
name: str
|
||||
bound: Optional[Type]
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.bound is not None:
|
||||
return f"{self.name} <: {self.bound}"
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class GenericType:
|
||||
@@ -70,6 +114,9 @@ class GenericType:
|
||||
params: list[TypeVar]
|
||||
body: Type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}[{', '.join(map(str, self.params))}]"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AppliedType:
|
||||
@@ -77,6 +124,9 @@ class AppliedType:
|
||||
args: list[Type]
|
||||
body: Type
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}[{', '.join(map(str, self.args))}]"
|
||||
|
||||
|
||||
def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
|
||||
def sub_argument(arg: Function.Argument):
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
13
|
||||
]
|
||||
},
|
||||
"message": "Cannot assign BaseType(name='str') to c of type BaseType(name='int')"
|
||||
"message": "Cannot assign str to variable 'c' of type int"
|
||||
}
|
||||
],
|
||||
"judgments": [
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
13
|
||||
]
|
||||
},
|
||||
"message": "Wrong type for argument 'a', expected BaseType(name='int'), got BaseType(name='str')"
|
||||
"message": "Wrong type for argument 'a', expected int, got str"
|
||||
},
|
||||
{
|
||||
"type": "Error",
|
||||
@@ -250,7 +250,7 @@
|
||||
25
|
||||
]
|
||||
},
|
||||
"message": "Wrong type for argument 'c', expected BaseType(name='str'), got BaseType(name='bool')"
|
||||
"message": "Wrong type for argument 'c', expected str, got bool"
|
||||
}
|
||||
],
|
||||
"judgments": [
|
||||
|
||||
Reference in New Issue
Block a user