28 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
HEL 0c851cbc9b Merge pull request 'Cleanup and minor improvements' (#37) from feat/tidying into main
Tests / tests (push) Successful in 5s
Reviewed-on: #37
2026-07-09 14:59:29 +00:00
HEL 54b2058b86 chore: fix name and complete language configuration
Tests / tests (pull_request) Successful in 7s
2026-07-09 16:54:38 +02:00
HEL 60ff529226 chore: add midas snippets 2026-07-09 16:54:37 +02:00
HEL c3b243288d fix(checker): remove redundant instance check 2026-07-09 15:59:55 +02:00
HEL cd9b80d22b docs: fix some typos in manual 2026-07-09 15:59:29 +02:00
HEL 21b648e18f docs: fix some docstrings 2026-07-09 15:56:56 +02:00
HEL 742693fa38 chore: add script to check docstrings 2026-07-09 15:55:06 +02:00
59 changed files with 7962 additions and 1377 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
+844 -807
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -624,7 +624,7 @@ For example:
== Control flow == Control flow
Some control flow features are supported. For the limited code of this project, not all constructs are supported. The following are those currently handled and typ checked by Midas. Some control flow features are supported. For the limited code of this project, not all constructs are supported. The following are those currently handled and type checked by Midas.
=== `if` / `elif` / `else` <if-else> === `if` / `elif` / `else` <if-else>
@@ -757,7 +757,7 @@ If the value passed to `cast` or `unsafe_cast` is a literal (e.g. an integer, a
Vanilla Python already lets you use type hints to specify the type of variables and function parameters. Vanilla Python already lets you use type hints to specify the type of variables and function parameters.
Midas use them to type check your code. Additionally, it allows you to use a special syntax to define a `Frame` types directly in these annotations. Midas use them to type check your code. Additionally, it allows you to use a special syntax to define a `Frame` type directly in these annotations.
Because these annotations are not interpretable by Python, your integrated type checker might complain loudly about them being invalid. Because these annotations are not interpretable by Python, your integrated type checker might complain loudly about them being invalid.
A workaround is to silence it by adding a type comment at the end of the line, as shown in @silence-errors. A workaround is to silence it by adding a type comment at the end of the line, as shown in @silence-errors.
+1
View File
@@ -61,6 +61,7 @@
set document( set document(
title: title, title: title,
author: author, author: author,
date: none,
) )
set text( set text(
font: "Source Sans 3", font: "Source Sans 3",
@@ -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):
+5 -1
View File
@@ -26,7 +26,11 @@ Circular dependencies and diamond inheritance MUST be avoided
def define_builtins(reg: TypesRegistry): def define_builtins(reg: TypesRegistry):
"""Define builtin types and operations""" """Define builtin types and operations
Args:
reg (TypesRegistry): the types registry
"""
any = reg.define_type("Any", TopType()) any = reg.define_type("Any", TopType())
unit = reg.define_type("None", UnitType()) unit = reg.define_type("None", UnitType())
object = reg.define_type("object", BaseType(name="object")) object = reg.define_type("object", BaseType(name="object"))
+14 -9
View File
@@ -102,6 +102,11 @@ class CallDispatcher(Generic[E]):
self.logger: logging.Logger = logging.getLogger("CallDispatcher") self.logger: logging.Logger = logging.getLogger("CallDispatcher")
def set_reporter(self, reporter: FileReporter): def set_reporter(self, reporter: FileReporter):
"""Set the current reporter
Args:
reporter (FileReporter): the new file reporter
"""
self.reporter = reporter self.reporter = reporter
def get_result( def get_result(
@@ -123,8 +128,8 @@ class CallDispatcher(Generic[E]):
Args: Args:
location (Location): the call location location (Location): the call location
callee (Type): the called function callee (Type): the called function
positional (list[TypedExpr]): the list of positional arguments positional (list[TypedExpr[E]]): the list of positional arguments
keywords (dict[str, TypedExpr]): the map of keyword arguments keywords (dict[str, TypedExpr[E]]): the map of keyword arguments
report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True. report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True.
Returns: Returns:
@@ -250,7 +255,7 @@ class CallDispatcher(Generic[E]):
"""Check whether the passed argument types correspond to their matched parameter definitions """Check whether the passed argument types correspond to their matched parameter definitions
Args: Args:
arguments (list[MappedArgument]): the list of argument/parameter pairs arguments (list[MappedArgument[E]]): the list of argument/parameter pairs
report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True. report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True.
Returns: Returns:
@@ -286,8 +291,8 @@ class CallDispatcher(Generic[E]):
Args: Args:
overloads (list[Type]): the list of possible overloads overloads (list[Type]): the list of possible overloads
location (Location): the call location location (Location): the call location
positional (list[TypedExpr]): the list of positional arguments positional (list[TypedExpr[E]]): the list of positional arguments
keywords (dict[str, TypedExpr]): the map of keywords arguments keywords (dict[str, TypedExpr[E]]): the map of keywords arguments
report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True. report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True.
Returns: Returns:
@@ -385,8 +390,8 @@ class CallDispatcher(Generic[E]):
Args: Args:
function (Function): the function definition function (Function): the function definition
location (Location): the call location location (Location): the call location
positional (list[TypedExpr]): the list of positional arguments positional (list[TypedExpr[E]]): the list of positional arguments
keywords (dict[str, TypedExpr]): the map of keyword arguments keywords (dict[str, TypedExpr[E]]): the map of keyword arguments
report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True. report_errors (bool, optional): whether type errors should be reported as diagnostics. Defaults to True.
Returns: Returns:
@@ -514,8 +519,8 @@ class CallDispatcher(Generic[E]):
function / a subtype of another. function / a subtype of another.
Args: Args:
mapped1 (list[MappedArgument]): the first argument mappings (subtype) mapped1 (list[MappedArgument[E]]): the first argument mappings (subtype)
mapped2 (list[MappedArgument]): the second argument mappings (supertype) mapped2 (list[MappedArgument[E]]): the second argument mappings (supertype)
Returns: Returns:
bool: `True` if `mapped1` is a subtype of `mapped2`, `False` otherwise bool: `True` if `mapped1` is a subtype of `mapped2`, `False` otherwise
+4 -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
@@ -190,6 +191,7 @@ class Evaluator(m.Expr.Visitor[Any]):
"""Evaluate a predicate function call """Evaluate a predicate function call
Args: Args:
location (Location): the location of the call expression
predicate (Predicate): the predicate to evaluate predicate (Predicate): the predicate to evaluate
args (list[Any]): a list of positional arguments args (list[Any]): a list of positional arguments
kwargs (dict[str, Any]): a map of keyword arguments kwargs (dict[str, Any]): a map of keyword arguments
@@ -234,6 +236,7 @@ class Evaluator(m.Expr.Visitor[Any]):
is set in the context using :func:`set_value` with the parameter's name is set in the context using :func:`set_value` with the parameter's name
Args: Args:
location (Location): the location of the call expression
function (Function): the called function function (Function): the called function
args (list[Any]): a list of positional arguments args (list[Any]): a list of positional arguments
kwargs (dict[str, Any]): a map of keyword arguments kwargs (dict[str, Any]): a map of keyword arguments
+1 -1
View File
@@ -45,7 +45,7 @@ class ColumnManager:
Args: Args:
reporter (FileReporter): the file reporter to use for diagnostics reporter (FileReporter): the file reporter to use for diagnostics
location (Location): the subscript's location location (Location): the subscript's location
column (DataFrameType): the column type column (ColumnType): the column type
index (TypedExpr): the index index (TypedExpr): the index
Returns: Returns:
+6 -4
View File
@@ -353,10 +353,12 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
call (Call): the call object call (Call): the call object
kwargs (list[Function.Parameter], optional): a list of extra kwargs (list[Function.Parameter], optional): a list of extra
keyword-only parameters. Defaults to []. keyword-only parameters. Defaults to [].
formula (Callable[[Type], Formula], optional): optional formula formula (Optional[Callable[[Type], Formula]], optional):
builder function to compute the return type. If set, the function optional formula builder function to compute the return type.<br>
should accept the inner column type and return a formula. If set, the function should accept the inner column type and
If `None`, the result is typed as `Column[Any]`. Defaults to None. return a formula.<br>
If `None`, the result is typed as `Column[Any]`.
Defaults to None.
Returns: Returns:
Type: the result type Type: the result type
+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}",
) )
+29 -10
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],
@@ -486,11 +487,10 @@ class PythonTyper(
self._assign_sub(location, var, index, value_type) self._assign_sub(location, var, index, value_type)
case _: case _:
if not isinstance(target, p.VariableExpr): self.logger.warning(f"Unsupported assignment to {target}")
self.logger.warning(f"Unsupported assignment to {target}") self.reporter.warning(
self.reporter.warning( target.location, f"Unsupported assignment to {target}"
target.location, f"Unsupported assignment to {target}" )
)
def _assign_var(self, location: Location, target: p.VariableExpr, value_type: Type): def _assign_var(self, location: Location, target: p.VariableExpr, value_type: Type):
"""Type check assignment to the given target """Type check assignment to the given target
@@ -518,11 +518,12 @@ class PythonTyper(
def _assign_attr( def _assign_attr(
self, location: Location, object: p.Expr, name: str, value_type: Type self, location: Location, object: p.Expr, name: str, value_type: Type
): ):
"""Type check assignment to the given target """Type check assignment to the given attribute target
Args: Args:
location (Location): the location of the assignment location (Location): the location of the assignment
target (p.VariableExpr): the assignment's target object (p.Expr): the target attribute's owner object
name (str): the target attribute's name
value_type (Type): the value to be assigned value_type (Type): the value to be assigned
""" """
object_type: Type = self.type_of(object) object_type: Type = self.type_of(object)
@@ -544,11 +545,15 @@ class PythonTyper(
index: p.Expr, index: p.Expr,
value_type: Type, value_type: Type,
): ):
"""Type check assignment to the given target """Type check assignment to the given subscript target
Args: Args:
location (Location): the location of the assignment location (Location): the location of the assignment
target (p.VariableExpr): the assignment's target var (p.VariableExpr): the target subscript's owner. We only allow
a variable expression here because we might modify its type (for
example when assigning a column to a dataframe) and reference
types are not implemented
index (p.Expr): the target subscript's index expression
value_type (Type): the value to be assigned value_type (Type): the value to be assigned
""" """
var_type: Type = self.type_of(var) var_type: Type = self.type_of(var)
@@ -690,6 +695,20 @@ class PythonTyper(
right: TypedExpr, right: TypedExpr,
method: str, method: str,
) -> Type: ) -> Type:
"""Compute the result type of a binary operation method call
This method is called for dunder methods called by binary operators
Args:
location (Location): the location of the operation
expr (p.Expr): the expression which triggered this resolution
left (TypedExpr): the left operand
right (TypedExpr): the right operand
method (str): the method name
Returns:
Type: the result type
"""
try: try:
return self.call_method( return self.call_method(
location=location, location=location,
+5
View File
@@ -70,6 +70,11 @@ class FileReporter:
@contextmanager @contextmanager
def with_context(self, ctx: str): def with_context(self, ctx: str):
"""Push given context for reports inside this manager and pop it on exit
Args:
ctx (str): the context to temporarily push on the stack
"""
self._context.append(ctx) self._context.append(ctx)
try: try:
yield yield
+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
+174
View File
@@ -0,0 +1,174 @@
import ast
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional
@dataclass
class ArgDoc:
name: str
type: str
optional: bool
@dataclass
class Param:
name: str
annotation: Optional[str]
optional: bool
class Checker(ast.NodeVisitor):
def _get_args(self, docstring: str) -> list[ArgDoc]:
args: list[ArgDoc] = []
in_args: bool = False
for line in docstring.splitlines():
if not in_args:
if line == "Args:":
in_args = True
continue
# End of args
if not line.startswith(" "):
break
# Continuation line
if line.startswith(" "):
continue
line = line.strip()
m = re.match(r"(?P<name>\w+) \((?P<type>.*?)(?P<opt>, optional)?\):", line)
if m is None:
continue
args.append(
ArgDoc(
name=m.group("name"),
type=m.group("type"),
optional=m.group("opt") is not None,
)
)
return args
def log(self, node: ast.FunctionDef, msg: str):
loc: str = f"{node.name} L{node.lineno}:{node.col_offset+1}"
print(f" ({loc}) {msg}")
def _is_ignored(self, node: ast.FunctionDef) -> bool:
name: str = node.name
if name.startswith("visit_") or name.startswith("_visit_"):
return True
if name.startswith("parse_") or name.startswith("_parse_"):
return True
if name.startswith("_print"):
return True
if name.startswith("_write"):
return True
if name.startswith("__") and name.endswith("__"):
return True
if name == "accept":
return True
node.decorator_list
match node:
case ast.FunctionDef(
decorator_list=[
ast.Call(
func=ast.Name(id="method"),
),
],
):
return True
return False
def visit_FunctionDef(self, node: ast.FunctionDef) -> Any:
docstring: Optional[str] = ast.get_docstring(node)
func_name: str = node.name
if docstring is None:
if not self._is_ignored(node):
self.log(node, f"Missing docstring for function {func_name}")
return
args_doc: list[ArgDoc] = self._get_args(docstring)
by_name: dict[str, ArgDoc] = {}
for doc in args_doc:
if doc.name in by_name:
self.log(node, f"Multiple documentation lines for argument {doc.name}")
by_name[doc.name] = doc
all_params: list[Param] = []
pos_args: list[ast.arg] = node.args.posonlyargs
mixed_args: list[ast.arg] = node.args.args
kw_args: list[ast.arg] = node.args.kwonlyargs
def add_param(arg: ast.arg, optional: bool):
all_params.append(
Param(
name=arg.arg,
annotation=(
ast.unparse(arg.annotation)
if arg.annotation is not None
else None
),
optional=optional,
)
)
n_pos: int = len(pos_args) + len(mixed_args)
for i, arg in enumerate(pos_args):
j: int = n_pos - i - 1
optional: bool = j < len(node.args.defaults)
add_param(arg, optional)
for i, arg in enumerate(mixed_args):
j: int = len(mixed_args) - i - 1
optional: bool = j < len(node.args.defaults)
add_param(arg, optional)
for arg, default in zip(kw_args, node.args.kw_defaults):
optional: bool = default is not None
add_param(arg, optional)
for param in all_params:
doc: Optional[ArgDoc] = by_name.get(param.name, None)
if doc is None:
if param.name not in {"self", "cls"}:
self.log(
node, f"Missing documentation for parameter '{param.name}'"
)
continue
if doc.name != param.name:
self.log(node, f"Documentation mismatch for '{param.name}': wrong name")
if doc.type != param.annotation:
self.log(node, f"Documentation mismatch for '{param.name}': wrong type")
if doc.optional != param.optional:
self.log(
node,
f"Documentation mismatch for '{param.name}': wrong optionality",
)
def check_file(path: Path):
source: str = path.read_text()
tree = ast.parse(source)
checker = Checker()
checker.visit(tree)
def main():
folder: Path = (Path(__file__).parent.parent / "midas").resolve()
all_files = folder.rglob("*.py")
for f in all_files:
print(f.relative_to(folder))
check_file(f)
print()
if __name__ == "__main__":
main()
-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,10 +2632,17 @@
"_type": "WildcardExpr" "_type": "WildcardExpr"
} }
}, },
"operator": "<=", "operator": "&",
"right": { "right": {
"_type": "LiteralExpr", "_type": "BinaryExpr",
"value": 90 "left": {
"_type": "WildcardExpr"
},
"operator": "<=",
"right": {
"_type": "LiteralExpr",
"value": 90
}
} }
} }
} }
@@ -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,10 +2677,17 @@
"_type": "WildcardExpr" "_type": "WildcardExpr"
} }
}, },
"operator": "<=", "operator": "&",
"right": { "right": {
"_type": "LiteralExpr", "_type": "BinaryExpr",
"value": 180 "left": {
"_type": "WildcardExpr"
},
"operator": "<=",
"right": {
"_type": "LiteralExpr",
"value": 180
}
} }
} }
} }
@@ -3013,42 +3099,40 @@
"kind": "PROPERTY", "kind": "PROPERTY",
"name": "age", "name": "age",
"type": { "type": {
"_type": "GenericType", "_type": "ConstraintType",
"type": { "type": {
"_type": "NamedType", "_type": "NamedType",
"name": "Optional" "name": "int"
}, },
"args": [ "constraint": {
{ "_type": "GroupingExpr",
"_type": "ConstraintType", "expr": {
"type": { "_type": "LogicalExpr",
"_type": "NamedType", "left": {
"name": "int" "_type": "BinaryExpr",
"left": {
"_type": "LiteralExpr",
"value": 0
},
"operator": "<=",
"right": {
"_type": "WildcardExpr"
}
}, },
"constraint": { "operator": "&",
"_type": "GroupingExpr", "right": {
"expr": { "_type": "BinaryExpr",
"_type": "BinaryExpr", "left": {
"left": { "_type": "WildcardExpr"
"_type": "BinaryExpr", },
"left": { "operator": "<",
"_type": "LiteralExpr", "right": {
"value": 0 "_type": "LiteralExpr",
}, "value": 150
"operator": "<=",
"right": {
"_type": "WildcardExpr"
}
},
"operator": "<",
"right": {
"_type": "LiteralExpr",
"value": 150
}
} }
} }
} }
] }
} }
}, },
{ {
@@ -3062,8 +3146,17 @@
"name": "float" "name": "float"
}, },
"constraint": { "constraint": {
"_type": "VariableExpr", "_type": "CallExpr",
"name": "StrictlyPositive" "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 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):
+5 -5
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,10 +43,11 @@ 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
generator = Generator(workdir=path.parent, types=checker.types) # covered by checker tests
generator.set_src_path(path) generator = Generator(workdir=path.parent, types=checker.types)
result.compiled_ast = generator.generate_ast(typed_ast) generator.set_src_path(path)
result.compiled_ast = generator.generate_ast(typed_ast)
return result 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 ( 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]
): ):
+83
View File
@@ -0,0 +1,83 @@
{
"comments": {
"lineComment": {
"comment": "//",
},
"blockComment": [
"/*",
"*/"
]
},
"brackets": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
],
"autoClosingPairs": [
{
"open": "{",
"close": "}"
},
{
"open": "[",
"close": "]"
},
{
"open": "(",
"close": ")"
},
{
"open": "'",
"close": "'",
"notIn": [
"string",
"comment"
]
},
{
"open": "\"",
"close": "\"",
"notIn": [
"string"
]
},
{
"open": "/**",
"close": " */",
"notIn": [
"string"
]
}
],
"surroundingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"'",
"'"
],
[
"\"",
"\""
]
]
}
-16
View File
@@ -1,16 +0,0 @@
{
"brackets": [
["{", "}"],
["[", "]"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"]
]
}
+9 -1
View File
@@ -4,7 +4,9 @@
"engines": { "engines": {
"vscode": "*" "vscode": "*"
}, },
"categories": ["Programming Languages"], "categories": [
"Programming Languages"
],
"contributes": { "contributes": {
"languages": [ "languages": [
{ {
@@ -24,6 +26,12 @@
"scopeName": "source.midas", "scopeName": "source.midas",
"path": "./syntaxes/midas.tmLanguage.json" "path": "./syntaxes/midas.tmLanguage.json"
} }
],
"snippets": [
{
"language": "midas",
"path": "./snippets.json"
}
] ]
} }
} }
+50
View File
@@ -0,0 +1,50 @@
{
"Type alias": {
"prefix": "alias",
"body": "alias ${1:name} = $0",
"description": "Declare a type alias"
},
"Derived type": {
"prefix": "type",
"body": "type ${1:name} = $0",
"description": "Declare a derived type"
},
"Predicate": {
"prefix": "predicate",
"body": "predicate ${1:signature} = $0",
"description": "Declare a predicate"
},
"Extend": {
"prefix": "extend",
"body": [
"extend ${1:type} {",
"\t$0",
"}"
],
"description": "Extend a type to add members"
},
"Property": {
"prefix": "prop",
"body": "prop ${1:name}: $0",
"description": "Declare a property"
},
"Method": {
"prefix": "def",
"body": "def ${1:name}: $0",
"description": "Declare a method"
},
"Function type": {
"prefix": "fn",
"body": "fn(${1:parameters}) -> ${2:returns}",
"description": "A function type"
},
"Frame type": {
"prefix": "frame",
"body": [
"Frame[",
"\t$0",
"]"
],
"description": "A frame type"
}
}