updated readme
This commit is contained in:
parent
6f4ad20b7d
commit
dd419fc120
@ -37,7 +37,83 @@ Here is the list of files and their purpose:
|
|||||||
python AST.
|
python AST.
|
||||||
* `type_def.py`: python class for various types.
|
* `type_def.py`: python class for various types.
|
||||||
|
|
||||||
## Type Check Implementation
|
## Variable Scope
|
||||||
|
There is no shadowing in Python, so we decided that variables with the same name
|
||||||
|
in a function must have the same type. For example, the following is not
|
||||||
|
allowed:
|
||||||
|
```python
|
||||||
|
if foo():
|
||||||
|
a = 1
|
||||||
|
else:
|
||||||
|
a = None
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, as variables has to be well typed, they must be initialized before using
|
||||||
|
them. If a variable could be not initialized in some code path, then it is not
|
||||||
|
readable. The following is also not allowed:
|
||||||
|
```python
|
||||||
|
if foo():
|
||||||
|
a = 1
|
||||||
|
a = a + 1
|
||||||
|
```
|
||||||
|
## Generics
|
||||||
|
Generics are supported via type variables.
|
||||||
|
|
||||||
|
* Generic type variable:
|
||||||
|
```python
|
||||||
|
A = TypeVar('A')
|
||||||
|
```
|
||||||
|
* Bounded type variable (`A` can either be `T1` or `T2` or ...):
|
||||||
|
```python
|
||||||
|
A = TypeVar('A', T1, T2, ...)
|
||||||
|
```
|
||||||
|
> Note:
|
||||||
|
>
|
||||||
|
> 1. In normal python, the *bound* of a type variable is actually about class
|
||||||
|
> inheritance. However, our type variable would be invariant and would not
|
||||||
|
> deal with subtyping.
|
||||||
|
> 2. Type variables cannot contain any type variable in their bound. For
|
||||||
|
> example, `B = TypeVar('B', A, T3)` is not allowed.
|
||||||
|
> 3. I did not really check the difference between the variable name and the
|
||||||
|
> name parameter of `TypeVar`, so idk what would happen if they are
|
||||||
|
> different. Please don't do that right now, would be fixed in later more
|
||||||
|
> serious implementations.
|
||||||
|
|
||||||
|
For generic type variables, you can't really do much with them, other than
|
||||||
|
passing them around in parameters, dealing with their list, etc.
|
||||||
|
|
||||||
|
For bounded type variables, if an operation is supported by all the possible
|
||||||
|
values of the variable, we can use that directly:
|
||||||
|
```python
|
||||||
|
A = TypeVar('A', int32, int64)
|
||||||
|
|
||||||
|
def add(a: A, b: A) -> A:
|
||||||
|
return a + b
|
||||||
|
```
|
||||||
|
|
||||||
|
If an operation is supported by some possible values, we can use type guard:
|
||||||
|
```python
|
||||||
|
A = TypeVar('A', int32, list[int32])
|
||||||
|
def add2(a: int32, b: A) -> a:
|
||||||
|
if type(b) == int32:
|
||||||
|
# b is int32 here
|
||||||
|
return a + b
|
||||||
|
else:
|
||||||
|
# b is list[int32] here
|
||||||
|
for x in b:
|
||||||
|
a = a + b
|
||||||
|
return a
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we only support very simple kinds of type guards in this toy
|
||||||
|
implementation. More specifically, the type guard has to meet the following
|
||||||
|
conditions:
|
||||||
|
1. The if statement must be of the form `type(*) == **` or `type(*) != **`.
|
||||||
|
For example, `if type(b) == int32 or type(b) == list[int32]` is not allowed.
|
||||||
|
2. The type of `*` must be a type variable. For example, `list[X]` is not
|
||||||
|
allowed.
|
||||||
|
|
||||||
|
### Substitution
|
||||||
The crucial constraint and assumption in our system is that, every
|
The crucial constraint and assumption in our system is that, every
|
||||||
(sub-)expressions must have their types fully determined, and cannot depend on
|
(sub-)expressions must have their types fully determined, and cannot depend on
|
||||||
statements/expressions after them. Hence, in a function call, every arguments
|
statements/expressions after them. Hence, in a function call, every arguments
|
||||||
@ -81,7 +157,7 @@ def sum_of_heads(a: list[Y], b: list[Y]) -> Y:
|
|||||||
|
|
||||||
In this example, `a` has type `list[Y]`, so the algorithm would give a
|
In this example, `a` has type `list[Y]`, so the algorithm would give a
|
||||||
substitution `X -> Y` for the call `head(a)`, and similarly for `b`.
|
substitution `X -> Y` for the call `head(a)`, and similarly for `b`.
|
||||||
As `Y` can only range over `int32` and `int64`, in the two valuations of `Y`,
|
As `Y` can only range over `int32` and `int64`, in the two instances of `Y`,
|
||||||
the return statement would have type
|
the return statement would have type
|
||||||
* `int32 + int32 : int32 : Y` under `Y -> int32`, and
|
* `int32 + int32 : int32 : Y` under `Y -> int32`, and
|
||||||
* `int64 + int64 : int64 : Y` under `Y -> int64`.
|
* `int64 + int64 : int64 : Y` under `Y -> int64`.
|
||||||
@ -96,6 +172,7 @@ def add(a: int32, b: I) -> int32:
|
|||||||
if type(b) == int32:
|
if type(b) == int32:
|
||||||
return a + b
|
return a + b
|
||||||
else:
|
else:
|
||||||
|
# b must be list[int32] in this branch.
|
||||||
for x in b:
|
for x in b:
|
||||||
a = add(a, x)
|
a = add(a, x)
|
||||||
return a
|
return a
|
||||||
@ -105,7 +182,21 @@ add(1, [1, 2, 3])
|
|||||||
This one should type check. `I -> list[int32]` only affects 1 call,
|
This one should type check. `I -> list[int32]` only affects 1 call,
|
||||||
and the recursion inside could substitute `I -> int32`.
|
and the recursion inside could substitute `I -> int32`.
|
||||||
|
|
||||||
## Variable Scoping
|
Example of a failure case:
|
||||||
|
```python
|
||||||
|
A = TypeVar('A')
|
||||||
|
B = TypeVar('B')
|
||||||
|
|
||||||
|
def foo(a: A, b: A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def bar(a: A, b: B):
|
||||||
|
foo(a, b)
|
||||||
|
```
|
||||||
|
|
||||||
|
This would fail. From the first argument, we have `A -> A`, and from the second
|
||||||
|
argument we need `A -> B`. In general, we may have `A != B`, so there is no
|
||||||
|
substitution that meets the requirement and the type check failed.
|
||||||
|
|
||||||
## Operator Override
|
## Operator Override
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user