aboutsummaryrefslogtreecommitdiffstats
From mininix Require Export utils nix.floats.
From stdpp Require Import options.

(** Our development does not rely on a particular order on attribute set names.
It can be any decidable total order. We pick something concrete (lexicographic
order on strings) to avoid having to parametrize the whole development. *)
Definition attr_le := String.le.
Global Instance attr_le_dec : RelDecision attr_le := _.
Global Instance attr_le_po : PartialOrder attr_le := _.
Global Instance attr_le_total : Total attr_le := _.
Global Typeclasses Opaque attr_le.

Inductive mode := SHALLOW | DEEP.
Inductive kind := ABS | WITH.
Inductive rec := REC | NONREC.

Global Instance rec_eq_dec : EqDecision rec.
Proof. solve_decision. Defined.

Inductive num :=
  | NInt (n : Z)
  | NFloat (f : float).

Inductive base_lit :=
  | LitNum (n : num)
  | LitBool (b : bool)
  | LitString (s : string)
  | LitNull.

Global Instance num_inhabited : Inhabited num := populate (NInt 0).
Global Instance base_lit_inhabited : Inhabited base_lit := populate LitNull.

Global Instance num_eq_dec : EqDecision num.
Proof. solve_decision. Defined.
Global Instance base_lit_eq_dec : EqDecision base_lit.
Proof. solve_decision. Defined.

Global Instance maybe_NInt : Maybe NInt := λ n,
  if n is NInt i then Some i else None.
Global Instance maybe_NFloat : Maybe NFloat := λ n,
  if n is NFloat f then Some f else None.
Global Instance maybe_LitNum : Maybe LitNum := λ bl,
  if bl is LitNum n then Some n else None.
Global Instance maybe_LitBool : Maybe LitBool := λ bl,
  if bl is LitBool b then Some b else None.
Global Instance maybe_LitString : Maybe LitString := λ bl,
  if bl is LitString s then Some s else None.

Inductive bin_op : Set :=
  | AddOp | SubOp | MulOp | DivOp | AndOp | OrOp | XOrOp (* Arithmetic *)
  | LtOp | EqOp (* Relations *)
  | RoundOp (m : round_mode) (* Conversions *)
  | MatchStringOp (* Strings *)
  | MatchListOp | AppendListOp (* Lists *)
  | SelectAttrOp | UpdateAttrOp | HasAttrOp
  | DeleteAttrOp | SingletonAttrOp | MatchAttrOp (* Attribute sets *)
  | FunctionArgsOp | TypeOfOp.

Global Instance bin_op_eq_dec : EqDecision bin_op.
Proof. solve_decision. Defined.

Global Instance maybe_RoundOp : Maybe RoundOp := λ op,
  if op is RoundOp m then Some m else None.

Section expr.
  Local Unset Elimination Schemes.
  Inductive expr :=
    | ELit (bl : base_lit)
    | EId (x : string) (mke : option (kind * expr))
    | EAbs (x : string) (e : expr)
    | EAbsMatch (ms : gmap string (option expr)) (strict : bool) (e : expr)
    | EApp (e1 e2 : expr)
    | ESeq (μ : mode) (e1 e2 : expr)
    | EList (es : list expr)
    | EAttr (αs : gmap string attr)
    | ELetAttr (k : kind) (e1 e2 : expr)
    | EBinOp (op : bin_op) (e1 e2 : expr)
    | EIf (e1 e2 e3 : expr)
  with attr :=
    | Attr (τ : rec) (e : expr).
End expr.

Definition EId' x := EId x None.
Notation AttrR := (Attr REC).
Notation AttrN := (Attr NONREC).
Notation ESelect e x := (EBinOp SelectAttrOp e (ELit (LitString x))).
Notation ELet x e := (ELetAttr ABS (EAttr {[ x := AttrN e ]})).
Notation EWith := (ELetAttr WITH).

Definition attr_expr (α : attr) : expr := match α with Attr _ e => e end.
Definition attr_rec (α : attr) : rec := match α with Attr μ _ => μ end.
Definition attr_map (f : expr → expr) (α : attr) : attr :=
  match α with Attr μ e => Attr μ (f e) end.

Definition from_attr {A} (f g : expr → A) (α : attr) : A :=
  match α with AttrR e => f e | AttrN e => g e end.

Definition merge_kinded {A} (new old : kind * A) : option (kind * A) :=
  match new.1, old.1 with
  | WITH, ABS => Some old
  | _, _ => Some new
  end.
