diff --git a/examples/00_syntax_prototype/01_simple_types.py b/examples/00_syntax_prototype/01_simple_types.py index 725fdf4..c974415 100644 --- a/examples/00_syntax_prototype/01_simple_types.py +++ b/examples/00_syntax_prototype/01_simple_types.py @@ -7,10 +7,9 @@ from __future__ import annotations df: Frame[ verified: bool, birth_year: int, - height: float + ( _ > 0 ) + ( _ < 250 ), + height: float, name: str, - date: datetime, + date: object, float, # unnamed unknown: _, # untyped - _ # unnamed and untyped ] diff --git a/examples/00_syntax_prototype/02_custom_types.midas b/examples/00_syntax_prototype/02_custom_types.midas index 017e40c..7b0336c 100644 --- a/examples/00_syntax_prototype/02_custom_types.midas +++ b/examples/00_syntax_prototype/02_custom_types.midas @@ -1,24 +1,63 @@ -// Simple custom type derived from floats -type Latitude -type Longitude +// 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 +type GeoLocation = object + +extend GeoLocation { + prop lat: Latitude + prop lon: Longitude } -type LatitudeDiff -type LongitudeDiff +type GeoLocationDifference = object + +extend GeoLocationDifference { + prop lat: Difference[Latitude] + prop lon: Difference[Longitude] +} + +// Define operations on our custom type // Simple operation defined on our custom types -op - = -op - = +extend Latitude { + def __sub__: fn(Latitude, /) -> Difference[Latitude] +} -// Simple custom type with a constraint -type Age +extend Longitude { + def __sub__: fn(Longitude, /) -> Difference[Longitude] +} -// Predefined custom constraints that can be referenced in other definitions -constraint Positive = _ >= 0 -constraint StrictlyPositive = _ > 0 -//constraint Even = _ % 2 == 0 \ No newline at end of file +extend GeoLocation { + // This type is compatible with the `-` operation with another GeoLocation + // i.e. you can subtract a GeoLocation from another GeoLocation, resulting + // in a GeoLocationDifference + def __sub__: fn(GeoLocation, /) -> GeoLocationDifference +} + + +// Predefined custom predicates that can be referenced in other definitions +predicate Positive(v: float) = v >= 0 +predicate StrictlyPositive(v: float) = v > 0 +predicate Equatorial(loc: GeoLocation) = (-10 <= loc.lat <= 10) +predicate Arctic(loc: GeoLocation) = (loc.lat >= 66) + +type Person = object + +extend Person { + prop name: str + + // Property with an inline constraint + prop age: int where (0 <= _ & _ < 150) + + // Property referencing a predicate + prop height: float where StrictlyPositive(_) + + prop home: GeoLocation +} diff --git a/examples/00_syntax_prototype/02_custom_types.py b/examples/00_syntax_prototype/02_custom_types.py index 8678ba0..e78d458 100644 --- a/examples/00_syntax_prototype/02_custom_types.py +++ b/examples/00_syntax_prototype/02_custom_types.py @@ -8,8 +8,8 @@ df: Frame[ ] # 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 +lat: Column[Latitude] = ... +lon: Column[Longitude] = ... # Unregistered operations between types are not permitted lat + lon # Invalid operation @@ -18,13 +18,3 @@ lat + lon # Invalid operation 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, -] diff --git a/examples/00_syntax_prototype/03_custom_types_v2.midas b/examples/00_syntax_prototype/03_custom_types_v2.midas deleted file mode 100644 index 31b0f53..0000000 --- a/examples/00_syntax_prototype/03_custom_types_v2.midas +++ /dev/null @@ -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) diff --git a/examples/01_simple_type_checking/01_simple_operations.py b/examples/01_simple_type_checking/01_simple_operations.py index 4e767f2..8d7c6f8 100644 --- a/examples/01_simple_type_checking/01_simple_operations.py +++ b/examples/01_simple_type_checking/01_simple_operations.py @@ -6,7 +6,7 @@ c = a + b # -> int c = "invalid" # -> can't assign str to int variable d = True -e = d + d +e = d + d # -> addition not defined between booleans f: float = a diff --git a/examples/01_simple_type_checking/02_simple_types.py b/examples/01_simple_type_checking/02_simple_types.py index c015a75..ad5118d 100644 --- a/examples/01_simple_type_checking/02_simple_types.py +++ b/examples/01_simple_type_checking/02_simple_types.py @@ -1,6 +1,7 @@ # type: ignore # ruff: disable [F821] -distance: Meter = cast(Meter, 123.45) -time: Second = cast(Second, 6.7) +distance = cast(Meter, 123.45) +time = cast(Second, 6.7) speed = distance / time +print(speed) diff --git a/examples/01_simple_type_checking/03_control_flow.py b/examples/01_simple_type_checking/03_control_flow.py index 772c9ac..f0ba59b 100644 --- a/examples/01_simple_type_checking/03_control_flow.py +++ b/examples/01_simple_type_checking/03_control_flow.py @@ -1,3 +1,5 @@ +# Return types must have a LUB +# Valid def minimum(x: int, y: int): if x < y: return x @@ -5,18 +7,28 @@ def minimum(x: int, y: int): return y +# Invalid +def func(a: int): + if a < 5: + return "Oops" + return True + + a = 15 b = 72 c = minimum(a, b) +# Recursive but typable thanks to return hint def factorial(n: int) -> int: if n <= 1: return 1 return n * factorial(n - 1) -category = "Category 1" if a < 10 else "Category 2" +# Branches must be of the same type +category = "Category 1" if a < 10 else "Category 2" # Valid +category = "Category 1" if a < 10 else 3 # Invalid def foo() -> None: diff --git a/examples/01_simple_type_checking/04_complex_types.midas b/examples/01_simple_type_checking/04_complex_types.midas index adc76b3..fb1c255 100644 --- a/examples/01_simple_type_checking/04_complex_types.midas +++ b/examples/01_simple_type_checking/04_complex_types.midas @@ -15,7 +15,9 @@ extend Coordinate { type Difference[T <: float] = T type MeterDifference = Difference[Meter] -type CompDiff[T <: float] = { +type CompDiff[T <: float] = object + +extend CompDiff[T <: float] { prop d1: Difference[T] prop d2: Difference[T] } \ No newline at end of file diff --git a/examples/01_simple_type_checking/04_complex_types.py b/examples/01_simple_type_checking/04_complex_types.py index f1d1215..1ee0873 100644 --- a/examples/01_simple_type_checking/04_complex_types.py +++ b/examples/01_simple_type_checking/04_complex_types.py @@ -1,20 +1,20 @@ # type: ignore # ruff: disable [F821] -p1: Coordinate -p2: Coordinate +p1 = cast(Coordinate, object()) +p2 = cast(Coordinate, object()) diff_x = p2.x - p1.x diff_y = p2.y - p1.y dist = diff_x + diff_y -p2.x += cast(Meter, 1) +p2.x += cast(Meter, 1.0) p2.y = True # invalid, wrong type p2.z = 3 # invalid, no property 'z' on Coordinate p2.x.a = 3 # invalid, no properties on Meter -foo: list[float] = [] +foo = cast(list[float], []) append = foo.append @@ -23,7 +23,7 @@ foo.append(2) append(True) # invalid, must be float append(2) -bar: list[list[Meter]] +bar = cast(list[list[Meter]], []) bar.append([p2.x]) diff --git a/examples/01_simple_type_checking/06_overloads.py b/examples/01_simple_type_checking/06_overloads.py index 86406e0..1a9427a 100644 --- a/examples/01_simple_type_checking/06_overloads.py +++ b/examples/01_simple_type_checking/06_overloads.py @@ -1,9 +1,9 @@ # type: ignore # ruff: disable [F821] -foo: Foo -t1: T1 -t2: T2 +foo = cast(Foo, object()) +t1 = cast(T1, object()) +t2 = cast(T2, object()) a = foo.bar(t1) b = foo.bar(t2) @@ -13,6 +13,6 @@ func = foo.bar c = func(t1) d = func(t2) -t2b: T2b +t2b = cast(T2b, object()) e = foo.bar(t2b) diff --git a/tests/cases/midas-parser/01_simple_types.midas b/tests/cases/midas-parser/01_simple_types.midas index 3b982e1..7b0336c 100644 --- a/tests/cases/midas-parser/01_simple_types.midas +++ b/tests/cases/midas-parser/01_simple_types.midas @@ -2,8 +2,8 @@ type Custom = float // Simple custom types with constraints -type Latitude = float where (-90 <= _ <= 90) -type Longitude = float where (-180 <= _ <= 180) +type Latitude = float where (-90 <= _ & _ <= 90) +type Longitude = float where (-180 <= _ & _ <= 180) // Generic custom type (a Difference of T is derived from T, e.g. a difference of floats is a float type Difference[T] = T @@ -54,10 +54,10 @@ extend Person { prop name: str // Property with an inline constraint - prop age: Optional[int where (0 <= _ < 150)] + prop age: int where (0 <= _ & _ < 150) // Property referencing a predicate - prop height: float where StrictlyPositive + prop height: float where StrictlyPositive(_) prop home: GeoLocation } diff --git a/tests/cases/midas-parser/01_simple_types.midas.ref.json b/tests/cases/midas-parser/01_simple_types.midas.ref.json index 62936de..f764a4b 100644 --- a/tests/cases/midas-parser/01_simple_types.midas.ref.json +++ b/tests/cases/midas-parser/01_simple_types.midas.ref.json @@ -187,8 +187,8 @@ "column": 38 }, { - "type": "LESS_EQUAL", - "lexeme": "<=", + "type": "AND", + "lexeme": "&", "line": 5, "column": 39 }, @@ -196,25 +196,49 @@ "type": "WHITESPACE", "lexeme": " ", "line": 5, + "column": 40 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 5, "column": 41 }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 5, + "column": 42 + }, + { + "type": "LESS_EQUAL", + "lexeme": "<=", + "line": 5, + "column": 43 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 5, + "column": 45 + }, { "type": "NUMBER", "lexeme": "90", "line": 5, - "column": 42 + "column": 46 }, { "type": "RIGHT_PAREN", "lexeme": ")", "line": 5, - "column": 44 + "column": 48 }, { "type": "NEWLINE", "lexeme": "\n", "line": 5, - "column": 45 + "column": 49 }, { "type": "TYPE", @@ -325,8 +349,8 @@ "column": 40 }, { - "type": "LESS_EQUAL", - "lexeme": "<=", + "type": "AND", + "lexeme": "&", "line": 6, "column": 41 }, @@ -334,25 +358,49 @@ "type": "WHITESPACE", "lexeme": " ", "line": 6, + "column": 42 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 6, "column": 43 }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 6, + "column": 44 + }, + { + "type": "LESS_EQUAL", + "lexeme": "<=", + "line": 6, + "column": 45 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 6, + "column": 47 + }, { "type": "NUMBER", "lexeme": "180", "line": 6, - "column": 44 + "column": 48 }, { "type": "RIGHT_PAREN", "lexeme": ")", "line": 6, - "column": 47 + "column": 51 }, { "type": "NEWLINE", "lexeme": "\n", "line": 6, - "column": 48 + "column": 52 }, { "type": "NEWLINE", @@ -2240,22 +2288,40 @@ }, { "type": "IDENTIFIER", - "lexeme": "Optional", + "lexeme": "int", "line": 57, "column": 15 }, { - "type": "LEFT_BRACKET", - "lexeme": "[", + "type": "WHITESPACE", + "lexeme": " ", "line": 57, - "column": 23 + "column": 18 }, { - "type": "IDENTIFIER", - "lexeme": "int", + "type": "WHERE", + "lexeme": "where", + "line": 57, + "column": 19 + }, + { + "type": "WHITESPACE", + "lexeme": " ", "line": 57, "column": 24 }, + { + "type": "LEFT_PAREN", + "lexeme": "(", + "line": 57, + "column": 25 + }, + { + "type": "NUMBER", + "lexeme": "0", + "line": 57, + "column": 26 + }, { "type": "WHITESPACE", "lexeme": " ", @@ -2263,8 +2329,8 @@ "column": 27 }, { - "type": "WHERE", - "lexeme": "where", + "type": "LESS_EQUAL", + "lexeme": "<=", "line": 57, "column": 28 }, @@ -2272,17 +2338,35 @@ "type": "WHITESPACE", "lexeme": " ", "line": 57, + "column": 30 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 57, + "column": 31 + }, + { + "type": "WHITESPACE", + "lexeme": " ", + "line": 57, + "column": 32 + }, + { + "type": "AND", + "lexeme": "&", + "line": 57, "column": 33 }, { - "type": "LEFT_PAREN", - "lexeme": "(", + "type": "WHITESPACE", + "lexeme": " ", "line": 57, "column": 34 }, { - "type": "NUMBER", - "lexeme": "0", + "type": "UNDERSCORE", + "lexeme": "_", "line": 57, "column": 35 }, @@ -2293,8 +2377,8 @@ "column": 36 }, { - "type": "LESS_EQUAL", - "lexeme": "<=", + "type": "LESS", + "lexeme": "<", "line": 57, "column": 37 }, @@ -2302,55 +2386,25 @@ "type": "WHITESPACE", "lexeme": " ", "line": 57, - "column": 39 - }, - { - "type": "UNDERSCORE", - "lexeme": "_", - "line": 57, - "column": 40 - }, - { - "type": "WHITESPACE", - "lexeme": " ", - "line": 57, - "column": 41 - }, - { - "type": "LESS", - "lexeme": "<", - "line": 57, - "column": 42 - }, - { - "type": "WHITESPACE", - "lexeme": " ", - "line": 57, - "column": 43 + "column": 38 }, { "type": "NUMBER", "lexeme": "150", "line": 57, - "column": 44 + "column": 39 }, { "type": "RIGHT_PAREN", "lexeme": ")", "line": 57, - "column": 47 - }, - { - "type": "RIGHT_BRACKET", - "lexeme": "]", - "line": 57, - "column": 48 + "column": 42 }, { "type": "NEWLINE", "lexeme": "\n", "line": 57, - "column": 49 + "column": 43 }, { "type": "NEWLINE", @@ -2442,11 +2496,29 @@ "line": 60, "column": 30 }, + { + "type": "LEFT_PAREN", + "lexeme": "(", + "line": 60, + "column": 46 + }, + { + "type": "UNDERSCORE", + "lexeme": "_", + "line": 60, + "column": 47 + }, + { + "type": "RIGHT_PAREN", + "lexeme": ")", + "line": 60, + "column": 48 + }, { "type": "NEWLINE", "lexeme": "\n", "line": 60, - "column": 46 + "column": 49 }, { "type": "NEWLINE", @@ -2544,7 +2616,7 @@ "constraint": { "_type": "GroupingExpr", "expr": { - "_type": "BinaryExpr", + "_type": "LogicalExpr", "left": { "_type": "BinaryExpr", "left": { @@ -2560,10 +2632,17 @@ "_type": "WildcardExpr" } }, - "operator": "<=", + "operator": "&", "right": { - "_type": "LiteralExpr", - "value": 90 + "_type": "BinaryExpr", + "left": { + "_type": "WildcardExpr" + }, + "operator": "<=", + "right": { + "_type": "LiteralExpr", + "value": 90 + } } } } @@ -2582,7 +2661,7 @@ "constraint": { "_type": "GroupingExpr", "expr": { - "_type": "BinaryExpr", + "_type": "LogicalExpr", "left": { "_type": "BinaryExpr", "left": { @@ -2598,10 +2677,17 @@ "_type": "WildcardExpr" } }, - "operator": "<=", + "operator": "&", "right": { - "_type": "LiteralExpr", - "value": 180 + "_type": "BinaryExpr", + "left": { + "_type": "WildcardExpr" + }, + "operator": "<=", + "right": { + "_type": "LiteralExpr", + "value": 180 + } } } } @@ -3013,42 +3099,40 @@ "kind": "PROPERTY", "name": "age", "type": { - "_type": "GenericType", + "_type": "ConstraintType", "type": { "_type": "NamedType", - "name": "Optional" + "name": "int" }, - "args": [ - { - "_type": "ConstraintType", - "type": { - "_type": "NamedType", - "name": "int" + "constraint": { + "_type": "GroupingExpr", + "expr": { + "_type": "LogicalExpr", + "left": { + "_type": "BinaryExpr", + "left": { + "_type": "LiteralExpr", + "value": 0 + }, + "operator": "<=", + "right": { + "_type": "WildcardExpr" + } }, - "constraint": { - "_type": "GroupingExpr", - "expr": { - "_type": "BinaryExpr", - "left": { - "_type": "BinaryExpr", - "left": { - "_type": "LiteralExpr", - "value": 0 - }, - "operator": "<=", - "right": { - "_type": "WildcardExpr" - } - }, - "operator": "<", - "right": { - "_type": "LiteralExpr", - "value": 150 - } + "operator": "&", + "right": { + "_type": "BinaryExpr", + "left": { + "_type": "WildcardExpr" + }, + "operator": "<", + "right": { + "_type": "LiteralExpr", + "value": 150 } } } - ] + } } }, { @@ -3062,8 +3146,17 @@ "name": "float" }, "constraint": { - "_type": "VariableExpr", - "name": "StrictlyPositive" + "_type": "CallExpr", + "callee": { + "_type": "VariableExpr", + "name": "StrictlyPositive" + }, + "arguments": [ + { + "_type": "WildcardExpr" + } + ], + "keywords": {} } } },