blob: 1aa0fe5d91abe089f434400ae616d0d63ac5294e [file] [edit]
(*
This transformation replaces SubE expressions with explicit subtype injection
functions.
1. It traverses all expressions and finds out which type pairs
occur in SubE expressions
- all type pairs mentioned in SubE expressions
- for all variant types: list of constructors
- for all alias types: right hand side of the alias
2. It traverses all definitions to collect information about variant types and
type aliases (assuming only such types occur in type aliases).
3. It generates explicit injection functions for pairs, and put them in the
right spot (after both types are defined, but outside `RecD` groups)
4. It replaces occurrences of SubE with a suitable CallE
Step 1 and 4 are done together, and step 2 and 3
This pass assumes that there is no name shadowing in the type definitions.
*)
open Util
open Source
open Il.Ast
(* Errors *)
let error at msg = Error.error at "subtype elimination" msg
(* Environment *)
module M = Map.Make(String)
module S = Set.Make(struct
type t = id * id
let compare (t1, t2) (t3, t4) = compare (t1.it, t2.it) (t3.it, t4.it)
end)
(*
The environment consist of:
* Which constructors the type has (and their non-aliased concrete type)
* Which SubE type pairs have been observed, but not yet generated
*)
type env =
{ mutable typ : (param list * id * arg list * typcase list) M.t;
mutable pairs : S.t
}
let new_env () : env =
{ typ = M.empty;
pairs = S.empty;
}
let lookup (env : env) (id : id) : param list * id * arg list * typcase list =
match M.find_opt id.it env.typ with
| None -> error id.at ("unknown type `" ^ id.it ^ "`")
| Some t -> t
let arg_of_param param =
match param.it with
| ExpP (id, t) -> ExpA (VarE id $$ param.at % t) $ param.at
| TypP id -> TypA (VarT (id, []) $ param.at) $ param.at
| DefP (id, _ps, _t) -> DefA id $ param.at
| GramP (id, _ps, _t) -> GramA (VarG (id, []) $ param.at) $ param.at
let register_variant (env : env) (id : id) params (cases : typcase list) =
if M.mem id.it env.typ then
error id.at ("duplicate declaration for type `" ^ id.it ^ "`")
else
env.typ <- M.add id.it (params, id, List.map arg_of_param params, cases) env.typ
let subst_of_args =
List.fold_left2 (fun s arg param ->
match arg.it, param.it with
| ExpA e, ExpP (id, _) -> Il.Subst.add_varid s id e
| TypA t, TypP id -> Il.Subst.add_typid s id t
| DefA x, DefP (id, _, _) -> Il.Subst.add_defid s id x
| _, _ -> assert false
) Il.Subst.empty
let register_alias (env : env) (id : id) params (id2 : id) args =
match M.find_opt id2.it env.typ with
| Some (params2, id3, args2, cases) ->
let s = subst_of_args args params2 in
let args' = Il.Subst.(subst_list subst_arg s args2) in
let cases' = Il.Subst.(subst_list subst_typcase s cases) in
env.typ <- M.add id.it (params, id3, args', cases') env.typ
| None -> () (* Not an alias of a variant type *)
let injection_name (sub : id) (sup : id) = sup.it ^ "_" ^ sub.it $ no_region
let var_of_typ typ = match typ.it with
| VarT (id, args) -> Some (id, args)
| NumT _ -> None
| _ -> error typ.at ("Non-variable or number type expression not supported `" ^ Il.Print.string_of_typ typ ^ "`")
(* Step 1 and 4: Collect SubE occurrences, and replace with function *)
(* The main transformation case *)
let rec t_exp env exp =
let exp' = t_exp2 env exp in
match exp'.it with
| SubE (e, sub_ty, sup_ty) ->
(Printf.eprintf "[sub @ %s] %s <: %s\n%!" (string_of_region exp'.at) (Il.Print.string_of_typ sub_ty) (Il.Print.string_of_typ sup_ty);
begin match var_of_typ sub_ty, var_of_typ sup_ty with
| Some (sub, args_sub), Some (sup, args_sup) ->
env.pairs <- S.add (sub, sup) env.pairs;
{ exp' with it = CallE (injection_name sub sup, args_sub @ args_sup @ [ExpA e $ e.at])}
| _, _ ->
Printf.eprintf "[sub @ %s REMAINS] %s <: %s\n%!" (string_of_region exp'.at) (Il.Print.string_of_typ sub_ty) (Il.Print.string_of_typ sup_ty);
exp'
end
)
| _ -> exp'
(* Traversal boilerplate *)
and t_typ env x = { x with it = t_typ' env x.it }
and t_typ' env = function
| VarT (id, args) -> VarT (id, t_args env args)
| (BoolT | NumT _ | TextT) as t -> t
| TupT xts -> TupT (List.map (fun (id, t) -> (id, t_typ env t)) xts)
| IterT (t, iter) -> IterT (t_typ env t, iter)
and t_deftyp env x = { x with it = t_deftyp' env x.it }
and t_deftyp' env = function
| AliasT t -> AliasT (t_typ env t)
| StructT typfields -> StructT (List.map (t_typfield env) typfields)
| VariantT typcases -> VariantT (List.map (t_typcase env) typcases)
and t_typfield env (atom, (t, quants, prems), hints) =
(atom, (t_typ env t, t_params env quants, t_prems env prems), hints)
and t_typcase env (atom, (t, quants, prems), hints) =
(atom, (t_typ env t, t_params env quants, t_prems env prems), hints)
and t_exp2 env x = { x with it = t_exp' env x.it; note = t_typ env x.note }
and t_exp' env = function
| (VarE _ | BoolE _ | NumE _ | TextE _) as e -> e
| UnE (unop, nto, exp) -> UnE (unop, nto, t_exp env exp)
| BinE (binop, nto, exp1, exp2) -> BinE (binop, nto, t_exp env exp1, t_exp env exp2)
| CmpE (cmpop, nto, exp1, exp2) -> CmpE (cmpop, nto, t_exp env exp1, t_exp env exp2)
| IdxE (exp1, exp2) -> IdxE (t_exp env exp1, t_exp env exp2)
| SliceE (exp1, exp2, exp3) -> SliceE (t_exp env exp1, t_exp env exp2, t_exp env exp3)
| UpdE (exp1, path, exp2) -> UpdE (t_exp env exp1, t_path env path, t_exp env exp2)
| ExtE (exp1, path, exp2) -> ExtE (t_exp env exp1, t_path env path, t_exp env exp2)
| StrE fields -> StrE (List.map (fun (a, e) -> a, t_exp env e) fields)
| DotE (e, a) -> DotE (t_exp env e, a)
| CompE (exp1, exp2) -> CompE (t_exp env exp1, t_exp env exp2)
| LiftE exp -> LiftE (t_exp env exp)
| LenE exp -> LenE (t_exp env exp)
| TupE es -> TupE (List.map (t_exp env) es)
| CallE (a, args) -> CallE (a, t_args env args)
| IterE (e, iterexp) -> IterE (t_exp env e, t_iterexp env iterexp)
| ProjE (e, i) -> ProjE (t_exp env e, i)
| UncaseE (e, mixop) -> UncaseE (t_exp env e, mixop)
| OptE None -> OptE None
| OptE (Some exp) -> OptE (Some exp)
| TheE exp -> TheE exp
| ListE es -> ListE (List.map (t_exp env) es)
| CatE (exp1, exp2) -> CatE (t_exp env exp1, t_exp env exp2)
| MemE (exp1, exp2) -> MemE (t_exp env exp1, t_exp env exp2)
| CaseE (mixop, e) -> CaseE (mixop, t_exp env e)
| CvtE (exp, t1, t2) -> CvtE (t_exp env exp, t1, t2)
| SubE (e, t1, t2) -> SubE (e, t1, t2)
and t_iter env = function
| ListN (e, id_opt) -> ListN (t_exp env e, id_opt)
| i -> i
and t_iterexp env (iter, vs) =
(t_iter env iter, List.map (fun (id, e) -> (id, t_exp env e)) vs)
and t_path' env = function
| RootP -> RootP
| IdxP (path, e) -> IdxP (t_path env path, t_exp env e)
| SliceP (path, e1, e2) -> SliceP (t_path env path, t_exp env e1, t_exp env e2)
| DotP (path, a) -> DotP (t_path env path, a)
and t_path env x = { x with it = t_path' env x.it; note = t_typ env x.note }
and t_sym' env = function
| VarG (id, args) -> VarG (id, t_args env args)
| (NumG _ | TextG _ | EpsG) as g -> g
| SeqG syms -> SeqG (List.map (t_sym env) syms)
| AltG syms -> AltG (List.map (t_sym env) syms)
| RangeG (sym1, sym2) -> RangeG (t_sym env sym1, t_sym env sym2)
| IterG (sym, iter) -> IterG (t_sym env sym, t_iterexp env iter)
| AttrG (e, sym) -> AttrG (t_exp env e, t_sym env sym)
and t_sym env x = { x with it = t_sym' env x.it }
and t_arg' env = function
| ExpA exp -> ExpA (t_exp env exp)
| TypA t -> TypA t
| DefA id -> DefA id
| GramA sym -> GramA (t_sym env sym)
and t_arg env x = { x with it = t_arg' env x.it }
and t_param' env = function
| ExpP (id, t) -> ExpP (id, t_typ env t)
| TypP id -> TypP id
| DefP (id, ps, t) -> DefP (id, t_params env ps, t_typ env t)
| GramP (id, ps, t) -> GramP (id, t_params env ps, t_typ env t)
and t_param env x = { x with it = t_param' env x.it }
and t_args env = List.map (t_arg env)
and t_params env = List.map (t_param env)
and t_prem' env = function
| RulePr (id, args, mixop, exp) -> RulePr (id, t_args env args, mixop, t_exp env exp)
| IfPr e -> IfPr (t_exp env e)
| LetPr (e1, e2, ids) -> LetPr (t_exp env e1, t_exp env e2, ids)
| ElsePr -> ElsePr
| IterPr (prem, iterexp) -> IterPr (t_prem env prem, t_iterexp env iterexp)
and t_prem env x = { x with it = t_prem' env x.it }
and t_prems env = List.map (t_prem env)
let t_clause' env = function
| DefD (params, lhs, rhs, prems) ->
DefD (t_params env params, (*DO NOT intro calls on LHS: t_args env*) lhs, t_exp env rhs, t_prems env prems)
let t_clause env (clause : clause) = { clause with it = t_clause' env clause.it }
let t_clauses env = List.map (t_clause env)
let t_inst' env = function
| InstD (params, args, deftyp) ->
InstD (t_params env params, (*DO NOT intro calls on LHS: t_args env*) args, t_deftyp env deftyp)
let t_inst env (inst : inst) = { inst with it = t_inst' env inst.it }
let t_insts env = List.map (t_inst env)
let t_prod' env = function
| ProdD (params, lhs, rhs, prems) ->
ProdD (t_params env params, t_sym env lhs, t_exp env rhs, t_prems env prems)
let t_prod env (prod : prod) = { prod with it = t_prod' env prod.it }
let t_prods env = List.map (t_prod env)
let t_rule' env = function
| RuleD (id, params, mixop, exp, prems) ->
RuleD (id, t_params env params, mixop, t_exp env exp, t_prems env prems)
let t_rule env x = { x with it = t_rule' env x.it }
let rec t_def' env = function
| RecD defs -> RecD (List.map (t_def env) defs)
| DecD (id, params, typ, clauses) ->
DecD (id, t_params env params, typ, t_clauses env clauses)
| TypD (id, params, insts) ->
TypD (id, t_params env params, t_insts env insts)
| RelD (id, params, mixop, typ, rules) ->
RelD (id, t_params env params, mixop, t_typ env typ, List.map (t_rule env) rules)
| GramD (id, params, typ, prods) ->
GramD (id, t_params env params, typ, t_prods env prods)
| HintD _ as def -> def
and t_def env (def : def) = { def with it = t_def' env def.it }
(* Step 2 and 3: Traverse definitions, collect type information, insert as soon as possible *)
let rec add_type_info env (def : def) = match def.it with
| RecD defs -> List.iter (add_type_info env) defs
| TypD (id, params, [inst]) -> (* TODO: handle type families *)
let InstD (_, _, deftyp) = inst.it in
begin match deftyp.it with
| VariantT cases -> register_variant env id params cases
| AliasT {it = VarT (id2, args); _} -> register_alias env id params id2 args
| _ -> ()
end
| _ ->()
let is_ready env (t1, t2) = M.mem t1.it env.typ && M.mem t2.it env.typ
(* Returns type pairs that are defined now, and removes them from the env *)
let ready_pairs (env : env) =
let (ready, todo) = S.partition (is_ready env) env.pairs in
env.pairs <- todo;
S.elements ready
(* Rename parameters to avoid name clashes *)
let rec rename_params s = function
| [] -> []
| { it = ExpP (id, t); at; _ } :: params ->
let id' = (id.it ^ "_2") $ id.at in
let t' = Il.Subst.subst_typ s t in
(ExpP (id', t') $ at) ::
rename_params (Il.Subst.add_varid s id (VarE id' $$ id.at % t')) params
| { it = TypP id; at; _ } :: params ->
let id' = (id.it ^ "_2") $ id.at in
(TypP id' $ at) ::
rename_params (Il.Subst.add_typid s id (VarT (id', []) $ id.at)) params
| { it = DefP (id, ps, t); at; _ } :: params ->
let id' = (id.it ^ "_2") $ id.at in
(DefP (id', ps, t) $ at) ::
rename_params (Il.Subst.add_defid s id id') params
| { it = GramP (id, ps, t); at; _ } :: params ->
let id' = (id.it ^ "_2") $ id.at in
(GramP (id', ps, t) $ at) ::
rename_params (Il.Subst.add_gramid s id (VarG (id', []) $ id.at)) params
let insert_injections env (def : def) : def list =
add_type_info env def;
let pairs = ready_pairs env in
[ def ] @
List.map (fun (sub, sup) ->
let name = injection_name sub sup in
let (params_sub, real_id_sub, args_sub, cases_sub) = lookup env sub in
let (params_sup, _, _, _) = lookup env sup in
let params_sup' = rename_params Il.Subst.empty params_sup in
let sub_ty = VarT (sub, List.map arg_of_param params_sub) $ no_region in
let sup_ty = VarT (sup, List.map arg_of_param params_sup') $ no_region in
let real_ty = VarT (real_id_sub, args_sub) $ no_region in
let clauses = List.map (fun (a, (arg_typ, _quants, _prems), _hints) ->
match arg_typ.it with
| TupT ts ->
let params = List.mapi (fun i (_, arg_typ_i) -> ExpP ("x" ^ string_of_int i $ no_region, arg_typ_i) $ no_region) ts in
let xes = List.map (fun param ->
match param.it with
| ExpP (x, arg_typ_i) -> VarE x $$ no_region % arg_typ_i
| TypP _ | DefP _ | GramP _ -> assert false) params
in
let xe = TupE xes $$ no_region % arg_typ in
DefD (params,
[ExpA (CaseE (a, xe) $$ no_region % real_ty) $ no_region],
CaseE (a, xe) $$ no_region % sup_ty, []) $ no_region
| _ ->
let x = "x" $ no_region in
let xe = VarE x $$ no_region % arg_typ in
DefD ([ExpP (x, arg_typ) $ x.at],
[ExpA (CaseE (a, xe) $$ no_region % real_ty) $ no_region],
CaseE (a, xe) $$ no_region % sup_ty, []) $ no_region
) cases_sub in
DecD (name, params_sub @ params_sup' @ [ExpP ("_" $ no_region, sub_ty) $ no_region], sup_ty, clauses) $ no_region
) pairs
let transform (defs : script) =
let env = new_env () in
let defs' = List.map (t_def env) defs in
let defs'' = List.concat_map (insert_injections env) defs' in
S.iter (fun (sub, sup) -> error sup.at ("left-over subtype coercion `" ^ sub.it ^ "` <: `" ^ sup.it ^ "`")) env.pairs;
defs''