Arguments merge_kinded {_} !_ !_ / : simpl nomatch.
Notation union_kinded := (union_with merge_kinded).

Definition no_recs : gmap string attr → Prop :=
  map_Forall (λ _ α, attr_rec α = NONREC).

Definition indirects (αs : gmap string attr) : gmap string (kind * expr) :=
  map_imap (λ x _, Some (ABS, ESelect (EAttr αs) x)) αs.

Fixpoint subst (ds : gmap string (kind * expr)) (e : expr) : expr :=
  match e with
  | ELit b => ELit b
  | EId x mkd => EId x $ union_kinded (ds !! x) mkd
  | EAbs x e => EAbs x (subst ds e)
  | EAbsMatch ms strict e =>
      EAbsMatch (fmap (M:=option) (subst ds) <$> ms) strict (subst ds e)
  | EApp e1 e2 => EApp (subst ds e1) (subst ds e2)
  | ESeq μ e1 e2 => ESeq μ (subst ds e1) (subst ds e2)
  | EList es => EList (subst ds <$> es)
  | EAttr αs => EAttr (attr_map (subst ds) <$> αs)
  | ELetAttr k e1 e2 => ELetAttr k (subst ds e1) (subst ds e2)
  | EBinOp op e1 e2 => EBinOp op (subst ds e1) (subst ds e2)
  | EIf e1 e2 e3 => EIf (subst ds e1) (subst ds e2) (subst ds e3)
  end.

Notation attr_subst ds := (attr_map (subst ds)).

Definition int_min : Z := -(1 ≪ 63).
Definition int_max : Z := 1 ≪ 63 - 1.

Definition int_ok (i : Z) : bool := bool_decide (int_min ≤ i ≤ int_max)%Z.
Definition num_ok (n : num) : bool :=
  match n with NInt i => int_ok i | _ => true end.
Definition base_lit_ok (bl : base_lit) : bool :=
  match bl with LitNum n => num_ok n | _ => true end.

Inductive final : mode → expr → Prop :=
  | ELitFinal μ bl : base_lit_ok bl → final μ (ELit bl)
  | EAbsFinal μ x e : final μ (EAbs x e)
  | EAbsMatchFinal μ ms strict e : final μ (EAbsMatch ms strict e)
  | EListShallowFinal es : final SHALLOW (EList es)
  | EListDeepFinal es : Forall (final DEEP) es → final DEEP (EList es)
  | EAttrShallowFinal αs : no_recs αs → final SHALLOW (EAttr αs)
  | EAttrDeepFinal αs :
     no_recs αs →
     map_Forall (λ _, final DEEP ∘ attr_expr) αs →
     final DEEP (EAttr αs).

Fixpoint sem_eq_list (es1 es2 : list expr) : expr :=
  match es1, es2 with
  | [], [] => ELit (LitBool true)
  | e1 :: es1, e2 :: es2 =>
      EIf (EBinOp EqOp e1 e2) (sem_eq_list es1 es2) (ELit (LitBool false))
  | _, _ => ELit (LitBool false)
  end.

Fixpoint sem_lt_list (es1 es2 : list expr) : expr :=
  match es1, es2 with
  | [], _ => ELit (LitBool true)
  | e1 :: es1, e2 :: es2 =>
      EIf (EBinOp LtOp e1 e2) (ELit (LitBool true)) $
        EIf (EBinOp EqOp e1 e2) (sem_lt_list es1 es2) (ELit (LitBool false))
  | _ :: _, [] => ELit (LitBool false)
  end.

Definition sem_and_attr (es : gmap string expr) : expr :=
  map_fold_sorted attr_le
    (λ _ e1 e2, EIf e1 e2 (ELit (LitBool false)))
    (ELit (LitBool true)) es.

Definition sem_eq_attr (αs1 αs2 : gmap string attr) : expr :=
  sem_and_attr $ merge (λ mα1 mα2,
    α1 ← mα1; α2 ← mα2; Some (EBinOp EqOp (attr_expr α1) (attr_expr α2))) αs1 αs2.

Definition num_to_float (n : num) : float :=
  match n with
  | NInt i => Float.of_Z i
  | NFloat f => f
  end.

