21 Commits
Author SHA1 Message Date
HEL b3ff5a7b96 Merge pull request 'Tidy examples and minor housekeeping' (#39) from feat/tidy-examples into main
Tests / tests (push) Successful in 5s
Reviewed-on: #39
2026-07-09 21:41:48 +00:00
HEL dce35391b4 chore: add justfile
Tests / tests (pull_request) Successful in 6s
2026-07-09 23:41:04 +02:00
HEL 28be45eb8f chore: add CI badge 2026-07-09 23:41:03 +02:00
HEL b64110bb06 chore: remove draft architecture diagram 2026-07-09 23:41:02 +02:00
HEL edc2a65492 chore: remove test file 2026-07-09 23:41:01 +02:00
HEL dade8eb0f4 chore: move currency demo in subfolder 2026-07-09 23:41:01 +02:00
HEL 9f3dfd686b chore: remove syntax prototype examples 2026-07-09 23:41:00 +02:00
HEL 5ef21917ae chore: update and clean some examples 2026-07-09 23:40:59 +02:00
HEL 34072de270 Merge pull request 'Minor fixes and more tests' (#38) from feat/add-tests into main
Tests / tests (push) Successful in 5s
Reviewed-on: #38
2026-07-09 20:19:42 +00:00
HEL 2b09a29165 tests: add frame constraint generation test
Tests / tests (pull_request) Successful in 6s
2026-07-09 22:16:28 +02:00
HEL 309e1d4a5b tests: add generator test for simple syntax 2026-07-09 22:04:39 +02:00
HEL 74472c974b tests: add test covering some function subtyping rules 2026-07-09 21:58:51 +02:00
HEL a9a3164c24 fix: implement missing methods on printer and highlighter 2026-07-09 18:57:26 +02:00
HEL 88ab9dc14d fix: add final decorator to Visitor subclasses 2026-07-09 18:30:06 +02:00
HEL f41dbb528c tests: add custom JSON encoder to for checker output 2026-07-09 18:27:51 +02:00
HEL 44963db425 tests: add test for all supported Python syntaxes 2026-07-09 17:59:20 +02:00
HEL b67c940112 tests: add test for all Midas syntaxes 2026-07-09 17:47:18 +02:00
HEL ad40db98d0 fix(checker): always type check unary operand 2026-07-09 17:46:52 +02:00
HEL 7a021b2450 fix(checker): type check none literal 2026-07-09 17:39:11 +02:00
HEL a7b62e752b fix(checker): adjust error message location
when constraint doesn't evaluate to a boolean, report error on the constraint expression instead of the whole type
2026-07-09 17:38:53 +02:00
HEL 44984af8a3 fix(checker): avoid raising on unknown variable
when an unknown variable is referenced, avoid raising an error and only report it with a diagnostic, returning `UnknownType` instead
2026-07-09 17:37:49 +02:00
46 changed files with 6739 additions and 527 deletions
+2
View File
@@ -1,5 +1,7 @@
<h1>Midas</h1>
<img src="https://git.kb28.ch/HEL/midas/actions/workflows/tests.yaml/badge.svg">
*Midas* is a type system to _Maintain Integrity of Data with Annotated Structures_. In Greek mythology, [Midas](https://en.wikipedia.org/wiki/Midas) was a Phrygian king who was blessed with the gift of turning everything he touched into gold.
*Midas* aims at providing Python developers with a simple annotation system to enable compile-time integrity and data type checks, as well as generating runtime assertions.
-150
View File
@@ -1,150 +0,0 @@
#import "@preview/cetz:0.5.2": canvas, draw
#let diagram-only = false
#set document(
title: [Midas Architecture],
//author: "Louis Heredero",
)
#set text(
font: "Source Sans 3",
)
#let diagram = canvas({
let framed = draw.content.with(
padding: (x: .8em, y: 1em),
frame: "rect",
stroke: black,
)
let arrow = draw.line.with(mark: (end: ">", fill: black))
framed(
(0, 0),
name: "python-parser",
)[Python parser]
draw.content(
(rel: (0, 1), to: "python-parser.north"),
padding: 5pt,
anchor: "south",
name: "source-py",
)[_`source.py`_]
arrow("source-py", "python-parser")
framed(
(rel: (3, 0), to: "python-parser.east"),
anchor: "west",
name: "custom-parser",
align(center)[Custom python\ parser],
)
arrow("python-parser", "custom-parser", name: "arrow-python-ast")
draw.content(
"arrow-python-ast",
anchor: "south",
padding: 5pt,
)[`ast.Module`]
framed(
(rel: (-3, -2), to: "custom-parser.south"),
anchor: "east",
name: "python-resolver",
)[Python Resolver]
arrow(
"custom-parser",
((), "|-", "python-resolver.east"),
"python-resolver",
name: "arrow-python-custom-ast",
)
draw.content(
(rel: (1.5, 0), to: "arrow-python-custom-ast.end"),
padding: 5pt,
anchor: "south",
)[P-AST#footnote[#strong[P]ython *AST*]<fn-past>]
draw.content(
"python-resolver.west",
padding: 5pt,
anchor: "south-east",
)[Resolved P-AST@fn-past]
draw.circle(
(rel: (1, -2), to: "custom-parser.south-east"),
radius: .4,
name: "midas-loader",
)
arrow(
"custom-parser",
"midas-loader",
name: "arrow-load-midas",
mark: (end: (symbol: ">", fill: black), start: "o"),
)
draw.content(
"arrow-load-midas",
anchor: "west",
padding: 5pt,
)[```python midas.using("types.midas")```]
framed(
(rel: (0, -2), to: "midas-loader.south"),
name: "midas-parser",
)[Midas lexer/parser]
arrow("midas-loader", "midas-parser", name: "arrow-midas-source")
draw.content(
"arrow-midas-source",
anchor: "west",
padding: 5pt,
)[_`types.midas`_]
framed(
(rel: (-2, 0), to: "midas-parser.west"),
anchor: "east",
name: "midas-resolver",
)[Midas Resolver]
arrow("midas-parser", "midas-resolver", name: "arrow-midas-ast")
draw.content(
"arrow-midas-ast",
anchor: "south",
padding: 5pt,
)[M-AST#footnote[#strong[M]idas *AST*]<fn-mast>]
framed(
(rel: (-3, 0), to: "midas-resolver.west"),
anchor: "east",
name: "checker",
)[Checker]
arrow("midas-resolver", "checker", name: "arrow-type-ctx")
arrow(
"python-resolver",
((), "-|", "checker.north"),
"checker",
)
draw.content(
"arrow-type-ctx",
anchor: "south",
padding: 5pt,
)[Types context]
})
#show: doc => if diagram-only {
set page(width: auto, height: auto, margin: .5cm)
diagram
} else { doc }
#align(center, title())
#v(1cm)
#figure(
diagram,
caption: [Midas type-checker architecture],
)
== Components
- *Python parser*: builtin Python AST parser, extracts abstract syntax from the raw Python source (```python ast.parse(...)```)
- *Custom python parser*: converts the raw Python AST into custom, more suitable constructs, especially for type annotations
- *Python resolver*: resolves bindings and references, tracks binding scopes
- *Midas lexer/parser*: parses a Midas type definition file and extracts its AST
- *Midas resolver*: walks the AST and fills the environment with the defined types and operations
- *Checker*: evaluates expressions and checks type coherence
@@ -1,16 +0,0 @@
# type: ignore
# ruff: disable[F821]
from __future__ import annotations
# A simple data-frame with different column of various simple types
# Columns can be named and/or typed
df: Frame[
verified: bool,
birth_year: int,
height: float + ( _ > 0 ) + ( _ < 250 ),
name: str,
date: datetime,
float, # unnamed
unknown: _, # untyped
_ # unnamed and untyped
]
@@ -1,24 +0,0 @@
// Simple custom type derived from floats
type Latitude<float>
type Longitude<float>
// Complex custom type, containing two values accessible through properties
type GeoLocation<Latitude, Longitude> {
lat: Latitude
lon: Longitude
}
type LatitudeDiff<float>
type LongitudeDiff<float>
// Simple operation defined on our custom types
op <Latitude> - <Latitude> = <LatitudeDiff>
op <Longitude> - <Longitude> = <LongitudeDiff>
// Simple custom type with a constraint
type Age<int + (0 <= _) + (_ < 150)>
// Predefined custom constraints that can be referenced in other definitions
constraint Positive = _ >= 0
constraint StrictlyPositive = _ > 0
//constraint Even = _ % 2 == 0
@@ -1,30 +0,0 @@
# type: ignore
# ruff: disable[F821]
from __future__ import annotations
# A data-frame using a custom type
df: Frame[
location: GeoLocation
]
# Properties of a type can be used on a column of that type
lat: Column[GeoLocation] = df["location"].lat
lon: Column[GeoLocation] = df["location"].lon
# Unregistered operations between types are not permitted
lat + lon # Invalid operation
# Registered operations are permitted
lat1: Latitude = lat[0]
lat2: Latitude = lat[1]
lat_diff: Difference[Latitude] = lat2 - lat1 # Valid operation
# In addition to the type, a column can have one or more constraints, either defined inline or in a separate file
df2: Frame[
age: int + (_ >= 0),
height: float + (_ >= 0),
]
df2_bis: Frame[
age: int + Positive,
height: float + Positive,
]
@@ -1,73 +0,0 @@
// Simple custom type derived from float
type Custom(float)
// Simple custom types with constraints
type Latitude(float) where (-90 <= _ <= 90)
type Longitude(float) where (-180 <= _ <= 180)
// Generic custom type (a Difference of T is derived from T, e.g. a difference of floats is a float
type Difference[T](T)
// Complex custom type, containing two values accessible through properties
type GeoLocation {
lat: Latitude
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
op __sub__(GeoLocation) -> Difference[GeoLocation]
}
// For complex generics, you need to specify how the genericity the properties
// are handled
type Difference[GeoLocation] {
lat: Difference[Latitude]
lon: Difference[Longitude]
}
// Simple operation defined on our custom types
extend Latitude {
op __sub__(Latitude) -> Difference[Latitude]
}
extend Longitude {
op __sub__(Longitude) -> Difference[Longitude]
}
// 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 {
name: str
// Property with an inline constraint
age: int? where (0 <= _ < 150)
// Property referencing a predicate
height: float where StrictlyPositive
home: GeoLocation
}
// Custom complex type derived from another complex type, with a constraint
// on a property
// Multiple proposed syntaxes, not yet defined
// Explicit, but new keyword
type EquatorialPerson refines Person where Equatorial(_.home)
// Explicit with existing keyword, might be confusing if expectations regarding 'is'
type EquatorialPerson is Person where Equatorial(_.home)
// Consistent and Python-friendly but can be confused with structural extension
type EquatorialPerson(Person) where Equatorial(_.home)
// Allow new properties, probably not useful
type EquatorialPerson extends Person where Equatorial(_.home)
@@ -1,15 +0,0 @@
# type: ignore
# ruff: disable[F821]
from __future__ import annotations
def func(
col1: Column[float + (0 <= _ <= 1)],
col2: Column[float + (0 <= _ <= 1)],
) -> Column[float + (0 <= _ <= 2)]:
result: Column[float + (0 <= _ <= 2)] = col1 + col2
return result
def func2(a: int, /, b: float, *, c: str):
pass
@@ -1,33 +0,0 @@
type Foo1 = float
type Foo2 = float where (_ > 3)
type Foo3 = int | float
type Foo4 = int where (_ > 3) | float where (_ > 3)
type Foo5 = (int | float) where (_ > 3)
type Foo6 = {
foo: float
bar: float where (_ > 3)
}
type Foo7[T] = T where (_ > 3)
type Foo8[A, B<:int] = {
a: A
b: B
}
type Complex = {
a: int
b: int
}
type Complex2 = Complex where (_.a > 3 & _.b < 5)
predicate Positive(n: int) = n >= 0
extend Foo1 {
op __add__(Foo1) -> Foo1
}
extend Foo7[T] {
op __add__(Foo7[T]) -> Foo7[T]
}
type Optional[T] = None | T
@@ -6,7 +6,7 @@ c = a + b # -> int
c = "invalid" # -> can't assign str to int variable
d = True
e = d + d
e = d + d # -> addition not defined between booleans
f: float = a
@@ -1,6 +1,7 @@
# type: ignore
# ruff: disable [F821]
distance: Meter = cast(Meter, 123.45)
time: Second = cast(Second, 6.7)
distance = cast(Meter, 123.45)
time = cast(Second, 6.7)
speed = distance / time
print(speed)
@@ -1,3 +1,5 @@
# Return types must have a LUB
# Valid
def minimum(x: int, y: int):
if x < y:
return x
@@ -5,18 +7,28 @@ def minimum(x: int, y: int):
return y
# Invalid
def func(a: int):
if a < 5:
return "Oops"
return True
a = 15
b = 72
c = minimum(a, b)
# Recursive but typable thanks to return hint
def factorial(n: int) -> int:
if n <= 1:
return 1
return n * factorial(n - 1)
category = "Category 1" if a < 10 else "Category 2"
# Branches must be of the same type
category = "Category 1" if a < 10 else "Category 2" # Valid
category = "Category 1" if a < 10 else 3 # Invalid
def foo() -> None:
@@ -15,7 +15,9 @@ extend Coordinate {
type Difference[T <: float] = T
type MeterDifference = Difference[Meter]
type CompDiff[T <: float] = {
type CompDiff[T <: float] = object
extend CompDiff[T <: float] {
prop d1: Difference[T]
prop d2: Difference[T]
}
@@ -1,20 +1,20 @@
# type: ignore
# ruff: disable [F821]
p1: Coordinate
p2: Coordinate
p1 = cast(Coordinate, object())
p2 = cast(Coordinate, object())
diff_x = p2.x - p1.x
diff_y = p2.y - p1.y
dist = diff_x + diff_y
p2.x += cast(Meter, 1)
p2.x += cast(Meter, 1.0)
p2.y = True # invalid, wrong type
p2.z = 3 # invalid, no property 'z' on Coordinate
p2.x.a = 3 # invalid, no properties on Meter
foo: list[float] = []
foo = cast(list[float], [])
append = foo.append
@@ -23,7 +23,7 @@ foo.append(2)
append(True) # invalid, must be float
append(2)
bar: list[list[Meter]]
bar = cast(list[list[Meter]], [])
bar.append([p2.x])
@@ -1,9 +1,9 @@
# type: ignore
# ruff: disable [F821]
foo: Foo
t1: T1
t2: T2
foo = cast(Foo, object())
t1 = cast(T1, object())
t2 = cast(T2, object())
a = foo.bar(t1)
b = foo.bar(t2)
@@ -13,6 +13,6 @@ func = foo.bar
c = func(t1)
d = func(t2)
t2b: T2b
t2b = cast(T2b, object())
e = foo.bar(t2b)
-14
View File
@@ -1,14 +0,0 @@
from __future__ import annotations
from typing import Generic, TypeVar
class Currency(float): ...
_T0 = TypeVar("_T0", bound=Currency, covariant=True)
class Price(Currency, Generic[_T0]):
def __add__(self, _0: Price[_T0], /) -> Price[_T0]: ...
class EUR(Currency): ...
class USD(Currency): ...
class CHF(Currency): ...
class Discount(float): ...
+14
View File
@@ -0,0 +1,14 @@
# Local Variables:
# mode: makefile
# End:
set shell := ["bash", "-uc"]
build-docs:
typst c --root . docs/manual.typ
typst c --root . docs/function_subtyping.typ
tests:
uv run -m tests
check-docstrings:
uv run scripts/docstring_checker.py
+3
View File
@@ -1,6 +1,9 @@
from typing import final
import midas.ast.midas as m
@final
class MidasPrinter(
m.Expr.Visitor[str],
m.Stmt.Visitor[str],
+3
View File
@@ -1,7 +1,10 @@
from typing import final
import midas.ast.midas as m
from midas.ast.printer.base import AstPrinter
@final
class MidasAstPrinter(
AstPrinter,
m.Expr.Visitor[None],
+20
View File
@@ -1,9 +1,11 @@
import ast
from typing import final
import midas.ast.python as p
from midas.ast.printer.base import AstPrinter
@final
class PythonAstPrinter(
AstPrinter,
p.MidasType.Visitor[None],
@@ -115,6 +117,24 @@ class PythonAstPrinter(
stmt.iterator.accept(self)
self._write_sequence("body", stmt.body, last=True)
def visit_import_stmt(self, stmt: p.ImportStmt) -> None:
self._write_line("ImportStmt")
with self._child_level(single=True):
self._write_sequence("imports", stmt.imports, print_func=self._print_import)
def visit_from_import_stmt(self, stmt: p.FromImportStmt) -> None:
self._write_line("FromImportStmt")
with self._child_level():
self._write_line(f'module: "{stmt.module}"')
self._write_sequence("imports", stmt.imports, print_func=self._print_import)
self._write_line(f"level: {stmt.level}", last=True)
def _print_import(self, import_: p.ImportAlias) -> None:
self._write_line("ImportAlias")
with self._child_level():
self._write_line(f'name: "{import_.name}"')
self._write_line(f'alias: "{import_.alias}"')
def visit_raw_stmt(self, stmt: p.RawStmt) -> None:
self._write_line("RawStmt")
with self._child_level(single=True):
+2 -1
View File
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, Callable, Optional
from typing import Any, Callable, Optional, final
import midas.ast.midas as m
from midas.ast.location import Location
@@ -18,6 +18,7 @@ class PartialPredicate(Predicate):
"""A dictionary of already applied parameters"""
@final
class Evaluator(m.Expr.Visitor[Any]):
"""Helper class to evaluate an expression
+14 -10
View File
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import Optional
from typing import Optional, final
import midas.ast.midas as m
from midas.ast.location import Location
@@ -30,6 +30,7 @@ from midas.lexer.token import Token, TokenType
from midas.parser.midas import MidasParser
@final
class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type]):
"""A resolver which evaluates Midas type definitions and build a registry"""
@@ -110,7 +111,7 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
return self._local_variables[name]
return self.types.get_type(name)
def get_variable(self, name: str) -> Type:
def get_variable(self, location: Location, name: str) -> Type:
"""Get the type of a variable
This function will first look into the current predicate's parameters if
@@ -118,11 +119,9 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
The the variable is looked up in the preamble (i.e. global environment)
Args:
location (Location): the location of the variable reference
name (str): the name of the variable
Raises:
NameError: if the variable cannot be found
Returns:
Type: the type of the variable
"""
@@ -136,7 +135,8 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
if global_ is not None:
return global_
raise NameError(f"Unknown variable '{name}'")
self.reporter.error(location, f"Unknown variable '{name}'")
return UnknownType()
def resolve(self, stmts: list[m.Stmt]):
"""Process a sequence of statements
@@ -293,6 +293,9 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
return result.result
def visit_unary_expr(self, expr: m.UnaryExpr) -> Type:
# First evaluate operand to surface all errors
operand: Type = self.type_of(expr.right)
# Special case because there is no __not__ dunder method
match expr.operator:
case Token(type=TokenType.BANG):
@@ -306,7 +309,6 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
)
return UnknownType()
operand: Type = self.type_of(expr.right)
operation: Optional[Type] = self.types.lookup_member(operand, method)
if operation is None:
self.reporter.error(
@@ -350,7 +352,7 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
return member
def visit_variable_expr(self, expr: m.VariableExpr) -> Type:
return self.get_variable(expr.name.lexeme)
return self.get_variable(expr.location, expr.name.lexeme)
def visit_grouping_expr(self, expr: m.GroupingExpr) -> Type:
return expr.expr.accept(self)
@@ -365,12 +367,14 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
return self.types.get_type("float")
case str():
return self.types.get_type("str")
case None:
return self.types.get_type("None")
case _:
self.reporter.warning(expr.location, f"Unknown literal {expr}")
return UnknownType()
def visit_wildcard_expr(self, expr: m.WildcardExpr) -> Type:
return self.get_variable("_")
return self.get_variable(expr.location, "_")
def visit_named_type(self, type: m.NamedType) -> Type:
name: str = type.name.lexeme
@@ -409,7 +413,7 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
self._predicate_params = {}
if not self.types.is_subtype(constraint_type, self._bool):
self.reporter.error(
type.location,
type.constraint.location,
f"Constraint must evaluate to a boolean, got {constraint_type}",
)
+2 -1
View File
@@ -1,6 +1,6 @@
import ast
import logging
from typing import Any, Optional
from typing import Any, Optional, final
import midas.ast.python as p
from midas.ast.location import Location
@@ -55,6 +55,7 @@ class UndefinedMethodException(Exception):
pass
@final
class PythonTyper(
p.Stmt.Visitor[None],
p.Expr.Visitor[Type],
+3
View File
@@ -1,3 +1,5 @@
from typing import final
import midas.ast.python as p
from midas.ast.location import Location
from midas.checker.reporter import FileReporter
@@ -6,6 +8,7 @@ from midas.checker.reporter import FileReporter
class ResolverError(Exception): ...
@final
class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
"""A variable assignment and reference resolver
+13 -1
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import Generic, Optional, Protocol, TextIO, TypeVar
from typing import Generic, Optional, Protocol, TextIO, TypeVar, final
import midas.ast.midas as m
import midas.ast.python as p
@@ -121,6 +121,7 @@ class Highlighter(ABC):
self.openings.setdefault((l + 1, 0), []).append(opening)
@final
class PythonHighlighter(
Highlighter,
p.MidasType.Visitor[None],
@@ -197,6 +198,10 @@ class PythonHighlighter(
for body_stmt in stmt.body:
body_stmt.accept(self)
def visit_import_stmt(self, stmt: p.ImportStmt) -> None: ...
def visit_from_import_stmt(self, stmt: p.FromImportStmt) -> None: ...
def visit_binary_expr(self, expr: p.BinaryExpr) -> None: ...
def visit_compare_expr(self, expr: p.CompareExpr) -> None: ...
@@ -255,6 +260,7 @@ class PythonHighlighter(
def visit_raw_stmt(self, stmt: p.RawStmt) -> None: ...
@final
class MidasHighlighter(
Highlighter, m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[None]
):
@@ -263,6 +269,11 @@ class MidasHighlighter(
def highlight(self, node: Highlightable[MidasHighlighter]):
node.accept(self)
def visit_alias_stmt(self, stmt: m.AliasStmt) -> None:
self.wrap(stmt, "alias-stmt")
self.wrap(LocatableToken(stmt.name), "type-name")
stmt.type.accept(self)
def visit_type_stmt(self, stmt: m.TypeStmt) -> None:
self.wrap(stmt, "type-stmt")
self.wrap(LocatableToken(stmt.name), "type-name")
@@ -352,6 +363,7 @@ class MidasHighlighter(
self.wrap(column, "column")
@final
class DiagnosticsHighlighter(Highlighter):
EXTRA_CSS_PATH: Optional[Path] = Path(__file__).parent / "hl_diagnostic.css"
+2 -1
View File
@@ -1,5 +1,5 @@
import ast
from typing import Optional
from typing import Optional, final
import midas.ast.midas as m
from midas.checker.registry import TypesRegistry
@@ -39,6 +39,7 @@ COMPARISON_OPERATORS: dict[TokenType, type[ast.cmpop]] = {
}
@final
class ConstraintGenerator(m.Expr.Visitor[ast.expr]):
"""Class to generate Python code for constraint expressions"""
+2 -1
View File
@@ -3,7 +3,7 @@ import logging
import shutil
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional, assert_never
from typing import Optional, assert_never, final
import midas.ast.midas as m
import midas.ast.python as p
@@ -47,6 +47,7 @@ class Scope:
"""A list of aliases defined in the scope, that can be discard afterwards"""
@final
class Generator(p.Stmt.Visitor[ast.stmt], p.Expr.Visitor[ast.expr]):
"""
A class to translate the custom Python AST back into raw `ast` nodes
-33
View File
@@ -1,33 +0,0 @@
import json
from pathlib import Path
from midas.ast.printer import MidasAstPrinter
from midas.lexer.midas import MidasLexer
from midas.lexer.token import Token
from midas.parser.midas import MidasParser
def test_midas():
# Midas type definitions
path: Path = Path("examples") / "00_syntax_prototype" / "03_custom_types_v2.midas"
definitions: str = path.read_text()
midas_lexer: MidasLexer = MidasLexer(definitions, path.name)
tokens: list[Token] = midas_lexer.process()
# print([f"{t.type.name}('{t.lexeme}')" for t in tokens])
with open("tokens.json", "w") as f:
json.dump([f"{t.type.name}('{t.lexeme}')" for t in tokens], f, indent=4)
parser = MidasParser(tokens)
parsed = parser.parse()
print(parsed)
for err in parser.errors:
print(err.get_report())
printer = MidasAstPrinter()
for stmt in parsed:
if stmt is None:
print("None")
continue
print(printer.print(stmt))
test_midas()
@@ -0,0 +1,44 @@
def a1(param: int) -> float: ...
def a2(param: float) -> int: ...
a = a1
a = a2
def b1(a: int, /) -> float: ...
def b2(b: float, /) -> int: ...
b = b1
b = b2
def c1(a: int) -> None: ...
def c2(p: float = 0, /, *, a: float = 0) -> None: ...
c = c1
c = c2
# Invalid subtypes
def d1(a: int) -> float: ...
def d2(a: str) -> float: ...
def d3(a: int) -> str: ...
d = d1
d = d2
d = d3
def e1(*, a: int = 0) -> None: ...
def e2(*, a: int) -> None: ...
def e3(*, a: int = 0, b: int) -> None: ...
e = e1
e = e2
e = e3
@@ -0,0 +1,881 @@
{
"diagnostics": [
{
"type": "Warning",
"location": {
"start": [
1,
29
],
"end": [
1,
32
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=1, col_offset=29, end_lineno=1, end_col_offset=32), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
1,
22
],
"end": [
1,
27
]
},
"message": "Return type mismatch, annotated float but returns None"
},
{
"type": "Warning",
"location": {
"start": [
2,
29
],
"end": [
2,
32
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=2, col_offset=29, end_lineno=2, end_col_offset=32), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
2,
24
],
"end": [
2,
27
]
},
"message": "Return type mismatch, annotated int but returns None"
},
{
"type": "Warning",
"location": {
"start": [
9,
28
],
"end": [
9,
31
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=9, col_offset=28, end_lineno=9, end_col_offset=31), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
9,
21
],
"end": [
9,
26
]
},
"message": "Return type mismatch, annotated float but returns None"
},
{
"type": "Warning",
"location": {
"start": [
10,
28
],
"end": [
10,
31
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=10, col_offset=28, end_lineno=10, end_col_offset=31), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
10,
23
],
"end": [
10,
26
]
},
"message": "Return type mismatch, annotated int but returns None"
},
{
"type": "Warning",
"location": {
"start": [
17,
24
],
"end": [
17,
27
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=17, col_offset=24, end_lineno=17, end_col_offset=27), value=Ellipsis)"
},
{
"type": "Warning",
"location": {
"start": [
18,
50
],
"end": [
18,
53
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=18, col_offset=50, end_lineno=18, end_col_offset=53), value=Ellipsis)"
},
{
"type": "Warning",
"location": {
"start": [
27,
25
],
"end": [
27,
28
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=27, col_offset=25, end_lineno=27, end_col_offset=28), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
27,
18
],
"end": [
27,
23
]
},
"message": "Return type mismatch, annotated float but returns None"
},
{
"type": "Warning",
"location": {
"start": [
28,
25
],
"end": [
28,
28
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=28, col_offset=25, end_lineno=28, end_col_offset=28), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
28,
18
],
"end": [
28,
23
]
},
"message": "Return type mismatch, annotated float but returns None"
},
{
"type": "Warning",
"location": {
"start": [
29,
23
],
"end": [
29,
26
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=29, col_offset=23, end_lineno=29, end_col_offset=26), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
29,
18
],
"end": [
29,
21
]
},
"message": "Return type mismatch, annotated str but returns None"
},
{
"type": "Error",
"location": {
"start": [
33,
0
],
"end": [
33,
6
]
},
"message": "Cannot assign (a: str) -> float to variable 'd' of type (a: int) -> float"
},
{
"type": "Error",
"location": {
"start": [
34,
0
],
"end": [
34,
6
]
},
"message": "Cannot assign (a: int) -> str to variable 'd' of type (a: int) -> float"
},
{
"type": "Warning",
"location": {
"start": [
37,
31
],
"end": [
37,
34
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=37, col_offset=31, end_lineno=37, end_col_offset=34), value=Ellipsis)"
},
{
"type": "Warning",
"location": {
"start": [
38,
27
],
"end": [
38,
30
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=38, col_offset=27, end_lineno=38, end_col_offset=30), value=Ellipsis)"
},
{
"type": "Warning",
"location": {
"start": [
39,
39
],
"end": [
39,
42
]
},
"message": "Unknown literal LiteralExpr(location=Location(lineno=39, col_offset=39, end_lineno=39, end_col_offset=42), value=Ellipsis)"
},
{
"type": "Error",
"location": {
"start": [
43,
0
],
"end": [
43,
6
]
},
"message": "Cannot assign (*, a: int) -> None to variable 'e' of type (*, a: int?) -> None"
},
{
"type": "Error",
"location": {
"start": [
44,
0
],
"end": [
44,
6
]
},
"message": "Cannot assign (*, a: int?, b: int) -> None to variable 'e' of type (*, a: int?) -> None"
}
],
"judgments": [
{
"location": {
"from": "L1:29",
"to": "L1:32"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L2:29",
"to": "L2:32"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L5:4",
"to": "L5:6"
},
"expr": {
"_type": "VariableExpr",
"name": "a1"
},
"type": {
"params": {
"pos": [],
"mixed": [
{
"pos": 0,
"name": "param",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
],
"kw": []
},
"returns": {
"name": "float"
}
}
},
{
"location": {
"from": "L6:4",
"to": "L6:6"
},
"expr": {
"_type": "VariableExpr",
"name": "a2"
},
"type": {
"params": {
"pos": [],
"mixed": [
{
"pos": 0,
"name": "param",
"type": {
"name": "float"
},
"required": true,
"unsupported": false
}
],
"kw": []
},
"returns": {
"name": "int"
}
}
},
{
"location": {
"from": "L9:28",
"to": "L9:31"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L10:28",
"to": "L10:31"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L13:4",
"to": "L13:6"
},
"expr": {
"_type": "VariableExpr",
"name": "b1"
},
"type": {
"params": {
"pos": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
],
"mixed": [],
"kw": []
},
"returns": {
"name": "float"
}
}
},
{
"location": {
"from": "L14:4",
"to": "L14:6"
},
"expr": {
"_type": "VariableExpr",
"name": "b2"
},
"type": {
"params": {
"pos": [
{
"pos": 0,
"name": "b",
"type": {
"name": "float"
},
"required": true,
"unsupported": false
}
],
"mixed": [],
"kw": []
},
"returns": {
"name": "int"
}
}
},
{
"location": {
"from": "L17:24",
"to": "L17:27"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L18:18",
"to": "L18:19"
},
"expr": {
"_type": "LiteralExpr",
"value": 0
},
"type": {
"name": "int"
}
},
{
"location": {
"from": "L18:38",
"to": "L18:39"
},
"expr": {
"_type": "LiteralExpr",
"value": 0
},
"type": {
"name": "int"
}
},
{
"location": {
"from": "L18:50",
"to": "L18:53"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L21:4",
"to": "L21:6"
},
"expr": {
"_type": "VariableExpr",
"name": "c1"
},
"type": {
"params": {
"pos": [],
"mixed": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
],
"kw": []
},
"returns": {}
}
},
{
"location": {
"from": "L22:4",
"to": "L22:6"
},
"expr": {
"_type": "VariableExpr",
"name": "c2"
},
"type": {
"params": {
"pos": [
{
"pos": 0,
"name": "p",
"type": {
"name": "float"
},
"required": false,
"unsupported": false
}
],
"mixed": [],
"kw": [
{
"pos": 1,
"name": "a",
"type": {
"name": "float"
},
"required": false,
"unsupported": false
}
]
},
"returns": {}
}
},
{
"location": {
"from": "L27:25",
"to": "L27:28"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L28:25",
"to": "L28:28"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L29:23",
"to": "L29:26"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L32:4",
"to": "L32:6"
},
"expr": {
"_type": "VariableExpr",
"name": "d1"
},
"type": {
"params": {
"pos": [],
"mixed": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
],
"kw": []
},
"returns": {
"name": "float"
}
}
},
{
"location": {
"from": "L33:4",
"to": "L33:6"
},
"expr": {
"_type": "VariableExpr",
"name": "d2"
},
"type": {
"params": {
"pos": [],
"mixed": [
{
"pos": 0,
"name": "a",
"type": {
"name": "str"
},
"required": true,
"unsupported": false
}
],
"kw": []
},
"returns": {
"name": "float"
}
}
},
{
"location": {
"from": "L34:4",
"to": "L34:6"
},
"expr": {
"_type": "VariableExpr",
"name": "d3"
},
"type": {
"params": {
"pos": [],
"mixed": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
],
"kw": []
},
"returns": {
"name": "str"
}
}
},
{
"location": {
"from": "L37:19",
"to": "L37:20"
},
"expr": {
"_type": "LiteralExpr",
"value": 0
},
"type": {
"name": "int"
}
},
{
"location": {
"from": "L37:31",
"to": "L37:34"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L38:27",
"to": "L38:30"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L39:19",
"to": "L39:20"
},
"expr": {
"_type": "LiteralExpr",
"value": 0
},
"type": {
"name": "int"
}
},
{
"location": {
"from": "L39:39",
"to": "L39:42"
},
"expr": {
"_type": "LiteralExpr",
"value": "..."
},
"type": {}
},
{
"location": {
"from": "L42:4",
"to": "L42:6"
},
"expr": {
"_type": "VariableExpr",
"name": "e1"
},
"type": {
"params": {
"pos": [],
"mixed": [],
"kw": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": false,
"unsupported": false
}
]
},
"returns": {}
}
},
{
"location": {
"from": "L43:4",
"to": "L43:6"
},
"expr": {
"_type": "VariableExpr",
"name": "e2"
},
"type": {
"params": {
"pos": [],
"mixed": [],
"kw": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
]
},
"returns": {}
}
},
{
"location": {
"from": "L44:4",
"to": "L44:6"
},
"expr": {
"_type": "VariableExpr",
"name": "e3"
},
"type": {
"params": {
"pos": [],
"mixed": [],
"kw": [
{
"pos": 0,
"name": "a",
"type": {
"name": "int"
},
"required": false,
"unsupported": false
},
{
"pos": 1,
"name": "b",
"type": {
"name": "int"
},
"required": true,
"unsupported": false
}
]
},
"returns": {}
}
}
]
}
+49
View File
@@ -0,0 +1,49 @@
# type: ignore
# ruff: disable[F821]
import module1
import module2 as alias2
from module3 import submodule3
from module4 import submodule4 as alias4
a: int
b: Generic[int]
c: Generic2[int, float]
d: Frame[a:int, b:float]
e = 3
f: int = 4
g = []
h = [1, 0.1, " ", None, False, True]
i = {}
j = {"a": 1, "b": 2}
k = {"c": 3, **j}
l = cast(int, a)
m = unsafe_cast(int, a)
def n(a: int, /, b: float, *, c: str) -> Any:
return
def o(a: int = 1, /, b: float = 2.0, *, c: str = "") -> Any:
return 1
for i in h:
pass
if e == f:
pass
elif f == g:
pass
else:
pass
p = +a + -b - ~c * d / e**f
q = not (a and b) or c
r = a & b | c ^ d
s = a.b.c
t = a[b][c, d][e:f]
u = a(b)(c=d)
@@ -0,0 +1,249 @@
Module(
body=[
Import(
names=[
alias(name='module1')]),
Import(
names=[
alias(name='module2', asname='alias2')]),
ImportFrom(
module='module3',
names=[
alias(name='submodule3')],
level=0),
ImportFrom(
module='module4',
names=[
alias(name='submodule4', asname='alias4')],
level=0),
Assign(
targets=[
Name(id='e')],
value=Constant(value=3)),
Assign(
targets=[
Name(id='f')],
value=Constant(value=4)),
Assign(
targets=[
Name(id='g')],
value=List(elts=[])),
Assign(
targets=[
Name(id='h')],
value=List(
elts=[
Constant(value=1),
Constant(value=0.1),
Constant(value=' '),
Constant(value=None),
Constant(value=False),
Constant(value=True)])),
Assign(
targets=[
Name(id='i')],
value=Dict(keys=[], values=[])),
Assign(
targets=[
Name(id='j')],
value=Dict(
keys=[
Constant(value='a'),
Constant(value='b')],
values=[
Constant(value=1),
Constant(value=2)])),
Assign(
targets=[
Name(id='k')],
value=Dict(
keys=[
Constant(value='c'),
None],
values=[
Constant(value=3),
Name(id='j')])),
Assign(
targets=[
Name(id='__midas_a0__')],
value=Name(id='a')),
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='__midas_a0__'),
Name(id='int')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='03_simple_syntax.py:L21:5: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a0__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=' to int')])),
Assign(
targets=[
Name(id='l')],
value=Name(id='__midas_a0__')),
Delete(
targets=[
Name(id='__midas_a0__')]),
Assign(
targets=[
Name(id='m')],
value=Name(id='a')),
FunctionDef(
name='n',
args=arguments(
posonlyargs=[
arg(arg='a')],
args=[
arg(arg='b')],
kwonlyargs=[
arg(arg='c')],
kw_defaults=[
None],
defaults=[]),
body=[
Return()],
decorator_list=[]),
FunctionDef(
name='o',
args=arguments(
posonlyargs=[
arg(arg='a')],
args=[
arg(arg='b')],
kwonlyargs=[
arg(arg='c')],
kw_defaults=[
Constant(value='')],
defaults=[
Constant(value=1),
Constant(value=2.0)]),
body=[
Return(
value=Constant(value=1))],
decorator_list=[]),
For(
target=Name(id='i'),
iter=Name(id='h'),
body=[
Pass()],
orelse=[]),
If(
test=Compare(
left=Name(id='e'),
ops=[
Eq()],
comparators=[
Name(id='f')]),
body=[
Pass()],
orelse=[
If(
test=Compare(
left=Name(id='f'),
ops=[
Eq()],
comparators=[
Name(id='g')]),
body=[
Pass()],
orelse=[])]),
Assign(
targets=[
Name(id='p')],
value=BinOp(
left=BinOp(
left=UnaryOp(
op=UAdd(),
operand=Name(id='a')),
op=Add(),
right=UnaryOp(
op=USub(),
operand=Name(id='b'))),
op=Sub(),
right=BinOp(
left=BinOp(
left=UnaryOp(
op=Invert(),
operand=Name(id='c')),
op=Mult(),
right=Name(id='d')),
op=Div(),
right=BinOp(
left=Name(id='e'),
op=Pow(),
right=Name(id='f'))))),
Assign(
targets=[
Name(id='q')],
value=BoolOp(
op=Or(),
values=[
UnaryOp(
op=Not(),
operand=BoolOp(
op=And(),
values=[
Name(id='a'),
Name(id='b')])),
Name(id='c')])),
Assign(
targets=[
Name(id='r')],
value=BinOp(
left=BinOp(
left=Name(id='a'),
op=BitAnd(),
right=Name(id='b')),
op=BitOr(),
right=BinOp(
left=Name(id='c'),
op=BitXor(),
right=Name(id='d')))),
Assign(
targets=[
Name(id='s')],
value=Attribute(
value=Attribute(
value=Name(id='a'),
attr='b'),
attr='c')),
Assign(
targets=[
Name(id='t')],
value=Subscript(
value=Subscript(
value=Subscript(
value=Name(id='a'),
slice=Name(id='b')),
slice=Tuple(
elts=[
Name(id='c'),
Name(id='d')])),
slice=Slice(
lower=Name(id='e'),
upper=Name(id='f')))),
Assign(
targets=[
Name(id='u')],
value=Call(
func=Call(
func=Name(id='a'),
args=[
Name(id='b')],
keywords=[]),
args=[],
keywords=[
keyword(
arg='c',
value=Name(id='d'))]))],
type_ignores=[])
+15
View File
@@ -0,0 +1,15 @@
predicate is_positive(v: float) = v >= 0
type Positive = float where is_positive(_)
alias T1 = Frame[
a: int
]
alias T2 = Frame[
a: int,
b: str,
c: Positive,
d: float where is_positive(_)
]
alias Positives = Column[Positive]
+13
View File
@@ -0,0 +1,13 @@
from typing import Any
from midas import T1, T2, Column, Positive, Positives, cast
o: Any = object()
df1 = cast(T1, o)
df2 = cast(T2, o)
df1 + df2
col1: Positives = df2["c"]
col2 = cast(Column[Positive], col1)
+731
View File
@@ -0,0 +1,731 @@
Module(
body=[
FunctionDef(
name='__midas_column_same_length__',
args=arguments(
posonlyargs=[],
args=[
arg(arg='column1'),
arg(arg='column2')],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Return(
value=Compare(
left=Call(
func=Name(id='len'),
args=[
Attribute(
value=Name(id='column1'),
attr='index')],
keywords=[]),
ops=[
Eq()],
comparators=[
Call(
func=Name(id='len'),
args=[
Attribute(
value=Name(id='column2'),
attr='index')],
keywords=[])]))],
decorator_list=[]),
FunctionDef(
name='__midas_frame_same_length__',
args=arguments(
posonlyargs=[],
args=[
arg(arg='frame1'),
arg(arg='frame2')],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Return(
value=Compare(
left=Call(
func=Name(id='len'),
args=[
Attribute(
value=Name(id='frame1'),
attr='index')],
keywords=[]),
ops=[
Eq()],
comparators=[
Call(
func=Name(id='len'),
args=[
Attribute(
value=Name(id='frame2'),
attr='index')],
keywords=[])]))],
decorator_list=[]),
FunctionDef(
name='__midas_is_column__',
args=arguments(
posonlyargs=[
arg(arg='obj')],
args=[],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Import(
names=[
alias(name='pandas', asname='pd')]),
Return(
value=Call(
func=Name(id='isinstance'),
args=[
Name(id='obj'),
Attribute(
value=Name(id='pd'),
attr='Series')],
keywords=[]))],
decorator_list=[],
returns=Name(id='bool')),
FunctionDef(
name='__midas_is_dataframe__',
args=arguments(
posonlyargs=[
arg(arg='obj')],
args=[],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Import(
names=[
alias(name='pandas', asname='pd')]),
Return(
value=Call(
func=Name(id='isinstance'),
args=[
Name(id='obj'),
Attribute(
value=Name(id='pd'),
attr='DataFrame')],
keywords=[]))],
decorator_list=[],
returns=Name(id='bool')),
FunctionDef(
name='__midas_is_positive__',
args=arguments(
posonlyargs=[],
args=[
arg(
arg='v',
annotation=Constant(value='float'))],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Return(
value=Compare(
left=Name(id='v'),
ops=[
GtE()],
comparators=[
Constant(value=0)]))],
decorator_list=[],
returns=Constant(value='bool')),
FunctionDef(
name='__midas_p0__',
args=arguments(
posonlyargs=[],
args=[
arg(
arg='_',
annotation=Constant(value='Any'))],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Return(
value=Call(
func=Name(id='__midas_is_positive__'),
args=[
Name(id='_')],
keywords=[]))],
decorator_list=[],
returns=Constant(value='bool')),
FunctionDef(
name='__midas_p1__',
args=arguments(
posonlyargs=[],
args=[
arg(
arg='_',
annotation=Constant(value='Any'))],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Return(
value=Call(
func=Name(id='__midas_is_positive__'),
args=[
Name(id='_')],
keywords=[]))],
decorator_list=[],
returns=Constant(value='bool')),
ImportFrom(
module='typing',
names=[
alias(name='Any')],
level=0),
ImportFrom(
module='midas',
names=[
alias(name='T1'),
alias(name='T2'),
alias(name='Column'),
alias(name='Positive'),
alias(name='Positives'),
alias(name='cast')],
level=0),
Assign(
targets=[
Name(id='o')],
value=Call(
func=Name(id='object'),
args=[],
keywords=[])),
Assign(
targets=[
Name(id='__midas_a0__')],
value=Name(id='o')),
Assert(
test=Call(
func=Name(id='__midas_is_dataframe__'),
args=[
Name(id='__midas_a0__')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L7:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a0__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=' to Frame[a: Column[int]]: Not a dataframe')])),
Assert(
test=Compare(
left=Constant(value='a'),
ops=[
In()],
comparators=[
Name(id='__midas_a0__')]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L7:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a0__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Frame[a: Column[int]]: Missing column 'a'")])),
Assert(
test=Call(
func=Name(id='__midas_is_column__'),
args=[
Subscript(
value=Name(id='__midas_a0__'),
slice=Constant(value='a'))],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L7:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Subscript(
value=Name(id='__midas_a0__'),
slice=Constant(value='a'))],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Column[int], in column 'a': Not a column")])),
For(
target=Name(id='value'),
iter=Subscript(
value=Name(id='__midas_a0__'),
slice=Constant(value='a')),
body=[
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='value'),
Name(id='int')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L7:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='value')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to int, in column 'a'")]))],
orelse=[]),
Assign(
targets=[
Name(id='df1')],
value=Name(id='__midas_a0__')),
Delete(
targets=[
Name(id='__midas_a0__')]),
Assign(
targets=[
Name(id='__midas_a1__')],
value=Name(id='o')),
Assert(
test=Call(
func=Name(id='__midas_is_dataframe__'),
args=[
Name(id='__midas_a1__')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a1__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=' to Frame[a: Column[int], b: Column[str], c: Column[Positive], d: Column[float where is_positive(_)]]: Not a dataframe')])),
Assert(
test=Compare(
left=Constant(value='a'),
ops=[
In()],
comparators=[
Name(id='__midas_a1__')]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a1__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Frame[a: Column[int], b: Column[str], c: Column[Positive], d: Column[float where is_positive(_)]]: Missing column 'a'")])),
Assert(
test=Call(
func=Name(id='__midas_is_column__'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='a'))],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='a'))],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Column[int], in column 'a': Not a column")])),
For(
target=Name(id='value'),
iter=Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='a')),
body=[
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='value'),
Name(id='int')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='value')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to int, in column 'a'")]))],
orelse=[]),
Assert(
test=Compare(
left=Constant(value='b'),
ops=[
In()],
comparators=[
Name(id='__midas_a1__')]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a1__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Frame[a: Column[int], b: Column[str], c: Column[Positive], d: Column[float where is_positive(_)]]: Missing column 'b'")])),
Assert(
test=Call(
func=Name(id='__midas_is_column__'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='b'))],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='b'))],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Column[str], in column 'b': Not a column")])),
For(
target=Name(id='value'),
iter=Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='b')),
body=[
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='value'),
Name(id='str')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='value')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to str, in column 'b'")]))],
orelse=[]),
Assert(
test=Compare(
left=Constant(value='c'),
ops=[
In()],
comparators=[
Name(id='__midas_a1__')]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a1__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Frame[a: Column[int], b: Column[str], c: Column[Positive], d: Column[float where is_positive(_)]]: Missing column 'c'")])),
Assert(
test=Call(
func=Name(id='__midas_is_column__'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='c'))],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='c'))],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Column[Positive], in column 'c': Not a column")])),
For(
target=Name(id='value'),
iter=Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='c')),
body=[
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='value'),
Name(id='float')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='value')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to float, in column 'c'")])),
Assert(
test=Call(
func=Name(id='__midas_p0__'),
args=[
Name(id='value')],
keywords=[]),
msg=Constant(value="04_frames.py:L8:7: ConstraintError: Value does not fit constraint 'is_positive(_)', in column 'c'"))],
orelse=[]),
Assert(
test=Compare(
left=Constant(value='d'),
ops=[
In()],
comparators=[
Name(id='__midas_a1__')]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a1__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Frame[a: Column[int], b: Column[str], c: Column[Positive], d: Column[float where is_positive(_)]]: Missing column 'd'")])),
Assert(
test=Call(
func=Name(id='__midas_is_column__'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='d'))],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='d'))],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to Column[float where is_positive(_)], in column 'd': Not a column")])),
For(
target=Name(id='value'),
iter=Subscript(
value=Name(id='__midas_a1__'),
slice=Constant(value='d')),
body=[
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='value'),
Name(id='float')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L8:7: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='value')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=" to float, in column 'd'")])),
Assert(
test=Call(
func=Name(id='__midas_p1__'),
args=[
Name(id='value')],
keywords=[]),
msg=Constant(value="04_frames.py:L8:7: ConstraintError: Value does not fit constraint 'is_positive(_)', in column 'd'"))],
orelse=[]),
Assign(
targets=[
Name(id='df2')],
value=Name(id='__midas_a1__')),
Delete(
targets=[
Name(id='__midas_a1__')]),
Assign(
targets=[
Name(id='__midas_a2__')],
value=Name(id='df1')),
Assign(
targets=[
Name(id='__midas_a3__')],
value=Name(id='df2')),
Assert(
test=Call(
func=Name(id='__midas_column_same_length__'),
args=[
Name(id='__midas_a2__'),
Name(id='__midas_a3__')],
keywords=[]),
msg=Constant(value='04_frames.py:L10:1: AssertionError: Columns must have the same length')),
Assign(
targets=[
Name(id='__midas_a4__')],
value=Name(id='__midas_a2__')),
Assign(
targets=[
Name(id='__midas_a5__')],
value=Name(id='__midas_a3__')),
Assert(
test=Call(
func=Name(id='__midas_frame_same_length__'),
args=[
Name(id='__midas_a4__'),
Name(id='__midas_a5__')],
keywords=[]),
msg=Constant(value='04_frames.py:L10:1: AssertionError: DataFrames must have the same length')),
Expr(
value=BinOp(
left=Name(id='__midas_a2__'),
op=Add(),
right=Name(id='__midas_a3__'))),
Delete(
targets=[
Name(id='__midas_a2__'),
Name(id='__midas_a3__'),
Name(id='__midas_a4__'),
Name(id='__midas_a5__')]),
Assign(
targets=[
Name(id='col1')],
value=Subscript(
value=Name(id='df2'),
slice=Constant(value='c'))),
Assign(
targets=[
Name(id='__midas_a6__')],
value=Name(id='col1')),
Assert(
test=Call(
func=Name(id='__midas_is_column__'),
args=[
Name(id='__midas_a6__')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L13:8: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='__midas_a6__')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=' to Column[Positive]: Not a column')])),
For(
target=Name(id='value'),
iter=Name(id='__midas_a6__'),
body=[
Assert(
test=Call(
func=Name(id='isinstance'),
args=[
Name(id='value'),
Name(id='float')],
keywords=[]),
msg=JoinedStr(
values=[
Constant(value='04_frames.py:L13:8: CastError: Cannot cast '),
FormattedValue(
value=Attribute(
value=Call(
func=Name(id='type'),
args=[
Name(id='value')],
keywords=[]),
attr='__name__'),
conversion=-1),
Constant(value=' to float')])),
Assert(
test=Call(
func=Name(id='__midas_p0__'),
args=[
Name(id='value')],
keywords=[]),
msg=Constant(value="04_frames.py:L13:8: ConstraintError: Value does not fit constraint 'is_positive(_)'"))],
orelse=[]),
Assign(
targets=[
Name(id='col2')],
value=Name(id='__midas_a6__')),
Delete(
targets=[
Name(id='__midas_a6__')])],
type_ignores=[])
@@ -2,8 +2,8 @@
type Custom = float
// Simple custom types with constraints
type Latitude = float where (-90 <= _ <= 90)
type Longitude = float where (-180 <= _ <= 180)
type Latitude = float where (-90 <= _ & _ <= 90)
type Longitude = float where (-180 <= _ & _ <= 180)
// Generic custom type (a Difference of T is derived from T, e.g. a difference of floats is a float
type Difference[T] = T
@@ -54,10 +54,10 @@ extend Person {
prop name: str
// Property with an inline constraint
prop age: Optional[int where (0 <= _ < 150)]
prop age: int where (0 <= _ & _ < 150)
// Property referencing a predicate
prop height: float where StrictlyPositive
prop height: float where StrictlyPositive(_)
prop home: GeoLocation
}
@@ -187,8 +187,8 @@
"column": 38
},
{
"type": "LESS_EQUAL",
"lexeme": "<=",
"type": "AND",
"lexeme": "&",
"line": 5,
"column": 39
},
@@ -196,25 +196,49 @@
"type": "WHITESPACE",
"lexeme": " ",
"line": 5,
"column": 40
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 5,
"column": 41
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 5,
"column": 42
},
{
"type": "LESS_EQUAL",
"lexeme": "<=",
"line": 5,
"column": 43
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 5,
"column": 45
},
{
"type": "NUMBER",
"lexeme": "90",
"line": 5,
"column": 42
"column": 46
},
{
"type": "RIGHT_PAREN",
"lexeme": ")",
"line": 5,
"column": 44
"column": 48
},
{
"type": "NEWLINE",
"lexeme": "\n",
"line": 5,
"column": 45
"column": 49
},
{
"type": "TYPE",
@@ -325,8 +349,8 @@
"column": 40
},
{
"type": "LESS_EQUAL",
"lexeme": "<=",
"type": "AND",
"lexeme": "&",
"line": 6,
"column": 41
},
@@ -334,25 +358,49 @@
"type": "WHITESPACE",
"lexeme": " ",
"line": 6,
"column": 42
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 6,
"column": 43
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 6,
"column": 44
},
{
"type": "LESS_EQUAL",
"lexeme": "<=",
"line": 6,
"column": 45
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 6,
"column": 47
},
{
"type": "NUMBER",
"lexeme": "180",
"line": 6,
"column": 44
"column": 48
},
{
"type": "RIGHT_PAREN",
"lexeme": ")",
"line": 6,
"column": 47
"column": 51
},
{
"type": "NEWLINE",
"lexeme": "\n",
"line": 6,
"column": 48
"column": 52
},
{
"type": "NEWLINE",
@@ -2240,22 +2288,40 @@
},
{
"type": "IDENTIFIER",
"lexeme": "Optional",
"lexeme": "int",
"line": 57,
"column": 15
},
{
"type": "LEFT_BRACKET",
"lexeme": "[",
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 23
"column": 18
},
{
"type": "IDENTIFIER",
"lexeme": "int",
"type": "WHERE",
"lexeme": "where",
"line": 57,
"column": 19
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 24
},
{
"type": "LEFT_PAREN",
"lexeme": "(",
"line": 57,
"column": 25
},
{
"type": "NUMBER",
"lexeme": "0",
"line": 57,
"column": 26
},
{
"type": "WHITESPACE",
"lexeme": " ",
@@ -2263,8 +2329,8 @@
"column": 27
},
{
"type": "WHERE",
"lexeme": "where",
"type": "LESS_EQUAL",
"lexeme": "<=",
"line": 57,
"column": 28
},
@@ -2272,17 +2338,35 @@
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 30
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 57,
"column": 31
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 32
},
{
"type": "AND",
"lexeme": "&",
"line": 57,
"column": 33
},
{
"type": "LEFT_PAREN",
"lexeme": "(",
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 34
},
{
"type": "NUMBER",
"lexeme": "0",
"type": "UNDERSCORE",
"lexeme": "_",
"line": 57,
"column": 35
},
@@ -2293,8 +2377,8 @@
"column": 36
},
{
"type": "LESS_EQUAL",
"lexeme": "<=",
"type": "LESS",
"lexeme": "<",
"line": 57,
"column": 37
},
@@ -2302,55 +2386,25 @@
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 39
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 57,
"column": 40
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 41
},
{
"type": "LESS",
"lexeme": "<",
"line": 57,
"column": 42
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 57,
"column": 43
"column": 38
},
{
"type": "NUMBER",
"lexeme": "150",
"line": 57,
"column": 44
"column": 39
},
{
"type": "RIGHT_PAREN",
"lexeme": ")",
"line": 57,
"column": 47
},
{
"type": "RIGHT_BRACKET",
"lexeme": "]",
"line": 57,
"column": 48
"column": 42
},
{
"type": "NEWLINE",
"lexeme": "\n",
"line": 57,
"column": 49
"column": 43
},
{
"type": "NEWLINE",
@@ -2442,11 +2496,29 @@
"line": 60,
"column": 30
},
{
"type": "LEFT_PAREN",
"lexeme": "(",
"line": 60,
"column": 46
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 60,
"column": 47
},
{
"type": "RIGHT_PAREN",
"lexeme": ")",
"line": 60,
"column": 48
},
{
"type": "NEWLINE",
"lexeme": "\n",
"line": 60,
"column": 46
"column": 49
},
{
"type": "NEWLINE",
@@ -2544,7 +2616,7 @@
"constraint": {
"_type": "GroupingExpr",
"expr": {
"_type": "BinaryExpr",
"_type": "LogicalExpr",
"left": {
"_type": "BinaryExpr",
"left": {
@@ -2560,10 +2632,17 @@
"_type": "WildcardExpr"
}
},
"operator": "<=",
"operator": "&",
"right": {
"_type": "LiteralExpr",
"value": 90
"_type": "BinaryExpr",
"left": {
"_type": "WildcardExpr"
},
"operator": "<=",
"right": {
"_type": "LiteralExpr",
"value": 90
}
}
}
}
@@ -2582,7 +2661,7 @@
"constraint": {
"_type": "GroupingExpr",
"expr": {
"_type": "BinaryExpr",
"_type": "LogicalExpr",
"left": {
"_type": "BinaryExpr",
"left": {
@@ -2598,10 +2677,17 @@
"_type": "WildcardExpr"
}
},
"operator": "<=",
"operator": "&",
"right": {
"_type": "LiteralExpr",
"value": 180
"_type": "BinaryExpr",
"left": {
"_type": "WildcardExpr"
},
"operator": "<=",
"right": {
"_type": "LiteralExpr",
"value": 180
}
}
}
}
@@ -3013,42 +3099,40 @@
"kind": "PROPERTY",
"name": "age",
"type": {
"_type": "GenericType",
"_type": "ConstraintType",
"type": {
"_type": "NamedType",
"name": "Optional"
"name": "int"
},
"args": [
{
"_type": "ConstraintType",
"type": {
"_type": "NamedType",
"name": "int"
"constraint": {
"_type": "GroupingExpr",
"expr": {
"_type": "LogicalExpr",
"left": {
"_type": "BinaryExpr",
"left": {
"_type": "LiteralExpr",
"value": 0
},
"operator": "<=",
"right": {
"_type": "WildcardExpr"
}
},
"constraint": {
"_type": "GroupingExpr",
"expr": {
"_type": "BinaryExpr",
"left": {
"_type": "BinaryExpr",
"left": {
"_type": "LiteralExpr",
"value": 0
},
"operator": "<=",
"right": {
"_type": "WildcardExpr"
}
},
"operator": "<",
"right": {
"_type": "LiteralExpr",
"value": 150
}
"operator": "&",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "WildcardExpr"
},
"operator": "<",
"right": {
"_type": "LiteralExpr",
"value": 150
}
}
}
]
}
}
},
{
@@ -3062,8 +3146,17 @@
"name": "float"
},
"constraint": {
"_type": "VariableExpr",
"name": "StrictlyPositive"
"_type": "CallExpr",
"callee": {
"_type": "VariableExpr",
"name": "StrictlyPositive"
},
"arguments": [
{
"_type": "WildcardExpr"
}
],
"keywords": {}
}
}
},
@@ -0,0 +1,35 @@
// Alias declaration
alias A = object
// Type declaration
type B = object
// Generic declaration
type C[T] = object
type D[T <: A] = object
type E[T, U] = object
// Type expressions
type F[T] = T
type G = A where predicate(_)
type H = A where _ > 0 & _.attr < 1.0 & +_ + 4.0 >= "string" & !(-_ - 4.0 <= 0 & _ == none & _ != false)
type I = fn() -> Any
type J = fn(a: int, /, b: float, *, c: bool) -> Any
type K = fn(a: int, /, b: float, *, c: bool?) -> Any
type L = fn(a: int, /, b: float?, *, c: bool?) -> Any
type M = fn(a: int?, /, b: float?, *, c: bool?) -> Any
// Extend
extend N {}
extend O {
prop a: int
def b: fn(int, /) -> int
def b: fn(float, /) -> float
}
// Predicate
predicate P = true
predicate Q(v: float) = v > 0
predicate R(a: float, b: float)(v: float) = a < v & v < b
predicate S = R(0.0, 1.0)
predicate T = R(a=0.0, b=1.0)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
# type: ignore
# ruff: disable[F821]
import module1
import module2 as alias2
from module3 import submodule3
from module4 import submodule4 as alias4
a: int
b: Generic[int]
c: Generic2[int, float]
d: Frame[a:int, b:float]
e = 3
f: int = 4
g = []
h = [1, 0.1, " ", None, False, True]
i = {}
j = {"a": 1, "b": 2}
k = {"c": 3, **j}
l = cast(int, a)
m = unsafe_cast(int, a)
def n(a: int, /, b: float, *, c: str) -> Any:
return
def o(a: int = 1, /, b: float = 2.0, *, c: str = "") -> Any:
return 1
for i in h:
pass
if e == f:
pass
elif f == g:
pass
else:
pass
p = +a + -b - ~c * d / e**f
q = not (a and b) or c
r = a & b | c ^ d
s = a.b.c
t = a[b][c, d][e:f]
u = a(b)(c=d)
@@ -0,0 +1,725 @@
{
"stmts": [
{
"_type": "ImportStmt",
"imports": [
{
"_type": "ImportAlias",
"name": "module1",
"alias": null
}
]
},
{
"_type": "ImportStmt",
"imports": [
{
"_type": "ImportAlias",
"name": "module2",
"alias": "alias2"
}
]
},
{
"_type": "FromImportStmt",
"module": "module3",
"imports": [
{
"_type": "ImportAlias",
"name": "submodule3",
"alias": null
}
],
"level": 0
},
{
"_type": "FromImportStmt",
"module": "module4",
"imports": [
{
"_type": "ImportAlias",
"name": "submodule4",
"alias": "alias4"
}
],
"level": 0
},
{
"_type": "TypeAssign",
"name": "a",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
}
},
{
"_type": "TypeAssign",
"name": "b",
"type": {
"_type": "BaseType",
"base": "Generic",
"args": [
{
"_type": "BaseType",
"base": "int",
"args": []
}
]
}
},
{
"_type": "TypeAssign",
"name": "c",
"type": {
"_type": "BaseType",
"base": "Generic2",
"args": [
{
"_type": "BaseType",
"base": "int",
"args": []
},
{
"_type": "BaseType",
"base": "float",
"args": []
}
]
}
},
{
"_type": "TypeAssign",
"name": "d",
"type": {
"_type": "FrameType",
"columns": [
{
"_type": "FrameColumn",
"name": "a",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
}
},
{
"_type": "FrameColumn",
"name": "b",
"type": {
"_type": "BaseType",
"base": "float",
"args": []
}
}
]
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "e"
}
],
"value": {
"_type": "LiteralExpr",
"value": 3
}
},
{
"_type": "TypeAssign",
"name": "f",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "f"
}
],
"value": {
"_type": "LiteralExpr",
"value": 4
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "g"
}
],
"value": {
"_type": "ListExpr",
"items": []
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "h"
}
],
"value": {
"_type": "ListExpr",
"items": [
{
"_type": "LiteralExpr",
"value": 1
},
{
"_type": "LiteralExpr",
"value": 0.1
},
{
"_type": "LiteralExpr",
"value": " "
},
{
"_type": "LiteralExpr",
"value": null
},
{
"_type": "LiteralExpr",
"value": false
},
{
"_type": "LiteralExpr",
"value": true
}
]
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "i"
}
],
"value": {
"_type": "DictExpr",
"keys": [],
"values": []
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "j"
}
],
"value": {
"_type": "DictExpr",
"keys": [
{
"_type": "LiteralExpr",
"value": "a"
},
{
"_type": "LiteralExpr",
"value": "b"
}
],
"values": [
{
"_type": "LiteralExpr",
"value": 1
},
{
"_type": "LiteralExpr",
"value": 2
}
]
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "k"
}
],
"value": {
"_type": "DictExpr",
"keys": [
{
"_type": "LiteralExpr",
"value": "c"
},
null
],
"values": [
{
"_type": "LiteralExpr",
"value": 3
},
{
"_type": "VariableExpr",
"name": "j"
}
]
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "l"
}
],
"value": {
"_type": "CastExpr",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
},
"expr": {
"_type": "VariableExpr",
"name": "a"
},
"unsafe": false
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "m"
}
],
"value": {
"_type": "CastExpr",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
},
"expr": {
"_type": "VariableExpr",
"name": "a"
},
"unsafe": true
}
},
{
"_type": "Function",
"name": "n",
"params": {
"_type": "ParamSpec",
"pos": [
{
"name": "a",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
},
"default": null
}
],
"mixed": [
{
"name": "b",
"type": {
"_type": "BaseType",
"base": "float",
"args": []
},
"default": null
}
],
"kw": [
{
"name": "c",
"type": {
"_type": "BaseType",
"base": "str",
"args": []
},
"default": null
}
]
},
"returns": {
"_type": "BaseType",
"base": "Any",
"args": []
},
"body": [
{
"_type": "ReturnStmt",
"value": null
}
]
},
{
"_type": "Function",
"name": "o",
"params": {
"_type": "ParamSpec",
"pos": [
{
"name": "a",
"type": {
"_type": "BaseType",
"base": "int",
"args": []
},
"default": {
"_type": "LiteralExpr",
"value": 1
}
}
],
"mixed": [
{
"name": "b",
"type": {
"_type": "BaseType",
"base": "float",
"args": []
},
"default": {
"_type": "LiteralExpr",
"value": 2.0
}
}
],
"kw": [
{
"name": "c",
"type": {
"_type": "BaseType",
"base": "str",
"args": []
},
"default": {
"_type": "LiteralExpr",
"value": ""
}
}
]
},
"returns": {
"_type": "BaseType",
"base": "Any",
"args": []
},
"body": [
{
"_type": "ReturnStmt",
"value": {
"_type": "LiteralExpr",
"value": 1
}
}
]
},
{
"_type": "ForStmt",
"target": {
"_type": "VariableExpr",
"name": "i"
},
"iterator": {
"_type": "VariableExpr",
"name": "h"
},
"body": []
},
{
"_type": "IfStmt",
"test": {
"_type": "CompareExpr",
"left": {
"_type": "VariableExpr",
"name": "e"
},
"operator": "==",
"right": {
"_type": "VariableExpr",
"name": "f"
}
},
"body": [],
"orelse": [
{
"_type": "IfStmt",
"test": {
"_type": "CompareExpr",
"left": {
"_type": "VariableExpr",
"name": "f"
},
"operator": "==",
"right": {
"_type": "VariableExpr",
"name": "g"
}
},
"body": [],
"orelse": []
}
]
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "p"
}
],
"value": {
"_type": "BinaryExpr",
"left": {
"_type": "BinaryExpr",
"left": {
"_type": "UnaryExpr",
"operator": "+",
"right": {
"_type": "VariableExpr",
"name": "a"
}
},
"operator": "+",
"right": {
"_type": "UnaryExpr",
"operator": "-",
"right": {
"_type": "VariableExpr",
"name": "b"
}
}
},
"operator": "-",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "BinaryExpr",
"left": {
"_type": "UnaryExpr",
"operator": "~",
"right": {
"_type": "VariableExpr",
"name": "c"
}
},
"operator": "*",
"right": {
"_type": "VariableExpr",
"name": "d"
}
},
"operator": "/",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "VariableExpr",
"name": "e"
},
"operator": "**",
"right": {
"_type": "VariableExpr",
"name": "f"
}
}
}
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "q"
}
],
"value": {
"_type": "LogicalExpr",
"left": {
"_type": "UnaryExpr",
"operator": "not",
"right": {
"_type": "LogicalExpr",
"left": {
"_type": "VariableExpr",
"name": "a"
},
"operator": "and",
"right": {
"_type": "VariableExpr",
"name": "b"
}
}
},
"operator": "or",
"right": {
"_type": "VariableExpr",
"name": "c"
}
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "r"
}
],
"value": {
"_type": "BinaryExpr",
"left": {
"_type": "BinaryExpr",
"left": {
"_type": "VariableExpr",
"name": "a"
},
"operator": "&",
"right": {
"_type": "VariableExpr",
"name": "b"
}
},
"operator": "|",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "VariableExpr",
"name": "c"
},
"operator": "^",
"right": {
"_type": "VariableExpr",
"name": "d"
}
}
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "s"
}
],
"value": {
"_type": "GetExpr",
"object": {
"_type": "GetExpr",
"object": {
"_type": "VariableExpr",
"name": "a"
},
"name": "b"
},
"name": "c"
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "t"
}
],
"value": {
"_type": "SubscriptExpr",
"object": {
"_type": "SubscriptExpr",
"object": {
"_type": "SubscriptExpr",
"object": {
"_type": "VariableExpr",
"name": "a"
},
"index": {
"_type": "VariableExpr",
"name": "b"
}
},
"index": {
"_type": "TupleExpr",
"items": [
{
"_type": "VariableExpr",
"name": "c"
},
{
"_type": "VariableExpr",
"name": "d"
}
]
}
},
"index": {
"_type": "SliceExpr",
"lower": {
"_type": "VariableExpr",
"name": "e"
},
"upper": {
"_type": "VariableExpr",
"name": "f"
},
"step": null
}
}
},
{
"_type": "AssignStmt",
"targets": [
{
"_type": "VariableExpr",
"name": "u"
}
],
"value": {
"_type": "CallExpr",
"callee": {
"_type": "CallExpr",
"callee": {
"_type": "VariableExpr",
"name": "a"
},
"arguments": [
{
"_type": "VariableExpr",
"name": "b"
}
],
"keywords": {}
},
"arguments": [],
"keywords": {
"c": {
"_type": "VariableExpr",
"name": "d"
}
}
}
}
]
}
+15 -1
View File
@@ -1,3 +1,4 @@
import ast
import json
from dataclasses import asdict, dataclass, field
from pathlib import Path
@@ -6,17 +7,30 @@ import midas.ast.python as p
from midas.checker.checker import TypeChecker
from midas.checker.diagnostic import Diagnostic
from midas.checker.types import Type
from midas.lexer.token import TokenType
from tests.base import Tester
from tests.serializer.python import PythonAstJsonSerializer
class CustomEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ast.AST):
return ast.dump(o)
if isinstance(o, TokenType):
return o.name
if o == ...:
return "..."
return super().default(o)
@dataclass
class CaseResult:
diagnostics: list[dict] = field(default_factory=list)
judgments: list = field(default_factory=list)
def dumps(self) -> str:
return json.dumps(asdict(self), indent=2)
return json.dumps(asdict(self), indent=2, cls=CustomEncoder)
class CheckerTester(Tester):
+5 -5
View File
@@ -3,7 +3,6 @@ from dataclasses import dataclass
from pathlib import Path
from midas.checker.checker import TypeChecker
from midas.checker.diagnostic import DiagnosticType
from midas.generator.generator import Generator
from midas.utils import TypedAST
from tests.base import Tester
@@ -44,10 +43,11 @@ class GeneratorTester(Tester):
typed_ast: TypedAST = checker.type_check(path)
if not any(d.type == DiagnosticType.ERROR for d in checker.diagnostics):
generator = Generator(workdir=path.parent, types=checker.types)
generator.set_src_path(path)
result.compiled_ast = generator.generate_ast(typed_ast)
# Ignore errors and generate anyway, easier here and errors should be
# covered by checker tests
generator = Generator(workdir=path.parent, types=checker.types)
generator.set_src_path(path)
result.compiled_ast = generator.generate_ast(typed_ast)
return result
+2 -1
View File
@@ -1,4 +1,4 @@
from typing import Optional, Sequence
from typing import Optional, Sequence, final
from midas.ast.midas import (
AliasStmt,
@@ -28,6 +28,7 @@ from midas.ast.midas import (
)
@final
class MidasAstJsonSerializer(
Stmt.Visitor[dict], Expr.Visitor[dict], Type.Visitor[dict]
):
+2 -1
View File
@@ -1,5 +1,5 @@
import ast
from typing import Optional, Sequence, Type
from typing import Optional, Sequence, Type, final
from midas.ast.python import (
AssignStmt,
@@ -78,6 +78,7 @@ boolean_ops: dict[Type[ast.boolop], str] = {
}
@final
class PythonAstJsonSerializer(
Stmt.Visitor[dict], Expr.Visitor[dict], MidasType.Visitor[dict]
):