Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable cross-effect subtyping of arrows #3665

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion doc/book/code/Pure.fst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let fact (x:nat) : pos = factorial x

//SNIPPET_START: wp$
let pre = Type0
let post (a:Type) = a -> Type0
let post (a:Type) = a -> GTot Type0
let wp (a:Type) = post a -> pre
//SNIPPET_END: wp$

Expand Down
1 change: 1 addition & 0 deletions doc/old/tutorial/code/exercises/Ex04e.fst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type option 'a =
| None : option 'a
| Some : v:'a -> option 'a

val find : f:('a -> Tot bool) -> list 'a -> Tot (option (x:'a{f x}))
let rec find f l = match l with
| [] -> None
| hd::tl -> if f hd then Some hd else find f tl
Expand Down
2 changes: 1 addition & 1 deletion examples/algorithms/GC.fst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ type mutator_inv gc_state =
inv gc_state (fun i -> gc_state.color i = Unalloc \/ gc_state.color i = White)

new_effect GC_STATE = STATE_h gc_state
let gc_post (a:Type) = a -> gc_state -> Type0
let gc_post (a:Type) = a -> gc_state -> GTot Type0
sub_effect
DIV ~> GC_STATE = fun (a:Type) (wp:pure_wp a) (p:gc_post a) (gc:gc_state) -> wp (fun a -> p a gc)

Expand Down
2 changes: 1 addition & 1 deletion examples/algorithms/IntervalIntersect.fst
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ let rec ppIntervals' (is:intervals): ML unit =

let toI f t = I (int_to_t f) (int_to_t t)

let ppIntervals is : ML string = FStar.List.Tot.fold_left (sprintf "%s %s") "" (FStar.List.map ppInterval is)
let ppIntervals is : ML string = FStar.List.Tot.fold_left (sprintf "%s %s") "" (FStar.List.map (fun i -> ppInterval i) is)
let main =
print_string
(ppIntervals (intersect [toI 3 10; toI 10 15] [toI 1 4; toI 10 14]));
Expand Down
4 changes: 2 additions & 2 deletions examples/doublylinkedlist/DoublyLinkedList.fst
Original file line number Diff line number Diff line change
Expand Up @@ -1101,8 +1101,8 @@ let tot_dll_to_fragment_split (#t:Type) (h0:heap) (d:dll t{dll_valid h0 d})
reveal d.nodes == reveal p1.pnodes `append` reveal p2.pnodes))) =
let split_nodes = elift2_p split_using d.nodes (hide n2) in
lemma_split_using d.nodes n2;
let l1 = elift1 fst split_nodes in
let l2 = elift1 snd split_nodes in
let l1 = hide (fst split_nodes) in
let l2 = hide (snd split_nodes) in
let p1 = { phead = d.lhead ; ptail = n1 ; pnodes = l1 } in
let p2 = { phead = n2 ; ptail = d.ltail ; pnodes = l2 } in
let f = Frag2 p1 p2 in
Expand Down
63 changes: 60 additions & 3 deletions src/typechecker/FStarC.TypeChecker.Rel.fst
Original file line number Diff line number Diff line change
Expand Up @@ -2150,6 +2150,44 @@ let has_typeclass_constraint (u:ctx_uvar) (wl:worklist)
: bool
= wl.typeclass_variables |> for_any (fun v -> UF.equiv v.ctx_uvar_head u.ctx_uvar_head)

let maybe_eta_expand_fun (env: env) (e t_e t_expected: term) : option term =
let norm = N.normalize [Env.Primops; Env.UnfoldUntil delta_constant; Env.HNF; Env.Beta; Env.Unascribe; Env.Unrefine] env in
let t_e = norm t_e in
let t_expected = norm t_expected in
if !dbg_Rel then BU.print2 "t_e=%s t_exp=%s\n" (show t_e) (show t_expected);
let rec aux e t_e t_expected =
match (SS.compress t_e).n, (SS.compress t_expected).n with
| Tm_arrow {bs=bs_e; comp=c_e}, Tm_arrow {bs=bs_exp; comp=c_exp} ->
let mk_c c = function
| [] -> c
| bs -> mk_Total(mk (Tm_arrow {bs; comp=c}) c.pos) in
let (bs_e, c_e), (bs_exp, c_exp) =
match_num_binders (bs_e, mk_c c_e) (bs_exp, mk_c c_exp) in
let bs_e, c_e = SS.open_comp bs_e c_e in
let bs_exp, c_exp = SS.open_comp bs_exp c_exp in
let bs_exp, as_exp = U.args_of_binders bs_exp in
let e' = U.mk_app e as_exp in
let return e' = Some (U.abs bs_exp e' (Some (U.residual_comp_of_comp c_exp))) in
begin match aux e' (U.comp_result c_e) (U.comp_result c_exp) with
| Some e' -> return e'
| None ->
let eff_e, eff_exp =
c_e |> U.comp_effect_name |> Env.norm_eff_name env,
c_exp |> U.comp_effect_name |> Env.norm_eff_name env in
if lid_equals eff_e eff_exp then
None
else
return e'
end
| _, _ -> None
in
aux e t_e t_expected