Definition sem_bin_op_lift
    (fint : Z → Z → Z) (ffloat : float → float → float)
    (n1 n2 : num) : option num :=
  match n1, n2 with
  | NInt i1, NInt i2 =>
     let i := fint i1 i2 in
     guard (int_ok i);;
     Some (NInt i)
  | _, _ => Some $ NFloat $ ffloat (num_to_float n1) (num_to_float n2)
  end.

Definition sem_bin_rel_lift
    (fint : Z → Z → bool) (ffloat : float → float → bool)
    (n1 n2 : num) : bool :=
  match n1, n2 with
  | NInt i1, NInt i2 => fint i1 i2
  | _, _ => ffloat (num_to_float n1) (num_to_float n2)
  end.

Definition sem_eq_base_lit (bl1 bl2 : base_lit) : bool :=
  match bl1, bl2 with
  | LitNum n1, LitNum n2 => sem_bin_rel_lift Z.eqb Float.eqb n1 n2
  | LitBool b1, LitBool b2 => bool_decide (b1 = b2)
  | LitString s1, LitString s2 => bool_decide (s1 = s2)
  | LitNull, LitNull => true
  | _, _ => false
  end.

(** Precondition e1 and e2 are final *)
Definition sem_eq (e1 e2 : expr) : option expr :=
  match e1, e2 with
  | ELit bl1, ELit bl2 => Some $ ELit (LitBool (sem_eq_base_lit bl1 bl2))
  | EAbs _ _, EAbs _ _ => None
  | EList es1, EList es2 => Some $
      if decide (length es1 = length es2) then sem_eq_list es1 es2
      else ELit $ LitBool false
  | EAttr αs1, EAttr αs2 => Some $
      if decide (dom αs1 = dom αs2) then sem_eq_attr αs1 αs2
      else ELit $ LitBool false
  | _, _ => Some $ ELit (LitBool false)
  end.

Definition div_allowed (n : num) : bool :=
  match n with
  | NInt n => bool_decide (n ≠ 0%Z)
  | NFloat f => negb (Float.eqb f (Float.of_Z 0)) (* TODO: Check NaNs *)
  end.

Definition sem_bin_op_num (op : bin_op) (n1 : num) : option (num → option base_lit) :=
  match op with
  | AddOp => Some $ λ n2,
      LitNum <$> sem_bin_op_lift Z.add Float.add n1 n2
  | SubOp => Some $ λ n2,
      LitNum <$> sem_bin_op_lift Z.sub Float.sub n1 n2
  | MulOp => Some $ λ n2,
      LitNum <$> sem_bin_op_lift Z.mul Float.mul n1 n2
  | DivOp => Some $ λ n2,
      (* Quot can overflow: [MIN_INT `quot` -1] equals [MAX_INT + 1] *)
      guard (div_allowed n2);;
      LitNum <$> sem_bin_op_lift Z.quot Float.div n1 n2
  | AndOp =>
      i1 ← maybe NInt n1;
      Some $ λ n2, i2 ← maybe NInt n2;
        Some $ LitNum $ NInt $ Z.land i1 i2
  | OrOp =>
      i1 ← maybe NInt n1;
      Some $ λ n2, i2 ← maybe NInt n2;
        Some $ LitNum $ NInt $ Z.lor i1 i2
  | XOrOp =>
      i1 ← maybe NInt n1;
      Some $ λ n2, i2 ← maybe NInt n2;
        Some $ LitNum $ NInt $ Z.lxor i1 i2
  | LtOp => Some $ λ n2,
      Some $ LitBool (sem_bin_rel_lift Z.ltb Float.ltb n1 n2)
  | _ => None
  end%Z.

Definition sem_bin_op_string (op : bin_op) : option (string → string → base_lit) :=
  match op with
  | AddOp => Some $ λ s1 s2, LitString (s1 +:+ s2)
  | LtOp => Some $ λ s1 s2, LitBool (bool_decide (strict attr_le s1 s2))
  | _ => None
  end.

Definition type_of_num (n : num) : string :=
  match n with
  | NInt _ => "int"
  | NFloat _ => "float"
  end.

Definition type_of_base_lit (bl : base_lit) : string :=
  match bl with
  | LitNum n => type_of_num n
  | LitBool _ => "bool"
  | LitString _ => "string"
  | LitNull => "null"
  end.

Definition type_of_expr (e : expr) :=
  match e with
  | ELit bl => Some (type_of_base_lit bl)
  | EAbs _ _ | EAbsMatch _ _ _ => Some "lambda"
  | EList _ => Some "list"
  | EAttr _ => Some "set"
  | _ => None
  end.

