updated readme

pull/14/head
pca006132 2020-12-23 16:48:20 +08:00 committed by pca006132
parent 6f4ad20b7d
commit dd419fc120
1 changed files with 94 additions and 3 deletions

View File

@ -37,7 +37,83 @@ Here is the list of files and their purpose:
python AST.
* `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
(sub-)expressions must have their types fully determined, and cannot depend on
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
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
* `int32 + int32 : int32 : Y` under `Y -> int32`, and
* `int64 + int64 : int64 : Y` under `Y -> int64`.
@ -96,6 +172,7 @@ def add(a: int32, b: I) -> int32:
if type(b) == int32:
return a + b
else:
# b must be list[int32] in this branch.
for x in b:
a = add(a, x)
return a
@ -105,7 +182,21 @@ add(1, [1, 2, 3])
This one should type check. `I -> list[int32]` only affects 1 call,
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