let eta_expand_fun env (e t_e t_expected: term) : term =
match maybe_eta_expand_fun env e t_e t_expected with
| None -> e
| Some e -> e

(* This function returns true for those lazykinds that
are "complete" in the sense that unfolding them does not
lose any information. For instance, embedded universes
Expand Down Expand Up @@ -2915,7 +2953,6 @@ and solve_t_flex_rigid_eq (orig:prob) (wl:worklist) (lhs:flex_t) (rhs:term)
*)
let mk_solution env (lhs:flex_t) (bs:binders) (rhs:term) =
let bs_orig = bs in
let rhs_orig = rhs in
let (Flex (_, ctx_u, args)) = lhs in
let bs, rhs =
let bv_not_free_in_arg x arg =
Expand Down Expand Up @@ -2959,6 +2996,18 @@ and solve_t_flex_rigid_eq (orig:prob) (wl:worklist) (lhs:flex_t) (rhs:term)
bs,
S.mk_Tm_app rhs_hd rhs_args rhs.pos
in
let rhs =
// eta-expand functions for effect promotion, unless we're in --MLish
if Options.ml_ish () then rhs else
let env = { env with admit=true; expected_typ=None } in
let t_u, _ =
let Flex (lhs, _, _) = lhs in
let lhs_hd, _ = U.head_and_args lhs in
env.typeof_well_typed_tot_or_gtot_term env (U.mk_app lhs_hd (U.args_of_non_null_binders bs)) false in
let t_rhs, _ = env.typeof_well_typed_tot_or_gtot_term env rhs false in
if !dbg_Rel then BU.print2 "t_rhs=%s t_u=%s\n" (show t_rhs) (show t_u);
eta_expand_fun env rhs t_rhs t_u in
if !dbg_Rel then BU.print1 "rhs_eta=%s\n" (show rhs);
let sol =
match bs with
| [] -> rhs
Expand Down Expand Up @@ -4033,8 +4082,16 @@ and solve_t' (problem:tprob) (wl:worklist) : solution =
let (bs1, c1), (bs2, c2) =
match_num_binders (bs1, mk_c c1) (bs2, mk_c c2) in

