Remove complex type #35

Merged
HEL merged 4 commits from feat/remove-complex-type into main 2026-07-08 13:44:59 +00:00
19 changed files with 2195 additions and 1797 deletions

View File

@@ -136,15 +136,6 @@ class ConstraintType:
constraint: Expr
class ComplexType:
members: list[MemberStmt]
class ExtensionType:
base: Type
extension: ComplexType
class FunctionType:
params: ParamSpec
returns: Type

View File

@@ -256,12 +256,6 @@ class Type(ABC):
@abstractmethod
def visit_constraint_type(self, type: ConstraintType) -> T: ...
@abstractmethod
def visit_complex_type(self, type: ComplexType) -> T: ...
@abstractmethod
def visit_extension_type(self, type: ExtensionType) -> T: ...
@abstractmethod
def visit_function_type(self, type: FunctionType) -> T: ...
@@ -295,23 +289,6 @@ class ConstraintType(Type):
return visitor.visit_constraint_type(self)
@dataclass(frozen=True)
class ComplexType(Type):
members: list[MemberStmt]
def accept(self, visitor: Type.Visitor[T]) -> T:
return visitor.visit_complex_type(self)
@dataclass(frozen=True)
class ExtensionType(Type):
base: Type
extension: ComplexType
def accept(self, visitor: Type.Visitor[T]) -> T:
return visitor.visit_extension_type(self)
@dataclass(frozen=True)
class FunctionType(Type):
params: ParamSpec

View File

