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 constraint: Expr
class ComplexType:
members: list[MemberStmt]
class ExtensionType:
base: Type
extension: ComplexType
class FunctionType: class FunctionType:
params: ParamSpec params: ParamSpec
returns: Type returns: Type

View File

@@ -256,12 +256,6 @@ class Type(ABC):
@abstractmethod @abstractmethod
def visit_constraint_type(self, type: ConstraintType) -> T: ... 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 @abstractmethod
def visit_function_type(self, type: FunctionType) -> T: ... def visit_function_type(self, type: FunctionType) -> T: ...
@@ -295,23 +289,6 @@ class ConstraintType(Type):
return visitor.visit_constraint_type(self) 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) @dataclass(frozen=True)
class FunctionType(Type): class FunctionType(Type):
params: ParamSpec params: ParamSpec

View File

@@ -124,19 +124,6 @@ class MidasPrinter(
res += " where " + type.constraint.accept(self) res += " where " + type.constraint.accept(self)
return res 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: def visit_function_type(self, type: m.FunctionType) -> str:
spec: str = self._visit_param_spec(type.params) spec: str = self._visit_param_spec(type.params)
return f"fn {spec} -> {type.returns.accept(self)}" return f"fn {spec} -> {type.returns.accept(self)}"

View File

@@ -177,21 +177,6 @@ class MidasAstPrinter(
with self._child_level(single=True): with self._child_level(single=True):
type.constraint.accept(self) 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: def visit_function_type(self, type: m.FunctionType) -> None:
self._write_line("FunctionType") self._write_line("FunctionType")
with self._child_level(): 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.reporter import FileReporter, Reporter
from midas.checker.types import ( from midas.checker.types import (
ColumnType, ColumnType,
ComplexType,
ConstraintType, ConstraintType,
DataFrameType, DataFrameType,
DerivedType, DerivedType,
ExtensionType,
Function, Function,
GenericType, GenericType,
ParamSpec, ParamSpec,
@@ -422,19 +420,6 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
constraint=type.constraint, 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: def visit_function_type(self, type: m.FunctionType) -> Type:
return Function( return Function(
params=self._visit_param_spec(type.params), params=self._visit_param_spec(type.params),

View File

@@ -8,11 +8,9 @@ from midas.checker.types import (
AppliedType, AppliedType,
BaseType, BaseType,
ColumnType, ColumnType,
ComplexType,
ConstraintType, ConstraintType,
DataFrameType, DataFrameType,
DerivedType, DerivedType,
ExtensionType,
Function, Function,
GenericType, GenericType,
OverloadedFunction, OverloadedFunction,
@@ -202,14 +200,6 @@ class TypesRegistry:
case (BaseType(name=name1), BaseType(name=name2)): case (BaseType(name=name1), BaseType(name=name2)):
return self.is_builtin_subtype(name1, 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)): case (DataFrameType(columns=columns1), DataFrameType(columns=columns2)):
# TODO: check order? # TODO: check order?
by_name1: dict[str, DataFrameType.Column] = { by_name1: dict[str, DataFrameType.Column] = {
@@ -506,20 +496,6 @@ class TypesRegistry:
member_type2 = substitute_typevars(member_type2, substitutions) member_type2 = substitute_typevars(member_type2, substitutions)
return member_type2 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): case ConstraintType(type=base):
return self.lookup_member(base, member_name) return self.lookup_member(base, member_name)

View File

@@ -113,28 +113,6 @@ class OverloadedFunction:
return "<overloaded function>" 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): class Variance(StrEnum):
"""The variance of a :class:`TypeVar`""" """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): case AppliedType(name=name, args=args, body=body):
return AppliedType( return AppliedType(
name=name, name=name,
@@ -468,9 +428,6 @@ def to_annotation(type: Type) -> str:
case OverloadedFunction(): case OverloadedFunction():
return "Callable" return "Callable"
case ComplexType() | ExtensionType():
raise NotImplementedError
case TypeVar(name=name): case TypeVar(name=name):
return name return name
@@ -519,8 +476,6 @@ Type = (
| UnitType | UnitType
| Function | Function
| OverloadedFunction | OverloadedFunction
| ComplexType
| ExtensionType
| TypeVar | TypeVar
| GenericType | GenericType
| AppliedType | AppliedType

View File

@@ -338,21 +338,11 @@ class MidasHighlighter(
type.type.accept(self) type.type.accept(self)
type.constraint.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: def visit_function_type(self, type: m.FunctionType) -> None:
self.wrap(type, "function") self.wrap(type, "function")
self._visit_param_spec(type.params) self._visit_param_spec(type.params)
type.returns.accept(self) 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: def _visit_param_spec(self, spec: m.ParamSpec) -> None:
for param in spec.pos + spec.mixed + spec.kw: for param in spec.pos + spec.mixed + spec.kw:
param.type.accept(self) param.type.accept(self)

View File

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

View File

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

View File

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

View File

@@ -5,11 +5,9 @@ from midas.ast.midas import (
AliasStmt, AliasStmt,
BinaryExpr, BinaryExpr,
CallExpr, CallExpr,
ComplexType,
ConstraintType, ConstraintType,
Expr, Expr,
ExtendStmt, ExtendStmt,
ExtensionType,
FrameType, FrameType,
FunctionType, FunctionType,
GenericType, GenericType,
@@ -187,19 +185,9 @@ class MidasParser(Parser[list[Stmt]]):
Returns: Returns:
TypeExpr: the parsed type expression TypeExpr: the parsed type expression
""" """
base: Type
if self.match(TokenType.FUNC): if self.match(TokenType.FUNC):
base = self.function() return self.function()
else: return self.constraint_type()
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
def constraint_type(self) -> Type: def constraint_type(self) -> Type:
"""Parse a constraint type expression """Parse a constraint type expression
@@ -235,9 +223,6 @@ class MidasParser(Parser[list[Stmt]]):
self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis") self.consume(TokenType.RIGHT_PAREN, "Unclosed parenthesis")
return type return type
if self.check(TokenType.LEFT_BRACE):
return self.complex_type()
return self.generic_type() return self.generic_type()
def generic_type(self) -> Type: def generic_type(self) -> Type:
@@ -295,34 +280,6 @@ class MidasParser(Parser[list[Stmt]]):
name=name, 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: def frame_type(self) -> FrameType:
"""Parse a frame type expression """Parse a frame type expression

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
# type: ignore # type: ignore
# ruff: disable [F821] # ruff: disable [F821]
df1: Frame[a:int, b:float] df1: Frame[i:int, a:int, b:float]
df2: Frame[a:int, b:float] df2: Frame[i:int, a:int, b:float]
_: Any _: Any
@@ -38,7 +38,7 @@ _ = df1.sum()
_ = df1.var() _ = df1.var()
# Groupby # Groupby
df_gb = df1.groupby(by="a") df_gb = df1.groupby(by="i")
_ = df_gb.kurt() _ = df_gb.kurt()
_ = df_gb.max() _ = 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 type Difference[T] = T
// Complex custom type, containing two values accessible through properties // Complex custom type, containing two values accessible through properties
type GeoLocation = { type GeoLocation = object
extend GeoLocation {
prop lat: Latitude prop lat: Latitude
prop lon: Longitude prop lon: Longitude
} }
// Define operations on our custom type type GeoLocationDifference = object
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]
}
// For complex generics, you need to specify how the genericity the properties extend GeoLocationDifference {
// are handled
type Difference[GeoLocation] = {
prop lat: Difference[Latitude] prop lat: Difference[Latitude]
prop lon: Difference[Longitude] prop lon: Difference[Longitude]
} }
// Define operations on our custom type
// Simple operation defined on our custom types // Simple operation defined on our custom types
extend Latitude { extend Latitude {
def __sub__: fn(Latitude, /) -> Difference[Latitude] def __sub__: fn(Latitude, /) -> Difference[Latitude]
@@ -38,13 +34,23 @@ extend Longitude {
def __sub__: fn(Longitude, /) -> Difference[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 // Predefined custom predicates that can be referenced in other definitions
predicate Positive(v: float) = v >= 0 predicate Positive(v: float) = v >= 0
predicate StrictlyPositive(v: float) = v > 0 predicate StrictlyPositive(v: float) = v > 0
predicate Equatorial(loc: GeoLocation) = (-10 <= loc.lat <= 10) predicate Equatorial(loc: GeoLocation) = (-10 <= loc.lat <= 10)
predicate Arctic(loc: GeoLocation) = (loc.lat >= 66) predicate Arctic(loc: GeoLocation) = (loc.lat >= 66)
type Person = { type Person = object
extend Person {
prop name: str prop name: str
// Property with an inline constraint // 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, AliasStmt,
BinaryExpr, BinaryExpr,
CallExpr, CallExpr,
ComplexType,
ConstraintType, ConstraintType,
Expr, Expr,
ExtendStmt, ExtendStmt,
ExtensionType,
FrameType, FrameType,
FunctionType, FunctionType,
GenericType, GenericType,
@@ -172,12 +170,6 @@ class MidasAstJsonSerializer(
"constraint": type.constraint.accept(self), "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: def visit_function_type(self, type: FunctionType) -> dict:
return { return {
"_type": "FunctionType", "_type": "FunctionType",
@@ -200,13 +192,6 @@ class MidasAstJsonSerializer(
"required": param.required, "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: def visit_frame_type(self, type: FrameType) -> dict:
return { return {
"_type": "FrameType", "_type": "FrameType",