(* Used for [RoundOp] *)
Definition float_to_bounded_Z (f : float) : Z :=
  match Float.to_Z f with
  | Some x => if decide (int_ok x) then x else int_min
  | None => int_min
  end.

Inductive sem_bin_op : bin_op → expr → (expr → expr → Prop) → Prop :=
  | EqSem e1 :
     sem_bin_op EqOp e1 (λ e2 e, sem_eq e1 e2 = Some e)
  | LitNumSem op n1 f :
     sem_bin_op_num op n1 = Some f →
     sem_bin_op op (ELit (LitNum n1)) (λ e2 e, ∃ n2 bl,
       e2 = ELit (LitNum n2) ∧ f n2 = Some bl ∧ e = ELit bl)
  | RoundSem m n1 :
     sem_bin_op (RoundOp m) (ELit (LitNum n1)) (λ e2 e,
       e2 = ELit LitNull ∧
       e = ELit $ LitNum $ NInt $ float_to_bounded_Z $ Float.round m $ num_to_float n1)
  | LitStringSem op s1 f :
     sem_bin_op_string op = Some f →
     sem_bin_op op (ELit (LitString s1)) (λ e2 e, ∃ s2,
       e2 = ELit (LitString s2) ∧ e = ELit (f s1 s2))
  | MatchStringSem s :
     sem_bin_op MatchStringOp (ELit (LitString s)) (λ e2 e,
       e2 = ELit LitNull ∧
       match s with
       | EmptyString => e = EAttr {[
           "empty" := AttrN (ELit (LitBool true));
           "head" := AttrN (ELit LitNull);
           "tail" := AttrN (ELit LitNull) ]}
       | String a s => e = EAttr {[
           "empty" := AttrN (ELit (LitBool false));
           "head" := AttrN (ELit (LitString (String a EmptyString)));
           "tail" := AttrN (ELit (LitString s)) ]}
       end)
  | LtListSem es :
     sem_bin_op LtOp (EList es) (λ e2 e, ∃ es',
       e2 = EList es' ∧
       e = sem_lt_list es es')
  | MatchListSem es :
     sem_bin_op MatchListOp (EList es) (λ e2 e,
       e2 = ELit LitNull ∧
       match es with
       | [] => e = EAttr {[
           "empty" := AttrN (ELit (LitBool true));
           "head" := AttrN (ELit LitNull);
           "tail" := AttrN (ELit LitNull) ]}
       | e' :: es => e = EAttr {[
           "empty" := AttrN (ELit (LitBool false));
           "head" := AttrN e';
           "tail" := AttrN (EList es) ]}
       end)
  | AppendListSem es :
     sem_bin_op AppendListOp (EList es) (λ e2 e, ∃ es',
       e2 = EList es' ∧
       e = EList (es ++ es'))
  | SelectAttrSem αs :
     no_recs αs →
     sem_bin_op SelectAttrOp (EAttr αs) (λ e2 e, ∃ x,
       e2 = ELit (LitString x) ∧ αs !! x = Some (AttrN e))
  | UpdateAttrSem αs1 :
     no_recs αs1 →
     sem_bin_op UpdateAttrOp (EAttr αs1) (λ e2 e, ∃ αs2,
       e2 = EAttr αs2 ∧ no_recs αs2 ∧ e = EAttr (αs2 ∪ αs1))
  | HasAttrSem αs :
     no_recs αs →
     sem_bin_op HasAttrOp (EAttr αs) (λ e2 e, ∃ x,
       e2 = ELit (LitString x) ∧ e = ELit (LitBool (bool_decide (is_Some (αs !! x)))))
  | DeleteAttrSem αs :
     no_recs αs →
     sem_bin_op DeleteAttrOp (EAttr αs) (λ e2 e, ∃ x,
       e2 = ELit (LitString x) ∧ e = EAttr (delete x αs))
  | SingletonAttrSem x :
     sem_bin_op SingletonAttrOp (ELit (LitString x)) (λ e2 e,
       e2 = ELit LitNull ∧
       e = EAbs "t" (EAttr {[ x := AttrN (EId' "t") ]}))
  | MatchAttrSem αs :
     no_recs αs →
     sem_bin_op MatchAttrOp (EAttr αs) (λ e2 e,
       e2 = ELit LitNull ∧
       ((αs = ∅ ∧
         e = EAttr {[
           "empty" := AttrN (ELit (LitBool true));
           "key" := AttrN (ELit LitNull);
           "head" := AttrN (ELit LitNull);
           "tail" := AttrN (ELit LitNull) ]}) ∨
       (∃ x e',
         αs !! x = Some (AttrN e') ∧
         (∀ y, is_Some (αs !! y) → attr_le x y) ∧
         e = EAttr {[
           "empty" := AttrN (ELit (LitBool false));
           "key" := AttrN (ELit (LitString x));
           "head" := AttrN e';
           "tail" := AttrN (EAttr (delete x αs)) ]})))
  | FunctionArgsAbsSem x e' :
     sem_bin_op FunctionArgsOp (EAbs x e') (λ e2 e,
       e2 = ELit LitNull ∧
       e = EAttr ∅)
  | FunctionArgsAbsMatchSem ms strict e' :
     sem_bin_op FunctionArgsOp (EAbsMatch ms strict e') (λ e2 e,
       e2 = ELit LitNull ∧
       e = EAttr (AttrN ∘ ELit ∘ LitBool ∘ from_option (λ _, true) false <$> ms))
  | TypeOfSem e1 :
     sem_bin_op TypeOfOp e1 (λ e2 e, ∃ x,
       e2 = ELit LitNull ∧
       type_of_expr e1 = Some x ∧
       e = ELit (LitString x)).

Inductive matches :
     gmap string expr → gmap string (option expr) → bool → gmap string attr → Prop :=
  | MatchEmpty strict :
     matches ∅ ∅ strict ∅
  | MatchAny es :
     matches es ∅ false ∅
  | MatchAvail x e es ms md strict βs :
     es !! x = None →
     ms !! x = None →
     matches es ms strict βs →
     matches (<[x:=e]> es) (<[x:=md]> ms) strict (<[x:=AttrN e]> βs)
  | MatchOptDefault x es ms d strict βs :
     es !! x = None →
     ms !! x = None →
     matches es ms strict βs →
     matches es (<[x:=Some d]> ms) strict (<[x:=AttrR d]> βs).

Reserved Notation "e1 -{ μ }-> e2"
  (right associativity, at level 55, μ at level 1, format "e1  -{ μ }->  e2").

Inductive ctx1 : mode → mode → (expr → expr) → Prop :=
  | CList es1 es2 :
     Forall (final DEEP) es1 →
     ctx1 DEEP DEEP (λ e, EList (es1 ++ e :: es2))
  | CAttr αs x :
     no_recs αs →
     αs !! x = None →
     (∀ y α, αs !! y = Some α → final DEEP (attr_expr α) ∨ attr_le x y) →
     ctx1 DEEP DEEP (λ e, EAttr (<[x:=AttrN e]> αs))
  | CAppL μ e2 :
     ctx1 SHALLOW μ (λ e1, EApp e1 e2)
  | CAppR μ ms strict e1 :
     ctx1 SHALLOW μ (EApp (EAbsMatch ms strict e1))
  | CSeq μ μ' e2 :
     ctx1 μ' μ (λ e1, ESeq μ' e1 e2)
  | CLetAttr μ k e2 :
     ctx1 SHALLOW μ (λ e1, ELetAttr k e1 e2)
  | CBinOpL μ op e2 :
     ctx1 SHALLOW μ (λ e1, EBinOp op e1 e2)
  | CBinOpR μ op e1 Φ :
     final SHALLOW e1 →
     sem_bin_op op e1 Φ →
     ctx1 SHALLOW μ (EBinOp op e1)
  | CIf μ e2 e3 :
     ctx1 SHALLOW μ (λ e1, EIf e1 e2 e3).

Inductive step : mode → relation expr :=
  | Sβ μ x e1 e2 :
     EApp (EAbs x e1) e2 -{μ}-> subst {[x:=(ABS, e2)]} e1
  | SβMatch μ ms strict e1 αs βs :
     no_recs αs →
     matches (attr_expr <$> αs) ms strict βs →
     EApp (EAbsMatch ms strict e1) (EAttr αs) -{μ}->
       subst (indirects βs) e1
  | SFunctor μ αs e1 e2 :
     no_recs αs →
     αs !! "__functor" = Some (AttrN e1) →
     EApp (EAttr αs) e2 -{μ}-> EApp (EApp e1 (EAttr αs)) e2
  | SSeqFinal μ μ' e1 e2 :
     final μ' e1 → ESeq μ' e1 e2 -{μ}-> e2
  | SLetAttrAttr μ k αs e :
     no_recs αs →
     ELetAttr k (EAttr αs) e -{μ}-> subst ((k,.) ∘ attr_expr <$> αs) e
  | SAttr_rec μ αs :
     ¬no_recs αs →
     EAttr αs -{μ}->
       EAttr (AttrN ∘ from_attr (subst (indirects αs)) id <$> αs)
  | SBinOp μ op e1 Φ e2 e :
     final SHALLOW e1 →
     final SHALLOW e2 →
     sem_bin_op op e1 Φ → Φ e2 e →
     EBinOp op e1 e2 -{μ}-> e
  | SIfBool μ b e2 e3 :
     EIf (ELit (LitBool b)) e2 e3 -{μ}-> if b then e2 else e3
  | SId μ x k e :
     EId x (Some (k,e)) -{μ}-> e
  | SCtx K μ μ' e e' :
     ctx1 μ μ' K → e -{μ}-> e' → K e -{μ'}-> K e'
where "e1 -{ μ }-> e2" := (step μ e1 e2).

Notation "e1 -{ μ }->* e2" := (rtc (step μ) e1 e2)
  (right associativity, at level 55, μ at level 1, format "e1  -{ μ }->*  e2").
Notation "e1 -{ μ }->+ e2" := (tc (step μ) e1 e2)
  (right associativity, at level 55, μ at level 1, format "e1  -{ μ }->+  e2").

Definition stuck (μ : mode) (e : expr) : Prop :=
  nf (step μ) e ∧ ¬final μ e.

(** Induction *)
Fixpoint expr_size (e : expr) : nat :=
  match e with
  | ELit _ => 1
  | EId _ mkd => S (from_option (expr_size ∘ snd) 1 mkd)
  | EAbs _ d => S (expr_size d)
  | EAbsMatch ms _ e =>
     S (map_sum_with (from_option expr_size 1) ms + expr_size e)
  | EApp e1 e2 | ESeq _ e1 e2 => S (expr_size e1 + expr_size e2)
  | EList es => S (sum_list_with expr_size es)
  | EAttr eτs => S (map_sum_with (expr_size ∘ attr_expr) eτs)
  | ELetAttr _ e1 e2 => S (expr_size e1 + expr_size e2)
  | EBinOp _ e1 e2 => S (expr_size e1 + expr_size e2)
  | EIf e1 e2 e3 => S (expr_size e1 + expr_size e2 + expr_size e3)
  end.

Lemma expr_ind (P : expr → Prop) :
  (∀ b, P (ELit b)) →
  (∀ x mkd, from_option (P ∘ snd) True mkd → P (EId x mkd)) →
  (∀ x e, P e → P (EAbs x e)) →
  (∀ ms strict e,
    map_Forall (λ _, from_option P True) ms → P e → P (EAbsMatch ms strict e)) →
  (∀ e1 e2, P e1 → P e2 → P (EApp e1 e2)) →
  (∀ μ e1 e2, P e1 → P e2 → P (ESeq μ e1 e2)) →
  (∀ es, Forall P es → P (EList es)) →
  (∀ αs, map_Forall (λ _, P ∘ attr_expr) αs → P (EAttr αs)) →
  (∀ k e1 e2, P e1 → P e2 → P (ELetAttr k e1 e2)) →
  (∀ op e1 e2, P e1 → P e2 → P (EBinOp op e1 e2)) →
  (∀ e1 e2 e3, P e1 → P e2 → P e3 → P (EIf e1 e2 e3)) →
  ∀ e, P e.
Proof.
  intros Hlit Hid Habs Hmatch Happ Hseq Hlist Hattr Hlet Hop Hif e.
  induction (Nat.lt_wf_0_projected expr_size e) as [e _ IH].
  destruct e; repeat destruct select (option _); simpl in *; eauto with lia.
  - apply Hmatch; [|by eauto with lia]=> y [e'|] Hx //=. apply IH, Nat.lt_succ_r.
    etrans; [|apply Nat.le_add_r].
    eapply (map_sum_with_lookup_le (from_option expr_size 1) _ _ _ Hx).
  - apply Hlist, Forall_forall=> e ?. apply IH, Nat.lt_succ_r.
    by apply sum_list_with_in.
  - apply Hattr, map_Forall_lookup=> y e ?. apply IH, Nat.lt_succ_r.
    by eapply (map_sum_with_lookup_le (expr_size ∘ attr_expr)).
Qed.