@@ -124,19 +124,6 @@ class MidasPrinter(
res += " where " + type.constraint.accept(self)
return res
def visit_complex_type(self, type: m.ComplexType) -> str:
res: str = "{\n"
self.level += 1
for member in type.members:
res += member.accept(self)
res += "\n"
self.level -= 1
res += self.indented("}")
return res
def visit_extension_type(self, type: m.ExtensionType) -> str:
return f"{type.base.accept(self)} & {type.extension.accept(self)}"
def visit_function_type(self, type: m.FunctionType) -> str:
spec: str = self._visit_param_spec(type.params)
return f"fn {spec} -> {type.returns.accept(self)}"

View File

@@ -177,21 +177,6 @@ class MidasAstPrinter(
with self._child_level(single=True):
type.constraint.accept(self)
def visit_complex_type(self, type: m.ComplexType) -> None:
self._write_line("ComplexType")
with self._child_level():
self._write_sequence("members", type.members, last=True)
def visit_extension_type(self, type: m.ExtensionType) -> None:
self._write_line("ExtensionType")
with self._child_level():
self._write_line("base")
with self._child_level(single=True):
type.base.accept(self)
self._write_line("extension", last=True)
with self._child_level(single=True):
type.extension.accept(self)
def visit_function_type(self, type: m.FunctionType) -> None:
self._write_line("FunctionType")
with self._child_level():

View File

@@ -13,11 +13,9 @@ from midas.checker.registry import TypesRegistry
from midas.checker.reporter import FileReporter, Reporter
from midas.checker.types import (
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
Function,
GenericType,
ParamSpec,
@@ -422,19 +420,6 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
constraint=type.constraint,
)
def visit_complex_type(self, type: m.ComplexType) -> ComplexType:
return ComplexType(
members={
member.name.lexeme: member.type.accept(self) for member in type.members
}
)
def visit_extension_type(self, type: m.ExtensionType) -> Type:
return ExtensionType(
base=type.base.accept(self),
extension=self.visit_complex_type(type.extension),
)
def visit_function_type(self, type: m.FunctionType) -> Type:
return Function(
params=self._visit_param_spec(type.params),

View File

@@ -8,11 +8,9 @@ from midas.checker.types import (
AppliedType,
BaseType,
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
Function,
GenericType,
OverloadedFunction,
@@ -202,14 +200,6 @@ class TypesRegistry:
case (BaseType(name=name1), BaseType(name=name2)):
return self.is_builtin_subtype(name1, name2)
case (ComplexType(properties=props1), ComplexType(properties=props2)):
for k, t in props2.items():
if k not in props1:
return False
if not self.is_subtype(props1[k], t):
return False
return True
case (DataFrameType(columns=columns1), DataFrameType(columns=columns2)):
# TODO: check order?
by_name1: dict[str, DataFrameType.Column] = {
@@ -506,20 +496,6 @@ class TypesRegistry:
member_type2 = substitute_typevars(member_type2, substitutions)
return member_type2
case ComplexType(members=members):
if member_name in members:
return members[member_name]
self.logger.debug(f"No member '{member_name}' in {type}")
return None
case ExtensionType(base=base, extension=ComplexType(members=members)):
if member_name in members:
return members[member_name]
self.logger.debug(
f"No member '{member_name}' on {type}, looking up in base"
)
return self.lookup_member(base, member_name)
case ConstraintType(type=base):
return self.lookup_member(base, member_name)

View File

@@ -113,28 +113,6 @@ class OverloadedFunction:
return "<overloaded function>"
@dataclass(frozen=True, kw_only=True)
class ComplexType:
"""A type with inline members"""
members: dict[str, Type]
def __str__(self) -> str:
props: list[str] = [f"{name}: {type}" for name, type in self.members.items()]
return f"{{{', '.join(props)}}}"
@dataclass(frozen=True, kw_only=True)
class ExtensionType:
"""An extension of a type, adding members through a `ComplexType`"""
base: Type
extension: ComplexType
def __str__(self) -> str:
return f"{self.base} & {self.extension}"
class Variance(StrEnum):
"""The variance of a :class:`TypeVar`"""
@@ -323,24 +301,6 @@ def substitute_typevars(type: Type, substitutions: dict[str, Type]) -> Type:
]
)
case ComplexType(members=members):
members2: dict[str, Type] = {
name: substitute_typevars(prop, substitutions)
for name, prop in members.items()
}
return ComplexType(members=members2)
case ExtensionType(base=base, extension=ComplexType(members=members)):
return ExtensionType(
base=substitute_typevars(base, substitutions),
extension=ComplexType(
members={
name: substitute_typevars(prop, substitutions)
for name, prop in members.items()
}
),
)
case AppliedType(name=name, args=args, body=body):
return AppliedType(
name=name,
@@ -468,9 +428,6 @@ def to_annotation(type: Type) -> str:
case OverloadedFunction():
return "Callable"
case ComplexType() | ExtensionType():
raise NotImplementedError
case TypeVar(name=name):
return name
@@ -519,8 +476,6 @@ Type = (
| UnitType
| Function
| OverloadedFunction
| ComplexType
| ExtensionType
| TypeVar
| GenericType
| AppliedType

View File

@@ -338,21 +338,11 @@ class MidasHighlighter(
type.type.accept(self)
type.constraint.accept(self)
def visit_complex_type(self, type: m.ComplexType) -> None:
self.wrap(type, "complex-type")
for member in type.members:
member.accept(self)
def visit_function_type(self, type: m.FunctionType) -> None:
self.wrap(type, "function")
self._visit_param_spec(type.params)
type.returns.accept(self)
def visit_extension_type(self, type: m.ExtensionType) -> None:
self.wrap(type, "extension")
type.base.accept(self)
type.extension.accept(self)
def _visit_param_spec(self, spec: m.ParamSpec) -> None:
for param in spec.pos + spec.mixed + spec.kw:
param.type.accept(self)

View File

@@ -7,8 +7,7 @@ span {
&.named-type,
&.generic-type,
&.constraint-type,
&.complex-type {
&.constraint-type {
--col: 150, 150, 150;
}

View File

@@ -16,11 +16,9 @@ from midas.checker.types import (
BaseType,
ColumnGroupBy,
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
FrameGroupBy,
Function,
GenericType,
@@ -675,8 +673,6 @@ class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
case (
Function()
| OverloadedFunction()
| ComplexType()
| ExtensionType()
| GenericType()
| FrameGroupBy()
| ColumnGroupBy()

View File

@@ -8,11 +8,9 @@ from midas.checker.types import (
BaseType,
ColumnGroupBy,
ColumnType,
ComplexType,
ConstraintType,
DataFrameType,
DerivedType,
ExtensionType,
FrameGroupBy,
Function,
GenericType,
@@ -269,14 +267,6 @@ class StubsGenerator:
right=self.dump_type(overloads[-1]),
)
case ComplexType():
name: str = self.new_stub_name()
self.generate_stub(name, type)
return ast.Name(id=name)
case ExtensionType():
raise NotImplementedError
case TypeVar():
return ast.Name(id=type.name)

View File

@@ -5,11 +5,9 @@ from midas.ast.midas import (
AliasStmt,
BinaryExpr,
CallExpr,
ComplexType,
ConstraintType,
Expr,
ExtendStmt,
ExtensionType,
FrameType,
FunctionType,
GenericType,
@@ -187,19 +185,9 @@ class MidasParser(Parser[list[Stmt]]):
Returns:
TypeExpr: the parsed type expression
"""
base: Type
if self.match(TokenType.FUNC):
base = self.function()
else:
base = self.constraint_type()
if self.match(TokenType.AND):
extension: ComplexType = self.complex_type()
return ExtensionType(
location=Location.span(base.location, extension.location),
base=base,
extension=extension,
)
return base
return self.function()
return self.constraint_type()
def constraint_type(self) -> Type:
"""Parse a constraint type expression
@@ -235,9 +223,6 @@ class MidasParser(Parser[list[Stmt]]):
self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis")
return type
if self.check(TokenType.LEFT_BRACE):
return self.complex_type()
return self.generic_type()
def generic_type(self) -> Type:
@@ -295,34 +280,6 @@ class MidasParser(Parser[list[Stmt]]):
name=name,
)
def complex_type(self) -> ComplexType:
"""Parse a complex type expression
A complex type consists of zero or more member statements enclosed in
curly braces
Returns:
ComplexType: the parsed complex type expression
"""
left: Token = self.consume(
TokenType.LEFT_BRACE, "Expected '{' to start type body"
)
members: list[MemberStmt] = []
# TODO: add keyword to differentiate properties and methods,
# and allow multiple methods with the same name but not properties
names: set[str] = set()
while not self.check(TokenType.RIGHT_BRACE) and not self.is_at_end():
member: MemberStmt = self.member_stmt()
# if member.name.lexeme in names:
# raise self.error(member.name, "Duplicate property")
# names.add(member.name.lexeme)
members.append(member)
right: Token = self.consume(TokenType.RIGHT_BRACE, "Unclosed type body")
return ComplexType(
location=left.location_to(right),
members=members,
)
def frame_type(self) -> FrameType:
"""Parse a frame type expression

View File

@@ -66,15 +66,13 @@ Params ::= (
)
ParamSpec ::= "(" Params? ")"
TypeProperty ::= Identifier ":" Type
ComplexType ::= "{" TypeProperty* "}"
NamedType ::= Identifier
TypeArgs ::= "[" (Type ("," Type)*)? "]"
FrameColumn ::= TOKEN ":" Type
FrameSchema ::= "[" (FrameColumn ("," FrameColumn)*)? "]"
GenericType ::= "Frame" FrameSchema | NamedType TypeArgs?
GroupedType ::= "(" Type ")"
BaseType ::= GroupedType | ComplexType | GenericType
BaseType ::= GroupedType | GenericType
ConstraintType ::= BaseType ("where" Constraint)?
FuncType ::= "fn" ParamSpec "->" Type
Type ::= ConstraintType

View File

@@ -72,14 +72,6 @@ svg.railroad .terminal rect {
{[`template` "[" <!, 'template-param'*","> "]"]}
```
#let type-property = ```
{[`type-property` 'identifier' ":" 'type']}
```
#let complex-type = ```
{[`complex-type` "{" <!, 'type-property'*!> "}"]}
```
#let named-type = ```
{[`named-type` 'identifier']}
```
@@ -101,7 +93,7 @@ svg.railroad .terminal rect {
```
#let base-type = ```
{[`base-type` <'grouped-type', 'complex-type', 'generic-type'>]}
{[`base-type` <'grouped-type', 'generic-type'>]}
```
#let constraint-type = ```
@@ -169,7 +161,6 @@ svg.railroad .terminal rect {
template-param: template-param,
template: template,
type-property: type-property,
complex-type: complex-type,
named-type: named-type,
type-args: type-args,
generic-type: generic-type,
@@ -196,7 +187,6 @@ svg.railroad .terminal rect {
"template",
"type-property",
"call-args",
"complex-type",
"type-args",
"named-type",
"grouped-type",

View File

@@ -1,8 +1,8 @@
# type: ignore
# ruff: disable [F821]
df1: Frame[a:int, b:float]
df2: Frame[a:int, b:float]
df1: Frame[i:int, a:int, b:float]
df2: Frame[i:int, a:int, b:float]
_: Any
@@ -38,7 +38,7 @@ _ = df1.sum()
_ = df1.var()
# Groupby
df_gb = df1.groupby(by="a")
df_gb = df1.groupby(by="i")
_ = df_gb.kurt()
_ = df_gb.max()

File diff suppressed because it is too large Load Diff

View File

@@ -9,26 +9,22 @@ type Longitude = float where (-180 <= _ <= 180)
type Difference[T] = T
// Complex custom type, containing two values accessible through properties
type GeoLocation = {
type GeoLocation = object
extend GeoLocation {
prop lat: Latitude
prop lon: Longitude
}
// Define operations on our custom type
extend GeoLocation {
// This type is compatible with the `-` operation with another GeoLocation
// i.e. you can subtract a GeoLocation from another GeoLocation, resulting
// in a Difference of GeoLocations
def __sub__: fn(GeoLocation, /) -> Difference[GeoLocation]
}
type GeoLocationDifference = object
// For complex generics, you need to specify how the genericity the properties
// are handled
type Difference[GeoLocation] = {
extend GeoLocationDifference {
prop lat: Difference[Latitude]
prop lon: Difference[Longitude]
}
// Define operations on our custom type
// Simple operation defined on our custom types
extend Latitude {
def __sub__: fn(Latitude, /) -> Difference[Latitude]
@@ -38,13 +34,23 @@ extend Longitude {
def __sub__: fn(Longitude, /) -> Difference[Longitude]
}
extend GeoLocation {
// This type is compatible with the `-` operation with another GeoLocation
// i.e. you can subtract a GeoLocation from another GeoLocation, resulting
// in a GeoLocationDifference
def __sub__: fn(GeoLocation, /) -> GeoLocationDifference
}
// Predefined custom predicates that can be referenced in other definitions
predicate Positive(v: float) = v >= 0
predicate StrictlyPositive(v: float) = v > 0
predicate Equatorial(loc: GeoLocation) = (-10 <= loc.lat <= 10)
predicate Arctic(loc: GeoLocation) = (loc.lat >= 66)
type Person = {
type Person = object
extend Person {
prop name: str
// Property with an inline constraint

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,9 @@ from midas.ast.midas import (
AliasStmt,
BinaryExpr,
CallExpr,
ComplexType,
ConstraintType,
Expr,
ExtendStmt,
ExtensionType,
FrameType,
FunctionType,
GenericType,
@@ -172,12 +170,6 @@ class MidasAstJsonSerializer(
"constraint": type.constraint.accept(self),
}
def visit_complex_type(self, type: ComplexType) -> dict:
return {
"_type": "ComplexType",
"members": self._serialize_list(type.members),
}
def visit_function_type(self, type: FunctionType) -> dict:
return {
"_type": "FunctionType",
@@ -200,13 +192,6 @@ class MidasAstJsonSerializer(
"required": param.required,
}
def visit_extension_type(self, type: ExtensionType) -> dict:
return {
"_type": "ExtensionType",
"base": type.base.accept(self),
"extension": type.extension.accept(self),
}
def visit_frame_type(self, type: FrameType) -> dict:
return {
"_type": "FrameType",