diff options
Diffstat (limited to 'stdlib/source/library/lux/type/check.lux')
-rw-r--r-- | stdlib/source/library/lux/type/check.lux | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/stdlib/source/library/lux/type/check.lux b/stdlib/source/library/lux/type/check.lux new file mode 100644 index 000000000..a8b447338 --- /dev/null +++ b/stdlib/source/library/lux/type/check.lux @@ -0,0 +1,721 @@ +(.module: {#.doc "Type-checking functionality."} + [library + [lux #* + ["@" target] + [abstract + [functor (#+ Functor)] + [apply (#+ Apply)] + ["." monad (#+ Monad do)]] + [control + ["." try (#+ Try)] + ["." exception (#+ Exception exception:)]] + [data + ["." maybe] + ["." product] + ["." text ("#\." monoid equivalence)] + [collection + ["." list] + ["." set (#+ Set)]]] + [math + [number + ["n" nat ("#\." decimal)]]]]] + ["." // ("#\." equivalence)]) + +(template: (!n\= reference subject) + ("lux i64 =" reference subject)) + +(template: (!text\= reference subject) + ("lux text =" reference subject)) + +(exception: #export (unknown_type_var {id Nat}) + (exception.report + ["ID" (n\encode id)])) + +(exception: #export (unbound_type_var {id Nat}) + (exception.report + ["ID" (n\encode id)])) + +(exception: #export (invalid_type_application {funcT Type} {argT Type}) + (exception.report + ["Type function" (//.format funcT)] + ["Type argument" (//.format argT)])) + +(exception: #export (cannot_rebind_var {id Nat} {type Type} {bound Type}) + (exception.report + ["Var" (n\encode id)] + ["Wanted Type" (//.format type)] + ["Current Type" (//.format bound)])) + +(exception: #export (type_check_failed {expected Type} {actual Type}) + (exception.report + ["Expected" (//.format expected)] + ["Actual" (//.format actual)])) + +(type: #export Var + Nat) + +(type: Assumption + [Type Type]) + +(type: #export (Check a) + (-> Type_Context (Try [Type_Context a]))) + +(type: (Checker a) + (-> (List Assumption) a a (Check (List Assumption)))) + +(type: Type_Vars + (List [Var (Maybe Type)])) + +(implementation: #export functor + (Functor Check) + + (def: (map f fa) + (function (_ context) + (case (fa context) + (#try.Success [context' output]) + (#try.Success [context' (f output)]) + + (#try.Failure error) + (#try.Failure error))))) + +(implementation: #export apply + (Apply Check) + + (def: &functor ..functor) + + (def: (apply ff fa) + (function (_ context) + (case (ff context) + (#try.Success [context' f]) + (case (fa context') + (#try.Success [context'' a]) + (#try.Success [context'' (f a)]) + + (#try.Failure error) + (#try.Failure error)) + + (#try.Failure error) + (#try.Failure error) + ))) + ) + +(implementation: #export monad + (Monad Check) + + (def: &functor ..functor) + + (def: (wrap x) + (function (_ context) + (#try.Success [context x]))) + + (def: (join ffa) + (function (_ context) + (case (ffa context) + (#try.Success [context' fa]) + (case (fa context') + (#try.Success [context'' a]) + (#try.Success [context'' a]) + + (#try.Failure error) + (#try.Failure error)) + + (#try.Failure error) + (#try.Failure error) + ))) + ) + +(open: "check\." ..monad) + +(def: (var::new id plist) + (-> Var Type_Vars Type_Vars) + (#.Cons [id #.None] plist)) + +(def: (var::get id plist) + (-> Var Type_Vars (Maybe (Maybe Type))) + (case plist + (#.Cons [var_id var_type] + plist') + (if (!n\= id var_id) + (#.Some var_type) + (var::get id plist')) + + #.Nil + #.None)) + +(def: (var::put id value plist) + (-> Var (Maybe Type) Type_Vars Type_Vars) + (case plist + #.Nil + (list [id value]) + + (#.Cons [var_id var_type] + plist') + (if (!n\= id var_id) + (#.Cons [var_id value] + plist') + (#.Cons [var_id var_type] + (var::put id value plist'))))) + +(def: #export (run context proc) + (All [a] (-> Type_Context (Check a) (Try a))) + (case (proc context) + (#try.Success [context' output]) + (#try.Success output) + + (#try.Failure error) + (#try.Failure error))) + +(def: #export (fail message) + (All [a] (-> Text (Check a))) + (function (_ context) + (#try.Failure message))) + +(def: #export (assert message test) + (-> Text Bit (Check Any)) + (function (_ context) + (if test + (#try.Success [context []]) + (#try.Failure message)))) + +(def: #export (throw exception message) + (All [e a] (-> (Exception e) e (Check a))) + (..fail (exception.construct exception message))) + +(def: #export existential + {#.doc "A producer of existential types."} + (Check [Nat Type]) + (function (_ context) + (let [id (get@ #.ex_counter context)] + (#try.Success [(update@ #.ex_counter inc context) + [id (#.Ex id)]])))) + +(template [<name> <outputT> <fail> <succeed>] + [(def: #export (<name> id) + (-> Var (Check <outputT>)) + (function (_ context) + (case (|> context (get@ #.var_bindings) (var::get id)) + (^or (#.Some (#.Some (#.Var _))) + (#.Some #.None)) + (#try.Success [context <fail>]) + + (#.Some (#.Some bound)) + (#try.Success [context <succeed>]) + + #.None + (exception.throw ..unknown_type_var id))))] + + [bound? Bit false true] + [read (Maybe Type) #.None (#.Some bound)] + ) + +(def: #export (read! id) + (-> Var (Check Type)) + (do ..monad + [?type (read id)] + (case ?type + (#.Some type) + (wrap type) + + #.None + (..throw ..unbound_type_var id)))) + +(def: (peek id) + (-> Var (Check Type)) + (function (_ context) + (case (|> context (get@ #.var_bindings) (var::get id)) + (#.Some (#.Some bound)) + (#try.Success [context bound]) + + (#.Some _) + (exception.throw ..unbound_type_var id) + + _ + (exception.throw ..unknown_type_var id)))) + +(def: #export (bind type id) + (-> Type Var (Check Any)) + (function (_ context) + (case (|> context (get@ #.var_bindings) (var::get id)) + (#.Some #.None) + (#try.Success [(update@ #.var_bindings (var::put id (#.Some type)) context) + []]) + + (#.Some (#.Some bound)) + (exception.throw ..cannot_rebind_var [id type bound]) + + _ + (exception.throw ..unknown_type_var id)))) + +(def: (update type id) + (-> Type Var (Check Any)) + (function (_ context) + (case (|> context (get@ #.var_bindings) (var::get id)) + (#.Some _) + (#try.Success [(update@ #.var_bindings (var::put id (#.Some type)) context) + []]) + + _ + (exception.throw ..unknown_type_var id)))) + +(def: #export var + (Check [Var Type]) + (function (_ context) + (let [id (get@ #.var_counter context)] + (#try.Success [(|> context + (update@ #.var_counter inc) + (update@ #.var_bindings (var::new id))) + [id (#.Var id)]])))) + +(def: (apply_type! funcT argT) + (-> Type Type (Check Type)) + (case funcT + (#.Var func_id) + (do ..monad + [?funcT' (read func_id)] + (case ?funcT' + (#.Some funcT') + (apply_type! funcT' argT) + + _ + (throw ..invalid_type_application [funcT argT]))) + + (#.Apply argT' funcT') + (do ..monad + [funcT'' (apply_type! funcT' argT')] + (apply_type! funcT'' argT)) + + _ + (case (//.apply (list argT) funcT) + (#.Some output) + (check\wrap output) + + _ + (throw ..invalid_type_application [funcT argT])))) + +(type: Ring + (Set Var)) + +(def: empty_ring + Ring + (set.new n.hash)) + +## TODO: Optimize this by not using sets anymore. +(def: (ring start) + (-> Var (Check Ring)) + (function (_ context) + (loop [current start + output (set.add start empty_ring)] + (case (|> context (get@ #.var_bindings) (var::get current)) + (#.Some (#.Some type)) + (case type + (#.Var post) + (if (!n\= start post) + (#try.Success [context output]) + (recur post (set.add post output))) + + _ + (#try.Success [context empty_ring])) + + (#.Some #.None) + (#try.Success [context output]) + + #.None + (exception.throw ..unknown_type_var current))))) + +(def: #export fresh_context + Type_Context + {#.var_counter 0 + #.ex_counter 0 + #.var_bindings (list)}) + +(def: (attempt op) + (All [a] (-> (Check a) (Check (Maybe a)))) + (function (_ context) + (case (op context) + (#try.Success [context' output]) + (#try.Success [context' (#.Some output)]) + + (#try.Failure _) + (#try.Success [context #.None])))) + +(def: (either left right) + (All [a] (-> (Check a) (Check a) (Check a))) + (function (_ context) + (case (left context) + (#try.Failure _) + (right context) + + output + output))) + +(def: (assumed? [e a] assumptions) + (-> Assumption (List Assumption) Bit) + (list.any? (function (_ [e' a']) + (and (//\= e e') + (//\= a a'))) + assumptions)) + +(def: (assume! assumption assumptions) + (-> Assumption (List Assumption) (List Assumption)) + (#.Cons assumption assumptions)) + +## TODO: "if_bind" can be optimized... +(def: (if_bind id type then else) + (All [a] + (-> Var Type (Check a) (-> Type (Check a)) + (Check a))) + ($_ either + (do ..monad + [_ (..bind type id)] + then) + (do {! ..monad} + [ring (..ring id) + _ (assert "" (n.> 1 (set.size ring))) + _ (monad.map ! (update type) (set.to_list ring))] + then) + (do ..monad + [?bound (read id)] + (else (maybe.default (#.Var id) ?bound))))) + +## TODO: "link_2" can be optimized... +(def: (link_2 left right) + (-> Var Var (Check Any)) + (do ..monad + [_ (..bind (#.Var right) left)] + (..bind (#.Var left) right))) + +## TODO: "link_3" can be optimized... +(def: (link_3 interpose to from) + (-> Var Var Var (Check Any)) + (do ..monad + [_ (update (#.Var interpose) from)] + (update (#.Var to) interpose))) + +## TODO: "check_vars" can be optimized... +(def: (check_vars check' assumptions idE idA) + (-> (Checker Type) (Checker Var)) + (if (!n\= idE idA) + (check\wrap assumptions) + (do {! ..monad} + [ebound (attempt (peek idE)) + abound (attempt (peek idA))] + (case [ebound abound] + ## Link the 2 variables circularly + [#.None #.None] + (do ! + [_ (link_2 idE idA)] + (wrap assumptions)) + + ## Interpose new variable between 2 existing links + [(#.Some etype) #.None] + (case etype + (#.Var targetE) + (do ! + [_ (link_3 idA targetE idE)] + (wrap assumptions)) + + _ + (check' assumptions etype (#.Var idA))) + + ## Interpose new variable between 2 existing links + [#.None (#.Some atype)] + (case atype + (#.Var targetA) + (do ! + [_ (link_3 idE targetA idA)] + (wrap assumptions)) + + _ + (check' assumptions (#.Var idE) atype)) + + [(#.Some etype) (#.Some atype)] + (case [etype atype] + [(#.Var targetE) (#.Var targetA)] + (do ! + [ringE (..ring idE) + ringA (..ring idA)] + (if (\ set.equivalence = ringE ringA) + (wrap assumptions) + ## Fuse 2 rings + (do ! + [_ (monad.fold ! (function (_ interpose to) + (do ! + [_ (link_3 interpose to idE)] + (wrap interpose))) + targetE + (set.to_list ringA))] + (wrap assumptions)))) + + (^template [<pattern> <id> <type>] + [<pattern> + (do ! + [ring (..ring <id>) + _ (monad.map ! (update <type>) (set.to_list ring))] + (wrap assumptions))]) + ([[(#.Var _) _] idE atype] + [[_ (#.Var _)] idA etype]) + + _ + (check' assumptions etype atype)))))) + +(def: silent_failure! + (All [a] (Check a)) + (..fail "")) + +## TODO: "check_apply" can be optimized... +(def: (check_apply check' assumptions expected actual) + (-> (Checker Type) (Checker [Type Type])) + (let [[expected_input expected_function] expected + [actual_input actual_function] actual] + (case [expected_function actual_function] + [(#.Ex exE) (#.Ex exA)] + (if (!n\= exE exA) + (check' assumptions expected_input actual_input) + ..silent_failure!) + + [(#.UnivQ _ _) (#.Ex _)] + (do ..monad + [expected' (apply_type! expected_function expected_input)] + (check' assumptions expected' (#.Apply actual))) + + [(#.Ex _) (#.UnivQ _ _)] + (do ..monad + [actual' (apply_type! actual_function actual_input)] + (check' assumptions (#.Apply expected) actual')) + + [(#.Apply [expected_input' expected_function']) (#.Ex _)] + (do ..monad + [expected_function'' (apply_type! expected_function' expected_input')] + (check' assumptions (#.Apply [expected_input expected_function'']) (#.Apply actual))) + + [(#.Ex _) (#.Apply [actual_input' actual_function'])] + (do ..monad + [actual_function'' (apply_type! actual_function' actual_input')] + (check' assumptions (#.Apply expected) (#.Apply [actual_input actual_function'']))) + + (^or [(#.Ex _) _] [_ (#.Ex _)]) + (do ..monad + [assumptions (check' assumptions expected_function actual_function)] + (check' assumptions expected_input actual_input)) + + [(#.Var id) _] + (function (_ context) + (case ((do ..monad + [expected_function' (..read! id)] + (check' assumptions (#.Apply expected_input expected_function') (#.Apply actual))) + context) + (#try.Success output) + (#try.Success output) + + (#try.Failure _) + (case actual_function + (#.UnivQ _ _) + ((do ..monad + [actual' (apply_type! actual_function actual_input)] + (check' assumptions (#.Apply expected) actual')) + context) + + (#.Ex exA) + ((do ..monad + [assumptions (check' assumptions expected_function actual_function)] + (check' assumptions expected_input actual_input)) + context) + + _ + ((do ..monad + [assumptions (check' assumptions expected_function actual_function) + expected' (apply_type! actual_function expected_input) + actual' (apply_type! actual_function actual_input)] + (check' assumptions expected' actual')) + context)))) + + [_ (#.Var id)] + (function (_ context) + (case ((do ..monad + [actual_function' (read! id)] + (check' assumptions (#.Apply expected) (#.Apply actual_input actual_function'))) + context) + (#try.Success output) + (#try.Success output) + + _ + ((do ..monad + [assumptions (check' assumptions expected_function actual_function) + expected' (apply_type! expected_function expected_input) + actual' (apply_type! expected_function actual_input)] + (check' assumptions expected' actual')) + context))) + + _ + ..silent_failure!))) + +(def: (with exception parameter check) + (All [e a] (-> (Exception e) e (Check a) (Check a))) + (|>> check (exception.with exception parameter))) + +## TODO: "check'" can be optimized... +(def: (check' assumptions expected actual) + {#.doc "Type-check to ensure that the 'expected' type subsumes the 'actual' type."} + (Checker Type) + (if (for {@.php false} ## TODO: Remove this once JPHP is gone. + (is? expected actual)) + (check\wrap assumptions) + (with ..type_check_failed [expected actual] + (case [expected actual] + [(#.Var idE) (#.Var idA)] + (check_vars check' assumptions idE idA) + + [(#.Var id) _] + (if_bind id actual + (check\wrap assumptions) + (function (_ bound) + (check' assumptions bound actual))) + + [_ (#.Var id)] + (if_bind id expected + (check\wrap assumptions) + (function (_ bound) + (check' assumptions expected bound))) + + (^template [<fE> <fA>] + [[(#.Apply aE <fE>) (#.Apply aA <fA>)] + (check_apply check' assumptions [aE <fE>] [aA <fA>])]) + ([F1 (#.Ex ex)] + [(#.Ex exE) fA] + [fE (#.Var idA)] + [(#.Var idE) fA]) + + [(#.Apply A F) _] + (let [new_assumption [expected actual]] + (if (assumed? new_assumption assumptions) + (check\wrap assumptions) + (do ..monad + [expected' (apply_type! F A)] + (check' (assume! new_assumption assumptions) expected' actual)))) + + [_ (#.Apply A F)] + (do ..monad + [actual' (apply_type! F A)] + (check' assumptions expected actual')) + + ## TODO: Refactor-away as cold-code + (^template [<tag> <instancer>] + [[(<tag> _) _] + (do ..monad + [[_ paramT] <instancer> + expected' (apply_type! expected paramT)] + (check' assumptions expected' actual))]) + ([#.UnivQ ..existential] + [#.ExQ ..var]) + + ## TODO: Refactor-away as cold-code + (^template [<tag> <instancer>] + [[_ (<tag> _)] + (do ..monad + [[_ paramT] <instancer> + actual' (apply_type! actual paramT)] + (check' assumptions expected actual'))]) + ([#.UnivQ ..var] + [#.ExQ ..existential]) + + [(#.Primitive e_name e_params) (#.Primitive a_name a_params)] + (if (!text\= e_name a_name) + (loop [assumptions assumptions + e_params e_params + a_params a_params] + (case [e_params a_params] + [#.Nil #.Nil] + (check\wrap assumptions) + + [(#.Cons e_head e_tail) (#.Cons a_head a_tail)] + (do ..monad + [assumptions' (check' assumptions e_head a_head)] + (recur assumptions' e_tail a_tail)) + + _ + ..silent_failure!)) + ..silent_failure!) + + (^template [<compose>] + [[(<compose> eL eR) (<compose> aL aR)] + (do ..monad + [assumptions (check' assumptions eL aL)] + (check' assumptions eR aR))]) + ([#.Sum] + [#.Product]) + + [(#.Function eI eO) (#.Function aI aO)] + (do ..monad + [assumptions (check' assumptions aI eI)] + (check' assumptions eO aO)) + + [(#.Ex e!id) (#.Ex a!id)] + (if (!n\= e!id a!id) + (check\wrap assumptions) + ..silent_failure!) + + [(#.Named _ ?etype) _] + (check' assumptions ?etype actual) + + [_ (#.Named _ ?atype)] + (check' assumptions expected ?atype) + + _ + ..silent_failure!)))) + +(def: #export (check expected actual) + {#.doc "Type-check to ensure that the 'expected' type subsumes the 'actual' type."} + (-> Type Type (Check Any)) + (check' (list) expected actual)) + +(def: #export (checks? expected actual) + {#.doc "A simple type-checking function that just returns a yes/no answer."} + (-> Type Type Bit) + (case (..run ..fresh_context (..check' (list) expected actual)) + (#try.Failure _) + false + + (#try.Success _) + true)) + +(def: #export context + (Check Type_Context) + (function (_ context) + (#try.Success [context context]))) + +(def: #export (clean inputT) + (-> Type (Check Type)) + (case inputT + (#.Primitive name paramsT+) + (|> paramsT+ + (monad.map ..monad clean) + (check\map (|>> (#.Primitive name)))) + + (^or (#.Parameter _) (#.Ex _) (#.Named _)) + (check\wrap inputT) + + (^template [<tag>] + [(<tag> leftT rightT) + (do ..monad + [leftT' (clean leftT)] + (|> (clean rightT) + (check\map (|>> (<tag> leftT')))))]) + ([#.Sum] [#.Product] [#.Function] [#.Apply]) + + (#.Var id) + (do ..monad + [?actualT (read id)] + (case ?actualT + (#.Some actualT) + (clean actualT) + + _ + (wrap inputT))) + + (^template [<tag>] + [(<tag> envT+ unquantifiedT) + (do {! ..monad} + [envT+' (monad.map ! clean envT+)] + (wrap (<tag> envT+' unquantifiedT)))]) + ([#.UnivQ] [#.ExQ]) + )) |