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> <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* 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. *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 c = "invalid" # -> can't assign str to int variable
d = True d = True
e = d + d e = d + d # -> addition not defined between booleans
f: float = a f: float = a
@@ -1,6 +1,7 @@
# type: ignore # type: ignore
# ruff: disable [F821] # ruff: disable [F821]
distance: Meter = cast(Meter, 123.45) distance = cast(Meter, 123.45)
time: Second = cast(Second, 6.7) time = cast(Second, 6.7)
speed = distance / time speed = distance / time
print(speed)
@@ -1,3 +1,5 @@
# Return types must have a LUB
# Valid
def minimum(x: int, y: int): def minimum(x: int, y: int):
if x < y: if x < y:
return x return x
@@ -5,18 +7,28 @@ def minimum(x: int, y: int):
return y return y
# Invalid
def func(a: int):
if a < 5:
return "Oops"
return True
a = 15 a = 15
b = 72 b = 72
c = minimum(a, b) c = minimum(a, b)
# Recursive but typable thanks to return hint
def factorial(n: int) -> int: def factorial(n: int) -> int:
if n <= 1: if n <= 1:
return 1 return 1
return n * factorial(n - 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: def foo() -> None:
@@ -15,7 +15,9 @@ extend Coordinate {
type Difference[T <: float] = T type Difference[T <: float] = T
type MeterDifference = Difference[Meter] type MeterDifference = Difference[Meter]
type CompDiff[T <: float] = { type CompDiff[T <: float] = object
extend CompDiff[T <: float] {
prop d1: Difference[T] prop d1: Difference[T]
prop d2: Difference[T] prop d2: Difference[T]
} }
@@ -1,20 +1,20 @@
# type: ignore # type: ignore
# ruff: disable [F821] # ruff: disable [F821]
p1: Coordinate p1 = cast(Coordinate, object())
p2: Coordinate p2 = cast(Coordinate, object())
diff_x = p2.x - p1.x diff_x = p2.x - p1.x
diff_y = p2.y - p1.y diff_y = p2.y - p1.y
dist = diff_x + diff_y dist = diff_x + diff_y
p2.x += cast(Meter, 1) p2.x += cast(Meter, 1.0)
p2.y = True # invalid, wrong type p2.y = True # invalid, wrong type
p2.z = 3 # invalid, no property 'z' on Coordinate p2.z = 3 # invalid, no property 'z' on Coordinate
p2.x.a = 3 # invalid, no properties on Meter p2.x.a = 3 # invalid, no properties on Meter
foo: list[float] = [] foo = cast(list[float], [])
append = foo.append append = foo.append
@@ -23,7 +23,7 @@ foo.append(2)
append(True) # invalid, must be float append(True) # invalid, must be float
append(2) append(2)
bar: list[list[Meter]] bar = cast(list[list[Meter]], [])
bar.append([p2.x]) bar.append([p2.x])
@@ -1,9 +1,9 @@
# type: ignore # type: ignore
# ruff: disable [F821] # ruff: disable [F821]
foo: Foo foo = cast(Foo, object())
t1: T1 t1 = cast(T1, object())
t2: T2 t2 = cast(T2, object())
a = foo.bar(t1) a = foo.bar(t1)
b = foo.bar(t2) b = foo.bar(t2)
@@ -13,6 +13,6 @@ func = foo.bar
c = func(t1) c = func(t1)
d = func(t2) d = func(t2)
t2b: T2b t2b = cast(T2b, object())
e = foo.bar(t2b) 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 import midas.ast.midas as m
@final
class MidasPrinter( class MidasPrinter(
m.Expr.Visitor[str], m.Expr.Visitor[str],
m.Stmt.Visitor[str], m.Stmt.Visitor[str],
+3
View File
@@ -1,7 +1,10 @@
from typing import final
import midas.ast.midas as m import midas.ast.midas as m
from midas.ast.printer.base import AstPrinter from midas.ast.printer.base import AstPrinter
@final
class MidasAstPrinter( class MidasAstPrinter(
AstPrinter, AstPrinter,
m.Expr.Visitor[None], m.Expr.Visitor[None],
+20
View File
@@ -1,9 +1,11 @@
import ast import ast
from typing import final
import midas.ast.python as p import midas.ast.python as p
from midas.ast.printer.base import AstPrinter from midas.ast.printer.base import AstPrinter
@final
class PythonAstPrinter( class PythonAstPrinter(
AstPrinter, AstPrinter,
p.MidasType.Visitor[None], p.MidasType.Visitor[None],
@@ -115,6 +117,24 @@ class PythonAstPrinter(
stmt.iterator.accept(self) stmt.iterator.accept(self)
self._write_sequence("body", stmt.body, last=True) 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: def visit_raw_stmt(self, stmt: p.RawStmt) -> None:
self._write_line("RawStmt") self._write_line("RawStmt")
with self._child_level(single=True): with self._child_level(single=True):
+2 -1
View File
@@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Callable, Optional from typing import Any, Callable, Optional, final
import midas.ast.midas as m import midas.ast.midas as m
from midas.ast.location import Location from midas.ast.location import Location
@@ -18,6 +18,7 @@ class PartialPredicate(Predicate):
"""A dictionary of already applied parameters""" """A dictionary of already applied parameters"""
@final
class Evaluator(m.Expr.Visitor[Any]): class Evaluator(m.Expr.Visitor[Any]):
"""Helper class to evaluate an expression """Helper class to evaluate an expression
+14 -10
View File
@@ -1,6 +1,6 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, final
import midas.ast.midas as m import midas.ast.midas as m
from midas.ast.location import Location from midas.ast.location import Location
@@ -30,6 +30,7 @@ from midas.lexer.token import Token, TokenType
from midas.parser.midas import MidasParser from midas.parser.midas import MidasParser
@final
class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type]): 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""" """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._local_variables[name]
return self.types.get_type(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 """Get the type of a variable
This function will first look into the current predicate's parameters if 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) The the variable is looked up in the preamble (i.e. global environment)
Args: Args:
location (Location): the location of the variable reference
name (str): the name of the variable name (str): the name of the variable
Raises:
NameError: if the variable cannot be found
Returns: Returns:
Type: the type of the variable 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: if global_ is not None:
return global_ 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]): def resolve(self, stmts: list[m.Stmt]):
"""Process a sequence of statements """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 return result.result
def visit_unary_expr(self, expr: m.UnaryExpr) -> Type: 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 # Special case because there is no __not__ dunder method
match expr.operator: match expr.operator:
case Token(type=TokenType.BANG): 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() return UnknownType()
operand: Type = self.type_of(expr.right)
operation: Optional[Type] = self.types.lookup_member(operand, method) operation: Optional[Type] = self.types.lookup_member(operand, method)
if operation is None: if operation is None:
self.reporter.error( self.reporter.error(
@@ -350,7 +352,7 @@ class MidasTyper(m.Stmt.Visitor[None], m.Expr.Visitor[Type], m.Type.Visitor[Type
return member return member
def visit_variable_expr(self, expr: m.VariableExpr) -> Type: 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: def visit_grouping_expr(self, expr: m.GroupingExpr) -> Type:
return expr.expr.accept(self) 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") return self.types.get_type("float")
case str(): case str():
return self.types.get_type("str") return self.types.get_type("str")
case None:
return self.types.get_type("None")
case _: case _:
self.reporter.warning(expr.location, f"Unknown literal {expr}") self.reporter.warning(expr.location, f"Unknown literal {expr}")
return UnknownType() return UnknownType()
def visit_wildcard_expr(self, expr: m.WildcardExpr) -> Type: 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: def visit_named_type(self, type: m.NamedType) -> Type:
name: str = type.name.lexeme 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 = {} self._predicate_params = {}
if not self.types.is_subtype(constraint_type, self._bool): if not self.types.is_subtype(constraint_type, self._bool):
self.reporter.error( self.reporter.error(
type.location, type.constraint.location,
f"Constraint must evaluate to a boolean, got {constraint_type}", f"Constraint must evaluate to a boolean, got {constraint_type}",
) )
+2 -1
View File
@@ -1,6 +1,6 @@
import ast import ast
import logging import logging
from typing import Any, Optional from typing import Any, Optional, final
import midas.ast.python as p import midas.ast.python as p
from midas.ast.location import Location from midas.ast.location import Location
@@ -55,6 +55,7 @@ class UndefinedMethodException(Exception):
pass pass
@final
class PythonTyper( class PythonTyper(
p.Stmt.Visitor[None], p.Stmt.Visitor[None],
p.Expr.Visitor[Type], p.Expr.Visitor[Type],
+3
View File
@@ -1,3 +1,5 @@
from typing import final
import midas.ast.python as p import midas.ast.python as p
from midas.ast.location import Location from midas.ast.location import Location
from midas.checker.reporter import FileReporter from midas.checker.reporter import FileReporter
@@ -6,6 +8,7 @@ from midas.checker.reporter import FileReporter
class ResolverError(Exception): ... class ResolverError(Exception): ...
@final
class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]): class Resolver(p.Stmt.Visitor[None], p.Expr.Visitor[None]):
"""A variable assignment and reference resolver """A variable assignment and reference resolver
+13 -1
View File
@@ -3,7 +3,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path 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.midas as m
import midas.ast.python as p import midas.ast.python as p
@@ -121,6 +121,7 @@ class Highlighter(ABC):
self.openings.setdefault((l + 1, 0), []).append(opening) self.openings.setdefault((l + 1, 0), []).append(opening)
@final
class PythonHighlighter( class PythonHighlighter(
Highlighter, Highlighter,
p.MidasType.Visitor[None], p.MidasType.Visitor[None],
@@ -197,6 +198,10 @@ class PythonHighlighter(
for body_stmt in stmt.body: for body_stmt in stmt.body:
body_stmt.accept(self) 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_binary_expr(self, expr: p.BinaryExpr) -> None: ...
def visit_compare_expr(self, expr: p.CompareExpr) -> 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: ... def visit_raw_stmt(self, stmt: p.RawStmt) -> None: ...
@final
class MidasHighlighter( class MidasHighlighter(
Highlighter, m.Stmt.Visitor[None], m.Expr.Visitor[None], m.Type.Visitor[None] 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]): def highlight(self, node: Highlightable[MidasHighlighter]):
node.accept(self) 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: def visit_type_stmt(self, stmt: m.TypeStmt) -> None:
self.wrap(stmt, "type-stmt") self.wrap(stmt, "type-stmt")
self.wrap(LocatableToken(stmt.name), "type-name") self.wrap(LocatableToken(stmt.name), "type-name")
@@ -352,6 +363,7 @@ class MidasHighlighter(
self.wrap(column, "column") self.wrap(column, "column")
@final
class DiagnosticsHighlighter(Highlighter): class DiagnosticsHighlighter(Highlighter):
EXTRA_CSS_PATH: Optional[Path] = Path(__file__).parent / "hl_diagnostic.css" EXTRA_CSS_PATH: Optional[Path] = Path(__file__).parent / "hl_diagnostic.css"
+2 -1
View File
@@ -1,5 +1,5 @@
import ast import ast
from typing import Optional from typing import Optional, final
import midas.ast.midas as m import midas.ast.midas as m
from midas.checker.registry import TypesRegistry 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 ConstraintGenerator(m.Expr.Visitor[ast.expr]):
"""Class to generate Python code for constraint expressions""" """Class to generate Python code for constraint expressions"""
+2 -1
View File
@@ -3,7 +3,7 @@ import logging
import shutil import shutil
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path 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.midas as m
import midas.ast.python as p 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""" """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]): 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 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 type Custom = float
// Simple custom types with constraints // Simple custom types with constraints
type Latitude = float where (-90 <= _ <= 90) type Latitude = float where (-90 <= _ & _ <= 90)
type Longitude = float where (-180 <= _ <= 180) 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 // Generic custom type (a Difference of T is derived from T, e.g. a difference of floats is a float
type Difference[T] = T type Difference[T] = T
@@ -54,10 +54,10 @@ extend Person {
prop name: str prop name: str
// Property with an inline constraint // Property with an inline constraint
prop age: Optional[int where (0 <= _ < 150)] prop age: int where (0 <= _ & _ < 150)
// Property referencing a predicate // Property referencing a predicate
prop height: float where StrictlyPositive prop height: float where StrictlyPositive(_)
prop home: GeoLocation prop home: GeoLocation
} }
@@ -187,8 +187,8 @@
"column": 38 "column": 38
}, },
{ {
"type": "LESS_EQUAL", "type": "AND",
"lexeme": "<=", "lexeme": "&",
"line": 5, "line": 5,
"column": 39 "column": 39
}, },
@@ -196,25 +196,49 @@
"type": "WHITESPACE", "type": "WHITESPACE",
"lexeme": " ", "lexeme": " ",
"line": 5, "line": 5,
"column": 40
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 5,
"column": 41 "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", "type": "NUMBER",
"lexeme": "90", "lexeme": "90",
"line": 5, "line": 5,
"column": 42 "column": 46
}, },
{ {
"type": "RIGHT_PAREN", "type": "RIGHT_PAREN",
"lexeme": ")", "lexeme": ")",
"line": 5, "line": 5,
"column": 44 "column": 48
}, },
{ {
"type": "NEWLINE", "type": "NEWLINE",
"lexeme": "\n", "lexeme": "\n",
"line": 5, "line": 5,
"column": 45 "column": 49
}, },
{ {
"type": "TYPE", "type": "TYPE",
@@ -325,8 +349,8 @@
"column": 40 "column": 40
}, },
{ {
"type": "LESS_EQUAL", "type": "AND",
"lexeme": "<=", "lexeme": "&",
"line": 6, "line": 6,
"column": 41 "column": 41
}, },
@@ -334,25 +358,49 @@
"type": "WHITESPACE", "type": "WHITESPACE",
"lexeme": " ", "lexeme": " ",
"line": 6, "line": 6,
"column": 42
},
{
"type": "UNDERSCORE",
"lexeme": "_",
"line": 6,
"column": 43 "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", "type": "NUMBER",
"lexeme": "180", "lexeme": "180",
"line": 6, "line": 6,
"column": 44 "column": 48
}, },
{ {
"type": "RIGHT_PAREN", "type": "RIGHT_PAREN",
"lexeme": ")", "lexeme": ")",
"line": 6, "line": 6,
"column": 47 "column": 51
}, },
{ {
"type": "NEWLINE", "type": "NEWLINE",
"lexeme": "\n", "lexeme": "\n",
"line": 6, "line": 6,
"column": 48 "column": 52
}, },
{ {
"type": "NEWLINE", "type": "NEWLINE",
@@ -2240,22 +2288,40 @@
}, },
{ {
"type": "IDENTIFIER", "type": "IDENTIFIER",
"lexeme": "Optional", "lexeme": "int",
"line": 57, "line": 57,
"column": 15 "column": 15
}, },
{ {
"type": "LEFT_BRACKET", "type": "WHITESPACE",
"lexeme": "[", "lexeme": " ",
"line": 57, "line": 57,
"column": 23 "column": 18
}, },
{ {
"type": "IDENTIFIER", "type": "WHERE",
"lexeme": "int", "lexeme": "where",
"line": 57,
"column": 19
},
{
"type": "WHITESPACE",
"lexeme": " ",
"line": 57, "line": 57,
"column": 24 "column": 24
}, },
{
"type": "LEFT_PAREN",
"lexeme": "(",
"line": 57,
"column": 25
},
{
"type": "NUMBER",
"lexeme": "0",
"line": 57,
"column": 26
},
{ {
"type": "WHITESPACE", "type": "WHITESPACE",
"lexeme": " ", "lexeme": " ",
@@ -2263,8 +2329,8 @@
"column": 27 "column": 27
}, },
{ {
"type": "WHERE", "type": "LESS_EQUAL",
"lexeme": "where", "lexeme": "<=",
"line": 57, "line": 57,
"column": 28 "column": 28
}, },
@@ -2272,17 +2338,35 @@
"type": "WHITESPACE", "type": "WHITESPACE",
"lexeme": " ", "lexeme": " ",
"line": 57, "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 "column": 33
}, },
{ {
"type": "LEFT_PAREN", "type": "WHITESPACE",
"lexeme": "(", "lexeme": " ",
"line": 57, "line": 57,
"column": 34 "column": 34
}, },
{ {
"type": "NUMBER", "type": "UNDERSCORE",
"lexeme": "0", "lexeme": "_",
"line": 57, "line": 57,
"column": 35 "column": 35
}, },
@@ -2293,8 +2377,8 @@
"column": 36 "column": 36
}, },
{ {
"type": "LESS_EQUAL", "type": "LESS",
"lexeme": "<=", "lexeme": "<",
"line": 57, "line": 57,
"column": 37 "column": 37
}, },
@@ -2302,55 +2386,25 @@
"type": "WHITESPACE", "type": "WHITESPACE",
"lexeme": " ", "lexeme": " ",
"line": 57, "line": 57,
"column": 39 "column": 38
},
{
"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
}, },
{ {
"type": "NUMBER", "type": "NUMBER",
"lexeme": "150", "lexeme": "150",
"line": 57, "line": 57,
"column": 44 "column": 39
}, },
{ {
"type": "RIGHT_PAREN", "type": "RIGHT_PAREN",
"lexeme": ")", "lexeme": ")",
"line": 57, "line": 57,
"column": 47 "column": 42
},
{
"type": "RIGHT_BRACKET",
"lexeme": "]",
"line": 57,
"column": 48
}, },
{ {
"type": "NEWLINE", "type": "NEWLINE",
"lexeme": "\n", "lexeme": "\n",
"line": 57, "line": 57,
"column": 49 "column": 43
}, },
{ {
"type": "NEWLINE", "type": "NEWLINE",
@@ -2442,11 +2496,29 @@
"line": 60, "line": 60,
"column": 30 "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", "type": "NEWLINE",
"lexeme": "\n", "lexeme": "\n",
"line": 60, "line": 60,
"column": 46 "column": 49
}, },
{ {
"type": "NEWLINE", "type": "NEWLINE",
@@ -2544,7 +2616,7 @@
"constraint": { "constraint": {
"_type": "GroupingExpr", "_type": "GroupingExpr",
"expr": { "expr": {
"_type": "BinaryExpr", "_type": "LogicalExpr",
"left": { "left": {
"_type": "BinaryExpr", "_type": "BinaryExpr",
"left": { "left": {
@@ -2560,6 +2632,12 @@
"_type": "WildcardExpr" "_type": "WildcardExpr"
} }
}, },
"operator": "&",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "WildcardExpr"
},
"operator": "<=", "operator": "<=",
"right": { "right": {
"_type": "LiteralExpr", "_type": "LiteralExpr",
@@ -2568,6 +2646,7 @@
} }
} }
} }
}
}, },
{ {
"_type": "TypeStmt", "_type": "TypeStmt",
@@ -2582,7 +2661,7 @@
"constraint": { "constraint": {
"_type": "GroupingExpr", "_type": "GroupingExpr",
"expr": { "expr": {
"_type": "BinaryExpr", "_type": "LogicalExpr",
"left": { "left": {
"_type": "BinaryExpr", "_type": "BinaryExpr",
"left": { "left": {
@@ -2598,6 +2677,12 @@
"_type": "WildcardExpr" "_type": "WildcardExpr"
} }
}, },
"operator": "&",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "WildcardExpr"
},
"operator": "<=", "operator": "<=",
"right": { "right": {
"_type": "LiteralExpr", "_type": "LiteralExpr",
@@ -2606,6 +2691,7 @@
} }
} }
} }
}
}, },
{ {
"_type": "TypeStmt", "_type": "TypeStmt",
@@ -3013,13 +3099,6 @@
"kind": "PROPERTY", "kind": "PROPERTY",
"name": "age", "name": "age",
"type": { "type": {
"_type": "GenericType",
"type": {
"_type": "NamedType",
"name": "Optional"
},
"args": [
{
"_type": "ConstraintType", "_type": "ConstraintType",
"type": { "type": {
"_type": "NamedType", "_type": "NamedType",
@@ -3028,7 +3107,7 @@
"constraint": { "constraint": {
"_type": "GroupingExpr", "_type": "GroupingExpr",
"expr": { "expr": {
"_type": "BinaryExpr", "_type": "LogicalExpr",
"left": { "left": {
"_type": "BinaryExpr", "_type": "BinaryExpr",
"left": { "left": {
@@ -3040,6 +3119,12 @@
"_type": "WildcardExpr" "_type": "WildcardExpr"
} }
}, },
"operator": "&",
"right": {
"_type": "BinaryExpr",
"left": {
"_type": "WildcardExpr"
},
"operator": "<", "operator": "<",
"right": { "right": {
"_type": "LiteralExpr", "_type": "LiteralExpr",
@@ -3048,7 +3133,6 @@
} }
} }
} }
]
} }
}, },
{ {
@@ -3062,8 +3146,17 @@
"name": "float" "name": "float"
}, },
"constraint": { "constraint": {
"_type": "CallExpr",
"callee": {
"_type": "VariableExpr", "_type": "VariableExpr",
"name": "StrictlyPositive" "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 import json
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from pathlib import Path from pathlib import Path
@@ -6,17 +7,30 @@ import midas.ast.python as p
from midas.checker.checker import TypeChecker from midas.checker.checker import TypeChecker
from midas.checker.diagnostic import Diagnostic from midas.checker.diagnostic import Diagnostic
from midas.checker.types import Type from midas.checker.types import Type
from midas.lexer.token import TokenType
from tests.base import Tester from tests.base import Tester
from tests.serializer.python import PythonAstJsonSerializer 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 @dataclass
class CaseResult: class CaseResult:
diagnostics: list[dict] = field(default_factory=list) diagnostics: list[dict] = field(default_factory=list)
judgments: list = field(default_factory=list) judgments: list = field(default_factory=list)
def dumps(self) -> str: def dumps(self) -> str:
return json.dumps(asdict(self), indent=2) return json.dumps(asdict(self), indent=2, cls=CustomEncoder)
class CheckerTester(Tester): class CheckerTester(Tester):
+2 -2
View File
@@ -3,7 +3,6 @@ from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from midas.checker.checker import TypeChecker from midas.checker.checker import TypeChecker
from midas.checker.diagnostic import DiagnosticType
from midas.generator.generator import Generator from midas.generator.generator import Generator
from midas.utils import TypedAST from midas.utils import TypedAST
from tests.base import Tester from tests.base import Tester
@@ -44,7 +43,8 @@ class GeneratorTester(Tester):
typed_ast: TypedAST = checker.type_check(path) typed_ast: TypedAST = checker.type_check(path)
if not any(d.type == DiagnosticType.ERROR for d in checker.diagnostics): # Ignore errors and generate anyway, easier here and errors should be
# covered by checker tests
generator = Generator(workdir=path.parent, types=checker.types) generator = Generator(workdir=path.parent, types=checker.types)
generator.set_src_path(path) generator.set_src_path(path)
result.compiled_ast = generator.generate_ast(typed_ast) result.compiled_ast = generator.generate_ast(typed_ast)
+2 -1
View File
@@ -1,4 +1,4 @@
from typing import Optional, Sequence from typing import Optional, Sequence, final
from midas.ast.midas import ( from midas.ast.midas import (
AliasStmt, AliasStmt,
@@ -28,6 +28,7 @@ from midas.ast.midas import (
) )
@final
class MidasAstJsonSerializer( class MidasAstJsonSerializer(
Stmt.Visitor[dict], Expr.Visitor[dict], Type.Visitor[dict] Stmt.Visitor[dict], Expr.Visitor[dict], Type.Visitor[dict]
): ):
+2 -1
View File
@@ -1,5 +1,5 @@
import ast import ast
from typing import Optional, Sequence, Type from typing import Optional, Sequence, Type, final
from midas.ast.python import ( from midas.ast.python import (
AssignStmt, AssignStmt,
@@ -78,6 +78,7 @@ boolean_ops: dict[Type[ast.boolop], str] = {
} }
@final
class PythonAstJsonSerializer( class PythonAstJsonSerializer(
Stmt.Visitor[dict], Expr.Visitor[dict], MidasType.Visitor[dict] Stmt.Visitor[dict], Expr.Visitor[dict], MidasType.Visitor[dict]
): ):