def check_atomic(cls, env: TypeCheckEnv, prog: str) -> dslT.Type: # Try to interpret it as an integer or boolean first try: int(prog) return deepcopy(lang.T_INT) except ValueError: if prog in lang.bool_map: return deepcopy(lang.T_BOOL) if env.contains_fun(prog): return env.get_fun_def(prog) # If the program isn't a boolean or integer, it must be a variable. Look it up in the current context # A variable's entry is set to None once a linear or affine judgement about it has been used. If we're # trying to use it again, then throw an error if env.get_bind_val(prog).is_borrow(): raise tc_err.LinAffineVariableReuseError( f'{prog} attempts to use borrowed value') # If the judgement is unrestricted, then we can use it without any worry. If not, then we have to remove # the judgement from the context after using it. t = env.get_bind_val(prog) if not t.is_un(): t.set_borrow() return t
def check_while(cls, env: TypeCheckEnv, test, default, body): test_type = cls.type_check(env, test) if not dslT.Type.is_subtype(test_type, lang.T_LIN_BOOL): raise tc_err.TypeMismatchError( f"While statement conditional was not bool, but {str(test_type)}" ) old_env = deepcopy_env(env) new_env_def = TypeCheckEnv(outer=deepcopy_env(env)) new_env_bod = TypeCheckEnv(outer=env) def_type = cls.type_check(new_env_def, default, descope=True) bod_type = cls.type_check(new_env_bod, body, descope=True) if new_env_def.outer != old_env: raise tc_err.EnvironmentMismatchError( "Default clause illegally modifies environment") elif new_env_bod.outer != old_env: raise tc_err.EnvironmentMismatchError( "Body clause illegally modifies environment") if not def_type == bod_type: raise tc_err.TypeMismatchError( f"Default clause returns {str(def_type)}, while Body clause returns str{str(bod_type)}" ) return def_type
def type_check(cls, env: TypeCheckEnv, prog: Union[Tuple, str], descope: bool = False, being_bound: bool = False) -> dslT.Type: """ Given a context Gamma and program tree, type-check it (modifying gamma along the way) :param being_bound: Don't allow orphaning of linear types, essentially :param descope: :param env: :param prog: :return: """ ################################################################################# # If the program isn't a tree, then it should be an integer, string, or boolean # ################################################################################# macro_tcheck_fns = { "defvar": cls.check_defvar, "defun": cls.check_defun, "setrefval": cls.check_setrefval, "mkref": cls.check_mkref, "deref": cls.check_deref, "set": cls.check_set, "apply": cls.check_apply, "if": cls.check_if, "while": cls.check_while, "scope": cls.check_scope, } if not (isinstance(prog, tuple)): ret = cls.check_atomic(env, prog) elif len(prog) == 0: # The empty list is always interpreted as nil. ret = deepcopy(lang.T_NIL) elif prog[0] in macro_tcheck_fns: ret = macro_tcheck_fns[prog[0]](env, *prog[1:]) else: # We performed the check for zero-length above, so None will never actually be returned ret = cls.check_sequential(env, prog) if not being_bound: if ret.is_lin(): raise tc_err.UnusedLinVariableError() elif prog[0] == 'mkref': raise tc_err.ReferenceNoEffectError if descope: env.deallocate() return ret
def check_defvar(cls, env: TypeCheckEnv, name, declared_tprog, init_prog): signature_t = dslT.tparse(declared_tprog) valprog_t = cls.type_check(env, init_prog, being_bound=True) if not dslT.Type.is_subtype(valprog_t, signature_t): raise tc_err.TypeMismatchError( f'Binding {name} expects variable of type {signature_t} ' f'but got {valprog_t}, which is not a subtype') else: if isinstance(signature_t, dslT.RefType): signature_t.borrow_parent = valprog_t.borrow_parent valprog_t.set_borrow() env.define_bind(name, signature_t) return deepcopy(lang.T_UNIT)
def check_mkref(cls, env: TypeCheckEnv, var): ref_type = env.get_bind_val(var) if not ref_type.is_own(): raise tc_err.BorrowedValueUseError( f"Attempted to make reference to {var}, which is currently borrowed" ) ref_type.set_borrow() return dslT.RefType(mod=lang.Tmod.un, ref_type=ref_type, borrow_parent=ref_type)
def check_set(cls, env: TypeCheckEnv, name, new_def): signature_t = env.get_bind_val(name) valprog_t = cls.type_check(env, new_def, being_bound=True) if not dslT.Type.is_subtype(valprog_t, signature_t): raise tc_err.TypeMismatchError( f'Binding {name} expects variable of type {signature_t} ' f'but got {valprog_t}, which is not a subtype') else: if isinstance(signature_t, dslT.RefType): signature_t.borrow_parent = valprog_t.borrow_parent valprog_t.set_borrow() if signature_t.is_borrow(): env.get_bind_val(name).set_own() elif signature_t.is_own() and signature_t.is_lin(): raise tc_err.TypeMismatchError( "Trying to set variable which owns value (could leak memory this way)" ) return deepcopy(lang.T_UNIT)
def check_apply(cls, env: TypeCheckEnv, fname, *fargs): ftype = cls.type_check(env, fname) assert isinstance(ftype, dslT.FunType) fsig_arg_t_ls = ftype.argTs actual_arg_t_ls = () for arg_name in fargs: actual_arg_t_ls += (cls.type_check(env, arg_name, being_bound=True), ) if len(actual_arg_t_ls) != len(fsig_arg_t_ls): raise RuntimeError("Didn't pass in right number of arguments!") for i, (actual_argT, sigT) in enumerate(zip(actual_arg_t_ls, ftype.argTs)): if isinstance(fsig_arg_t_ls[i], dslT.RefType): assert env.contains_bind(fargs[i]) assert env.get_bind_val(fargs[i]).is_own() env.get_bind_val(fargs[i]).set_borrow() if not dslT.Type.is_subtype(actual_arg_t_ls[i], fsig_arg_t_ls[i]): raise tc_err.TypeMismatchError( f'Argument {i} expected to be {str(sigT)}, got {str(actual_argT)}' ) # Unborrow all the things passed in as arguments for i, bname in enumerate(fargs): if isinstance(fsig_arg_t_ls[i], dslT.RefType): env.get_bind_val(fargs[i]).set_own_unconditional() return ftype.retT
def check_if(cls, env: TypeCheckEnv, test, then_body, else_body): test_type = cls.type_check(env, test) if not dslT.Type.is_subtype(test_type, lang.T_LIN_BOOL): raise tc_err.TypeMismatchError( f"If statement conditional is not bool, but {str(test_type)}") new_env_then = TypeCheckEnv(outer=deepcopy_env(env)) new_env_else = TypeCheckEnv(outer=env) then_type = cls.type_check(new_env_then, then_body, descope=True) else_type = cls.type_check(new_env_else, else_body, descope=True) # TODO Confirm that this works if new_env_then.outer != env: raise tc_err.EnvironmentMismatchError( "Branches of if statement produce different environments") if then_type != else_type: raise tc_err.TypeMismatchError( f"Then body type {str(then_type)} does not equal else body type {str(else_type)}" ) return then_type
def check_deref(cls, env: TypeCheckEnv, refname): ref_type = env.get_bind_val(refname) assert isinstance(ref_type, dslT.RefType) if not ref_type.is_own(): raise RuntimeError("Attempt to dereference borrowed reference!") ref_base_type = ref_type.referenced_type() if ref_base_type.is_un(): ret_type = deepcopy(ref_base_type) ret_type.set_own_unconditional() else: raise tc_err.DerefNonCopyError() return ret_type
def check_defun(cls, env: TypeCheckEnv, fname, sig_ret_tprog, arg_spec_ls, *body): # Functions don't close over enclosing values, so declare a new env that the body will be type-checking in new_env = TypeCheckEnv(defaults=env.functions) # To type-check the body, first assume that all arguments have the declared types... arg_t_ls = () for arg_name, arg_tprog in arg_spec_ls: t = dslT.tparse(arg_tprog) # Need to manually set up dummy parents for all reference types... if isinstance(t, dslT.RefType): t.borrow_parent = deepcopy(t.referenced_type()) t.borrow_parent.set_borrow() new_env.define_bind(arg_name, t) arg_t_ls += (new_env.get_bind_val(arg_name), ) sig_ret_t = dslT.tparse(sig_ret_tprog) # And then check the body to see what is returned in the end. Set descope=True to make sure there aren't any # linear judgements unused # We actually assume that the function has the correct signature to allow type-checking recursive functions new_env.define_fun( fname, dslT.FunType(mod=lang.Tmod.un, retT=sig_ret_t, argTs=arg_t_ls)) actual_ret_t = cls.type_check(new_env, body, descope=True) # Make sure that the actual return value is a subtype of the signature return value if not dslT.Type.is_subtype(actual_ret_t, sig_ret_t): raise tc_err.TypeMismatchError( f'Function actually returns {actual_ret_t}, ' f'which is not a subtype of declared return {sig_ret_t}') else: env.define_fun( fname, dslT.FunType(mod=lang.Tmod.un, retT=sig_ret_t, argTs=arg_t_ls)) return deepcopy(lang.T_UNIT)
def check_setrefval(cls, env: TypeCheckEnv, ref_name, new_def): ref_type = env.get_bind_val(ref_name) if not isinstance(ref_type, dslT.RefType): raise RuntimeError( "Attempted to use setrefval on a non-reference!") if not ref_type.is_own(): raise RuntimeError( "Attempting to set through a borrowed reference") if ref_type.referenced_type().is_lin(): raise tc_err.UnusedLinVariableError( "Attempt to set an owned linear variable through a reference") new_def_type = cls.type_check(env, new_def, being_bound=True) if not new_def_type.is_own(): raise RuntimeError( "The definition form evaluates to something borrowed!") elif not new_def_type.eq_ignore_oship(ref_type.referenced_type()): raise tc_err.TypeMismatchError( f"{ref_name} is reference to {ref_type.referenced_type()}, " f"but set was attempted with {new_def_type}") return deepcopy(lang.T_UNIT)
def base_tcheck_env(): return TypeCheckEnv(defaults=lang.builtin_fn_types)
def check_scope(cls, env: TypeCheckEnv, *body): new_frame = TypeCheckEnv(outer=env) return cls.type_check(new_frame, body, descope=True)