solve_binders bs1 bs2 orig wl
(fun wl scope subst ->
let eff1, eff2 =
let env = p_env wl orig in
c1 |> U.comp_effect_name |> Env.norm_eff_name env,
c2 |> U.comp_effect_name |> Env.norm_eff_name env in

if not (Options.ml_ish () || Ident.lid_equals eff1 eff2) then
giveup wl (Thunk.mk fun _ -> "computation type mismatch in arrow: " ^ string_of_lid eff1 ^ " vs " ^ string_of_lid eff2) orig
else
solve_binders bs1 bs2 orig wl
(fun wl scope subst ->
let c1 = Subst.subst_comp subst c1 in
let c2 = Subst.subst_comp subst c2 in //open both comps
let rel = if (Options.use_eq_at_higher_order()) then EQ else problem.relation in
Expand Down
3 changes: 3 additions & 0 deletions src/typechecker/FStarC.TypeChecker.Rel.fsti
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ val head_matches_delta (env:env) (logical:bool) (smt_ok:bool) (t1 t2:typ) : (mat
val may_relate_with_logical_guard (env:env) (is_equality:bool) (head:typ) : bool
val guard_to_string : env -> guard_t -> string
val simplify_guard : env -> guard_t -> guard_t

val maybe_eta_expand_fun (env: env) (e t_e t_expected: term) : option term

val solve_deferred_constraints: env -> guard_t -> guard_t
val solve_non_tactic_deferred_constraints: maybe_defer_flex_flex:bool -> env -> guard_t -> guard_t

Expand Down
6 changes: 3 additions & 3 deletions src/typechecker/FStarC.TypeChecker.TcTerm.fst
Original file line number Diff line number Diff line change
Expand Up @@ -2051,7 +2051,7 @@ and tc_abs_expected_function_typ env (bs:binders) (t0:option (typ & bool)) (body
(* CK: add this case since the type may be f:(a -> M b wp){φ}, in which case I drop the refinement *)
(* NS: 07/21 dropping the refinement is not sound; we need to check that f validates phi. See Bug #284 *)
| Tm_refine {b} ->
let _, bs, bs', copt, env_body, body, g_env = as_function_typ norm b.sort in
let _, bs, bs', copt, env_body, body, g_env = as_function_typ false b.sort in
//we pass type `t` out to check afterwards the full refinement type is respected
Some t, bs, bs', copt, env_body, body, g_env

Expand Down Expand Up @@ -2284,8 +2284,8 @@ and tc_abs env (top:term) (bs:binders) (body:term) : term & lcomp & guard_t =
tc_abs_expected_function_typ env bs topt body in

if Debug.extreme () then
BU.print3 "After expected_function_typ, tfun_opt: %s, c_opt: %s, and expected type in envbody: %s\n"
(show tfun_opt) (show c_opt) (show (Env.expected_typ envbody));
BU.print4 "After expected_function_typ, tfun_opt: %s, bs: %s, c_opt: %s, and expected type in envbody: %s\n"
(show tfun_opt) (show bs) (show c_opt) (show (Env.expected_typ envbody));

if !dbg_NYC
then BU.print2 "!!!!!!!!!!!!!!!Guard for function with binders %s is %s\n"
Expand Down
10 changes: 10 additions & 0 deletions src/typechecker/FStarC.TypeChecker.Util.fst
Original file line number Diff line number Diff line change
Expand Up @@ -2572,6 +2572,16 @@ let maybe_coerce_lc env (e:term) (lc:lcomp) (exp_t:term) : term & lcomp & guard_
BU.print1 "(%s) No user coercion found\n"
(Range.string_of_range e.pos)
in

// eta-expand functions for effect promotion, unless we're in --MLish
match if Options.ml_ish () then None else maybe_eta_expand_fun env e lc.res_typ exp_t with
| Some e' ->
if !dbg_Coercions then
BU.print3 "(%s) Eta-expansion coercion from %s to %s" (Range.string_of_range e.pos) (show e) (show e');
// FIXME: do we need to type-check this again?
e', { lc with res_typ = exp_t }, Env.trivial_guard

| None ->

(* TODO: hide/reveal also user coercions? it's trickier for sure *)

Expand Down
11 changes: 11 additions & 0 deletions tests/bug-reports/closed/Bug3644.fst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Bug3644

let t = nat -> nat
let s = nat -> GTot nat

let f : t = (fun n -> n)
let g : s = f

// `g` must not have the type `nat -> GTot nat`
[@@expect_failure [19]]
let _ = assert (has_type g t)
37 changes: 37 additions & 0 deletions tests/bug-reports/closed/Bug3659.fst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Bug3659

let point t = unit -> t
let dv_point t = unit -> Dv t

let cast (#t: Type u#a) (f: point t) : dv_point t = f

let is_inj #t #s (f: t->s) : prop =
forall x y. f x == f y ==> x == y

// The `cast` function must not be injective.
[@@expect_failure [19]]
let cast_inj_fails (t: Type u#a) : squash (is_inj (cast #t)) = ()

// Otherwise we would get an unsoundness:
let cast_inj (t: Type u#a) : squash (is_inj (cast #t)) = admit ()

let to_type (#t: Type0) (x: t) : Type0 = y:t { y == x }
let to_type_inj t : squash (is_inj (to_type #t)) =
introduce forall (x y: t). to_type x == to_type y ==> x == y with
introduce _ ==> _ with _.
let x: to_type x = x in
let x: to_type y = coerce_eq () x in
()

let const #t (x: t) : point t = fun _ -> x
let const_inj t : squash (is_inj (const #t)) =
assert forall x. const #t x () == x

let f (t: Type u#a) : GTot Type0 = to_type (cast (const t))
let f_inj : squash (Functions.is_inj (f u#a)) =
to_type_inj (dv_point (Type u#a));
cast_inj (Type u#a);
const_inj (Type u#a)

let contradiction : squash False =
Cardinality.Universes.no_inj_universes_suc f
2 changes: 1 addition & 1 deletion ulib/FStar.All.fsti
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ unfold let lift_state_all (a : Type) (wp : st_wp a) (p : all_post a) = wp (fun a
sub_effect STATE ~> ALL { lift_wp = lift_state_all }

unfold
let lift_exn_all (a : Type) (wp : ex_wp a) (p : all_post a) (h : heap) = wp (fun ra -> p ra h)
let lift_exn_all (a : Type) (wp : ex_wp a) : all_wp_h heap a = fun (p : all_post a) (h : heap) -> wp (fun ra -> p ra h)
sub_effect EXN ~> ALL { lift_wp = lift_exn_all }

effect All (a:Type) (pre:all_pre) (post:(h:heap -> Tot (all_post' a (pre h)))) =
Expand Down
2 changes: 1 addition & 1 deletion ulib/FStar.BigOps.fst
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ let pairwise_or'_nil (#a:Type) (f:a -> a -> Type0)
= assert (pairwise_or' f [] == False) by (T.compute())

let pairwise_or'_cons (#a:Type) (f:a -> a -> Type) (hd:a) (tl:list a)
= assert (pairwise_or' f (hd::tl) == (big_or' (f hd) tl \/ pairwise_or' f tl))
= assert_norm (pairwise_or' f (hd::tl) == (big_or' (f hd) tl \/ pairwise_or' f tl))

let pairwise_or'_prop (#a:Type) (f:a -> a -> Type) (l:list a)
= match l with
Expand Down
6 changes: 3 additions & 3 deletions ulib/FStar.BigOps.fsti
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ val map_op'_cons

(** [big_and' f l] = [/\_{x in l} f x] *)
[@@ __reduce__]
let big_and' #a (f: (a -> Type)) (l: list a) : Type = map_op' l_and f l True
let big_and' #a (f: (a -> Type)) (l: list a) : Type = map_op' (fun p q -> p /\ q) (fun x -> f x) l True

(** Equations for [big_and'] showing it to be trivial over the empty list *)
val big_and'_nil (#a: Type) (f: (a -> Type)) : Lemma (big_and' f [] == True)
Expand Down Expand Up @@ -125,7 +125,7 @@ let big_and #a (f: (a -> Type)) (l: list a) : prop =

(** [big_or f l] = [\/_{x in l} f x] *)
[@@ __reduce__]
let big_or' #a (f: (a -> Type)) (l: list a) : Type = map_op' l_or f l False
let big_or' #a (f: (a -> Type)) (l: list a) : Type = map_op' (fun p q -> p \/ q) (fun x -> f x) l False

(** Equations for [big_or] showing it to be [False] on the empty list *)
val big_or'_nil (#a: Type) (f: (a -> Type)) : Lemma (big_or' f [] == False)
Expand Down Expand Up @@ -235,7 +235,7 @@ let pairwise_and #a (f: (a -> a -> Type)) (l: list a) : prop =
(** [pairwise_or f l] disjoins [f] on all pairs excluding the diagonal
i.e., [pairwise_or f [a; b; c] = f a b \/ f a c \/ f b c] *)
[@@ __reduce__]
let pairwise_or' #a (f: (a -> a -> Type)) (l: list a) : Type = pairwise_op' l_or f l False
let pairwise_or' #a (f: (a -> a -> Type)) (l: list a) : Type = pairwise_op' (fun p q -> p \/ q) (fun x y -> f x y) l False

(** Equations for [pairwise_or'] showing it to be a fold with [big_or'] *)
val pairwise_or'_nil (#a: Type) (f: (a -> a -> Type0)) : Lemma (pairwise_or' f [] == False)
Expand Down
6 changes: 3 additions & 3 deletions ulib/FStar.Cardinality.Cantor.fst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module FStar.Cardinality.Cantor

open FStar.Functions

let no_surj_powerset (a : Type) (f : a -> powerset a) : Lemma (~(is_surj f)) =
let no_surj_powerset (a : Type) (f : a -> GTot (powerset a)) : Lemma (~(is_surj f)) =
let aux () : Lemma (requires is_surj f) (ensures False) =
(* Cantor's proof: given a supposed surjective f,
we define a set s that cannot be in the image of f. Namely,
Expand All @@ -16,9 +16,9 @@ let no_surj_powerset (a : Type) (f : a -> powerset a) : Lemma (~(is_surj f)) =
in
Classical.move_requires aux ()

let no_inj_powerset (a : Type) (f : powerset a -> a) : Lemma (~(is_inj f)) =
let no_inj_powerset (a : Type) (f : powerset a -> GTot a) : Lemma (~(is_inj f)) =
let aux () : Lemma (requires is_inj f) (ensures False) =
let g : a -> GTot (powerset a) = inverse_of_inj f (fun _ -> false) in
let g : a -> GTot (powerset a) = inverse_of_inj (fun x -> f x) (fun _ -> false) in
no_surj_powerset a g
in
Classical.move_requires aux ()
4 changes: 2 additions & 2 deletions ulib/FStar.Cardinality.Cantor.fsti
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ set. *)

open FStar.Functions

val no_surj_powerset (a : Type) (f : a -> powerset a)
val no_surj_powerset (a : Type) (f : a -> GTot (powerset a))
: Lemma (~(is_surj f))

val no_inj_powerset (a : Type) (f : powerset a -> a)
val no_inj_powerset (a : Type) (f : powerset a -> GTot a)
: Lemma (~(is_inj f))
9 changes: 4 additions & 5 deletions ulib/FStar.Cardinality.Universes.fst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let aux_inj_type_powerset (f1 f2 : powerset (Type u#a))
assert (xx1 === xx2);
assert (f1 == f2)

let inj_type_powerset () : Lemma (is_inj type_powerset) =
let inj_type_powerset () : Lemma (is_inj_tot type_powerset) =
Classical.forall_intro_2 (fun f1 -> Classical.move_requires (aux_inj_type_powerset f1))

(* let u' > u be universes. (u' = max(a+1, b), u=a below)
Expand All @@ -29,14 +29,13 @@ let inj_type_powerset () : Lemma (is_inj type_powerset) =
3- Therefore, there cannot be an injection from Type(u') into Type(u), otherwise we would
compose it with the first injection and obtain the second impossible injection.
*)
let no_inj_universes (f : Type u#(max (a+1) b) -> Type u#a) : Lemma (~(is_inj f)) =
let no_inj_universes (f : Type u#(max (a+1) b) -> GTot (Type u#a)) : Lemma (~(is_inj f)) =
let aux () : Lemma (requires is_inj f) (ensures False) =
let comp : powerset (Type u#a) -> Type u#a = fun x -> f (type_powerset x) in
let comp : powerset (Type u#a) -> GTot (Type u#a) = fun x -> f (to_gtot type_powerset x) in
inj_type_powerset ();
inj_comp type_powerset f;
no_inj_powerset _ comp
in
Classical.move_requires aux ()

let no_inj_universes_suc (f : Type u#(a+1) -> Type u#a) : Lemma (~(is_inj f)) =
let no_inj_universes_suc (f : Type u#(a+1) -> GTot (Type u#a)) : Lemma (~(is_inj f)) =
no_inj_universes f
4 changes: 2 additions & 2 deletions ulib/FStar.Cardinality.Universes.fsti
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ open FStar.Cardinality.Cantor
(* Prove that there can be no injection from a universe into a strictly smaller
universe. We use `max (a+1) b` to represent an arbitrary universe strictly larger
than `a` as we cannot write sums of universe levels. *)
val no_inj_universes (f : Type u#(max (a+1) b) -> Type u#a)
val no_inj_universes (f : Type u#(max (a+1) b) -> GTot (Type u#a))
: Lemma (~(is_inj f))

(* A simpler version for the +1 case. *)
val no_inj_universes_suc (f : Type u#(a+1) -> Type u#a)
val no_inj_universes_suc (f : Type u#(a+1) -> GTot (Type u#a))
: Lemma (~(is_inj f))
2 changes: 1 addition & 1 deletion ulib/FStar.Classical.fst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ let get_equality #t a b = get_squashed #(equals a b) (a == b)
let impl_to_arrow #a #b impl sx =
bind_squash #(a -> GTot b) impl (fun f -> bind_squash sx (fun x -> return_squash (f x)))

let arrow_to_impl #a #b f = squash_double_arrow (return_squash (fun x -> f (return_squash x)))
let arrow_to_impl #a #b f = squash_double_arrow (return_squash #(a -> GTot (squash b)) (fun x -> f (return_squash x)))

let impl_intro_gtot #p #q f = return_squash f

Expand Down
11 changes: 11 additions & 0 deletions ulib/FStar.Functions.fsti
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ about functions and sets. *)
let is_inj (#a #b : _) (f : a -> GTot b) : prop =
forall (x1 x2 : a). f x1 == f x2 ==> x1 == x2

let to_gtot #a #b (f: a -> b) : (a -> GTot b) = fun x -> f x

let is_inj_tot #a #b (f: a -> b) : prop =
is_inj (to_gtot f)

let is_surj (#a #b : _) (f : a -> GTot b) : prop =
forall (y:b). exists (x:a). f x == y

let is_surj_tot #a #b (f: a -> b) : prop =
is_surj (to_gtot f)

let is_bij (#a #b : _) (f : a -> GTot b) : prop =
is_inj f /\ is_surj f

let is_bij_tot #a #b (f: a -> b) : prop =
is_bij (to_gtot f)

let in_image (#a #b : _) (f : a -> GTot b) (y : b) : prop =
exists (x:a). f x == y

Expand Down
2 changes: 1 addition & 1 deletion ulib/FStar.HyperStack.All.fst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ new_effect ALL = ALL_h HyperStack.mem
unfold let lift_state_all (a:Type) (wp:st_wp a) (p:all_post a) = wp (fun a -> p (V a))
sub_effect STATE ~> ALL = lift_state_all

unfold let lift_exn_all (a:Type) (wp:ex_wp a) (p:all_post a) (h:HyperStack.mem) = wp (fun ra -> p ra h)
unfold let lift_exn_all (a:Type) (wp:ex_wp a) : all_wp_h HyperStack.mem a = fun p h -> wp (fun ra -> p ra h)
sub_effect EXN ~> ALL = lift_exn_all

effect All (a:Type) (pre:all_pre) (post: (h0:HyperStack.mem -> Tot (all_post' a (pre h0)))) =
Expand Down
Loading
Loading