implemented signature checker

pull/14/head
pca006132 2020-12-17 14:50:51 +08:00 committed by pca006132
parent a003ebb14e
commit 606965ce9f
3 changed files with 246 additions and 0 deletions

View File

@ -0,0 +1,31 @@
import ast
from type_def import *
from top_level import *
test = """
class A:
a: int
def foo(a: B) -> int:
pass
class B(A):
a: str
def bar(a: list[list[virtual[A]]]) -> A:
pass
"""
variables = {'X': TypeVariable('X', []), 'Y': TypeVariable('Y', [])}
types = {'int': PrimitiveType('int'), 'str': PrimitiveType('str')}
ctx = Context(variables, types)
ctx, functions, _ = parse_top_level(ctx, ast.parse(test))
for name, t in ctx.types.items():
if isinstance(t, ClassType):
print(f"class {t.name}")
for name, ty in t.fields.items():
print(f" {name}: {ty}")
for name, (args, result, _) in t.methods.items():
print(f" {name}: ({', '.join([str(v) for v in args])}) -> {result}")

141
toy-impl/top_level.py Normal file
View File

@ -0,0 +1,141 @@
import ast
from type_def import *
class CustomError(Exception):
def __init__(self, msg):
self.msg = msg
class Context:
variables: dict[str, TypeVariable]
types: dict[Type]
def __init__(self, variables, types):
self.variables = variables
self.types = types
def parse_type(ctx: Context, ty):
if ty is None:
return None
elif isinstance(ty, ast.Name):
if ty.id in ctx.types:
return ctx.types[ty.id], set()
elif ty.id in ctx.variables:
return ctx.variables[ty.id], {ty.id}
else:
raise CustomError(f"Unbounded Type {ty.id}")
elif isinstance(ty, ast.Subscript):
if isinstance(ty.value, ast.Name):
generic = ty.value.id
else:
raise CustomError(f"Unknown Generic Type {ty.value}")
if not isinstance(ty.slice, ast.Name) and not isinstance(ty.slice, ast.Tuple) \
and not isinstance(ty.slice, ast.Subscript):
raise CustomError(f"Generic Type of the form {ty.slice} is not supported")
if generic == 'tuple':
if not isinstance(ty.slice, ast.Tuple):
raise CustomError(f"Generic Type of the form {ty} is not supported")
param = []
var = set()
for t in ty.slice.elts:
p, v = parse_type(ctx, t)
param.append(p)
var |= v
return TupleType(param), var
elif generic == 'list':
param, var = parse_type(ctx, ty.slice)
return ListType(param), var
elif generic == 'virtual':
param, var = parse_type(ctx, ty.slice)
if not isinstance(param, ClassType):
raise CustomError(f"Parameter of virtual must be a class instead of {param}")
return VirtualClassType(param), var
else:
raise CustomError(f"Unknown Generic Type {ty.value}")
def parse_function(ctx: Context, base, fn: ast.FunctionDef):
args = []
var = set()
for arg in fn.args.args:
name = arg.arg
ty, v = parse_type(ctx, arg.annotation)
var |= v
if name == 'self' and ty is None and base is not None:
ty = base
args.append(ty)
result, v = parse_type(ctx, fn.returns)
if len(v - var) > 0:
raise CustomError(f"Unbounded variable in return type of {fn.name}")
return args, result, var
def parse_class(ctx, c: ast.ClassDef):
node = ctx.types[c.name]
functions = []
for base in c.bases:
if not isinstance(base, ast.Name):
raise CustomError(f"Base class of the form {base} is not supported")
name = base.id
if name not in ctx.types:
raise CustomError(f"Unbounded base class name {base}")
if not isinstance(ctx.types[name], ClassType):
raise CustomError(f"Base class must be a class instead of {base}")
node.parents.append(ctx.types[name])
for stmt in c.body:
if isinstance(stmt, ast.AnnAssign):
if not isinstance(stmt.target, ast.Name):
raise CustomError(f"Assignment of the form {stmt.target} is not supported")
field = stmt.target.id
if field in node.fields:
raise CustomError(f"Duplicated fields {field} in {c.name}")
ty, var = parse_type(ctx, stmt.annotation)
if len(var) > 0:
raise CustomError(f"Type variable is not allowed in class fields")
if ty == None:
raise CustomError(f"{field} of {c.name} is not annotated")
node.fields[field] = ty
elif isinstance(stmt, ast.FunctionDef):
name = stmt.name
if name in node.methods:
raise CustomError(f"Duplicated method {name} in {c.name}")
args, result, var = parse_function(ctx, node, stmt)
node.methods[name] = (args, result, var)
functions.append((c.name, name, stmt))
else:
raise CustomError(f"{stmt} is not supported")
return functions
def parse_top_level(ctx: Context, module: ast.Module):
to_be_processed = []
# first pass, obtain all type names
for element in module.body:
if isinstance(element, ast.ClassDef):
name = element.name
if name in ctx.types or name in ctx.variables:
raise CustomError(f"Duplicated class name: {name}")
ctx.types[name] = ClassType(name)
to_be_processed.append(element)
elif isinstance(element, ast.FunctionDef):
to_be_processed.append(element)
# second pass, obtain all function types
functions = {}
function_stmts = []
for element in to_be_processed:
if isinstance(element, ast.ClassDef):
function_stmts += parse_class(ctx, element)
elif isinstance(element, ast.FunctionDef):
name = element.name
if name in functions:
raise CustomError(f"Duplicated function name {name}")
args, result, var = parse_function(ctx, None, element)
functions[name] = (args, result, var)
function_stmts += element
return ctx, functions, function_stmts

74
toy-impl/type_def.py Normal file
View File

@ -0,0 +1,74 @@
class Type:
pass
class BotType:
pass
class PrimitiveType(Type):
name: str
def __init__(self, name: str):
self.name = name
def __str__(self):
return self.name
class TypeVariable(Type):
name: str
constraints: list[Type]
def __init__(self, name: str, constraints: list[Type]):
self.name = name
self.constraints = constraints
def __str__(self):
return self.name
class ClassType(Type):
name: str
parents: list['ClassType']
methods: dict[str, tuple[list[Type], Type, set[str]]]
fields: dict[str, Type]
def __init__(self, name: str):
self.name = name
self.parents = []
self.methods = {}
self.fields = {}
def __str__(self):
return self.name
class VirtualClassType(Type):
base: ClassType
def __init__(self, base: ClassType):
self.base = base
def __str__(self):
return f"virtual[{self.base}]"
class ListType(Type):
elements: Type
def __init__(self, elements: Type):
self.elements = elements
def __str__(self):
return f"list[{self.elements}]"
class TupleType(Type):
elements: list[Type]
def __init__(self, elements: Type):
self.elements = elements
def __str__(self):
return f"tuple[{', '.join([str(v) for v in self.elements])}]"