Skip to content

Commit 017061d

Browse files
committed
Add a couple more cases to constraintgen
1 parent 7c066e4 commit 017061d

File tree

5 files changed

+98
-8
lines changed

5 files changed

+98
-8
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,40 @@ A Type Language for Python
33

44
https://github.com/kennknowles/python-typelanguage
55

6-
This module provides a type language for communicating about Python programs and values. Humans
7-
communicating to other humans, humans communicating to the computer, and even the computer
6+
This module provides a type language for communicating about Python programs and values.
7+
8+
Humans communicating to other humans, humans communicating to the computer, and even the computer
89
communicating to humans (via type inference and run-time contract checking).
910

11+
12+
The Types
13+
---------
14+
15+
This type language provides the following concepts:
16+
17+
* Atomic types `int`, `long`, `float`, `complex`, `str`, `unicode`
18+
* Compound types for tuples, lists, dictionaries, written `(int, str)`, `[int]`, {int: str}
19+
* Object types `object(field1: int, field2: string) `
20+
* Function types like `int -> int`, `str` (they can get much more complex in Python, though - see below)
21+
* Polymorphic types like `forall a. [a] -> [a]`
22+
23+
Function Types
24+
--------------
25+
26+
The basic type of e.g. `str -> str` is pretty easy. But what about named args? `*args`? `**kwargs`?
27+
We try to re-use the function call / declaration syntax also in the types, so they can look like this:
28+
29+
(int, *[int], **{int: str}) -> str
30+
31+
Type Inference
32+
--------------
33+
34+
In the spirit of Python and dynamic languages, type inference is best-effort. Based
35+
on the program, this module can discover constraints between types present at different
36+
parts of the program. This constraint set may well be undecidable, especially if your
37+
program gets crazy. In most sane code, types will be almost entirely inferrable.
38+
39+
1040
Further Reading:
1141
----------------
1242

tests/samples/literal_long.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3L

tests/test_constraintgen.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,14 @@ class TestConstraintGen(unittest.TestCase):
88
def parse_expr(self, expr_string):
99
return ast.parse(expr_string).body[0].value
1010

11-
def test_literals(self):
11+
def test_atomic_literals(self):
1212
assert constraintgen.constraints_expr(self.parse_expr('"hello"')).type == types.AtomicType('str')
13+
assert constraintgen.constraints_expr(self.parse_expr('u"hello"')).type == types.AtomicType('str')
14+
assert constraintgen.constraints_expr(self.parse_expr('3')).type == types.AtomicType('int')
15+
assert constraintgen.constraints_expr(self.parse_expr('3L')).type == types.AtomicType('long')
16+
assert constraintgen.constraints_expr(self.parse_expr('-2.5')).type == types.AtomicType('float')
17+
assert constraintgen.constraints_expr(self.parse_expr('7.40J')).type == types.AtomicType('complex')
18+
19+
def test_list_literals(self):
20+
for expr in ['[]', '[3]', '["hello"]', '[[]]' ]:
21+
assert isinstance(constraintgen.constraints_expr(self.parse_expr(expr)).type, types.ListType)

typelanguage/constraintgen.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def constraints(pyast, env=None):
5454

5555
elif isinstance(pyast, ast.Expression):
5656
expr_ty = constraints_expr(pyast.body, env=env)
57-
return Constrained_env(env=env, constraints=expr_ty.constraints)
57+
return ConstrainedEnv(env=env, constraints=expr_ty.constraints)
5858

5959
else:
6060
raise Exception('Unknown ast node: %s' % pyast)
@@ -129,16 +129,29 @@ def constraints_expr(expr, env=None):
129129
env = env or {}
130130

131131
if isinstance(expr, ast.Name) and isinstance(expr.ctx, ast.Load):
132-
if expr.id in env:
132+
if expr.id in ['False', 'True']: # Unlike other literals, these are actually just global identifiers
133+
return ConstrainedType(type=types.AtomicType('bool'))
134+
elif expr.id in env:
133135
return ConstrainedType(type=env[expr.id])
134136
else:
135137
raise Exception('Variable not found in environment: %s' % expr.id)
136138

137139
elif isinstance(expr, ast.Num):
138-
return ConstrainedType(type=types.AtomicType('num'))
140+
# The python ast module already chose the type of the num
141+
if isinstance(expr.n, int):
142+
return ConstrainedType(type=types.AtomicType('int'))
143+
elif isinstance(expr.n, long):
144+
return ConstrainedType(type=types.AtomicType('long'))
145+
elif isinstance(expr.n, float):
146+
return ConstrainedType(type=types.AtomicType('float'))
147+
elif isinstance(expr.n, complex):
148+
return ConstrainedType(type=types.AtomicType('complex'))
139149

140150
elif isinstance(expr, ast.Str):
141151
return ConstrainedType(type=types.AtomicType('str'))
152+
153+
elif isinstance(expr, ast.List):
154+
return ConstrainedType(type=types.ListType(elem_ty=types.fresh()))
142155

143156
elif isinstance(expr, ast.BinOp):
144157
left = constraints_expr(expr.left, env=env)
@@ -161,5 +174,7 @@ def constraints_expr(expr, env=None):
161174
if __name__ == '__main__':
162175
with open(sys.argv[1]) as fh:
163176
proggy = ast.parse(fh.read())
177+
178+
print ast.dump(proggy)
164179

165-
print constraints({}, proggy).pretty()
180+
print constraints(proggy).pretty()

typelanguage/types.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
from collections import namedtuple, defaultdict
2-
2+
3+
# Kinds
4+
5+
class Kind(object): pass
6+
7+
# The kind of types
8+
class Type(Kind): pass
9+
10+
# The kind of record field names
11+
class Label(Kind): pass
12+
13+
class FunctionKind(Kind):
14+
def __init__(self, dom, rng):
15+
self.dom = dom
16+
self.rng = rng
17+
18+
19+
## Types proper
20+
321
class Type(object): pass
422

523
class AtomicType(Type):
@@ -14,6 +32,20 @@ def __str__(self):
1432

1533
def __eq__(self, other):
1634
return isinstance(other, AtomicType) and other.name == self.name
35+
36+
# TODO: Make into a higher-kinded type? Maybe that's just a headache?
37+
class ListType(Type):
38+
def __init__(self, elem_ty):
39+
self.elem_ty = elem_ty
40+
41+
def substitute(self, substitution):
42+
return ListType(self.elem_ty.substitue(substitution))
43+
44+
def __str__(self):
45+
return '[%s]' % self.elem_ty
46+
47+
def __eq__(self):
48+
return isinstance(other, ListType) and other.elem_ty == self.elem_ty
1749

1850
class TypeVariable(Type):
1951
def __init__(self, name):
@@ -69,6 +101,9 @@ class UnionType(Type):
69101
def __init__(self, types):
70102
self.types = types
71103

104+
105+
# Fresh variable supply
106+
72107
used_vars = defaultdict(lambda: 0)
73108
def fresh(prefix=None):
74109
global used_vars

0 commit comments

Comments
 (0)