Skip to content

Commit aada811

Browse files
authored
Prove termination of LMonoTy[s].resolveAliases (#354)
*Description of changes:* Refactoring to prove the termination of `resolveAliases`. `LMonoTy.aliasDef?` can now throw an exception indicating an implementation error, instead of a panic. As such, `resolveAliases` can now also throw an exception. Minor fixes to proofs and formatting. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 16705a3 commit aada811

4 files changed

Lines changed: 224 additions & 185 deletions

File tree

Strata/DL/Lambda/LExprTypeEnv.lean

Lines changed: 124 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -552,113 +552,149 @@ instance : Inhabited (Option LMonoTy × TEnv IDMeta) where
552552
default := (none, TEnv.default)
553553

554554
/--
555-
Return the instantiated definition of `mty` -- which is expected to be
556-
`.tcons name args` -- if it is a type alias registered in the typing
557-
environment `T`.
558-
559-
This function does not descend into the subtrees of `mty`, nor does it check
560-
whether the de-aliased types are registered/known.
561-
-/
562-
def LMonoTy.aliasDef? [ToFormat IDMeta] (mty : LMonoTy) (Env : TEnv IDMeta) :
563-
(Option LMonoTy × TEnv IDMeta) :=
555+
Return the instantiated definition of `.tcons name args` if it is a registered
556+
type alias.
557+
558+
This function does not descend into the subtrees of types in `args`, nor does it
559+
check whether the de-aliased types are registered/known.
560+
-/
561+
def LMonoTy.tconsAlias [ToFormat IDMeta] (name : String) (args : LMonoTys)
562+
(Env : TEnv IDMeta) : Except Format (LMonoTy × TEnv IDMeta) := do
563+
let inputMty := .tcons name args
564+
-- Look for a matching alias with same name and arity.
565+
let matchingAlias := Env.context.aliases.find?
566+
(fun a => a.name == name && a.typeArgs.length == args.length)
567+
match matchingAlias with
568+
| none => return (inputMty, Env)
569+
| some alias =>
570+
-- Create instantiation pair: [alias pattern, alias definition].
571+
-- The alias pattern and definition share the same type variables here.
572+
let aliasPattern := .tcons name (alias.typeArgs.map .ftvar)
573+
let typesToInstantiate := [aliasPattern, alias.type]
574+
-- Instantiate both types with fresh variables.
575+
-- (FIXME) Would be nice to have the following LHS instead of
576+
-- `instTypesWithEnv`, but Lean erases the RHS needed in the length proof
577+
-- below if we do so.
578+
-- let (instantiatedTypes, updatedEnv) := ...
579+
let instTypesWithEnv := LMonoTys.instantiateEnv alias.typeArgs typesToInstantiate Env
580+
have : 1 < instTypesWithEnv.fst.length := by
581+
simp only [LMonoTys.instantiateEnv, liftGenEnv, instTypesWithEnv, typesToInstantiate]
582+
have hlen := @LMonoTys.instantiate_length _ alias.typeArgs typesToInstantiate Env.genEnv _
583+
simp [typesToInstantiate] at hlen
584+
grind
585+
-- Extract the instantiated pattern and definition.
586+
let instantiatedPattern := instTypesWithEnv.fst[0]'(by grind)
587+
let instantiatedDefinition := instTypesWithEnv.fst[1]'(by grind)
588+
let newEnv := instTypesWithEnv.snd
589+
-- Unify the input type with the instantiated alias pattern.
590+
match Constraints.unify [(inputMty, instantiatedPattern)] newEnv.stateSubstInfo with
591+
| .error e =>
592+
-- We don't expect this unification to fail; after all,
593+
-- `instantiatedPattern` is more general than `.tcons name args`.
594+
.error f!"🚨 Implementation error: \
595+
Unification failed in LMonoTy.tconsAlias: {e}"
596+
| .ok substInfo =>
597+
-- Apply the substitution to get the final definition.
598+
let finalDefinition := instantiatedDefinition.subst substInfo.subst
599+
return (finalDefinition, newEnv.updateSubst substInfo)
600+
601+
/--
602+
Return the instantiated definition of `mty` if it is a registered
603+
type alias.
604+
605+
This function does not descend into any subtrees of types in `args`, nor does it
606+
check whether the de-aliased types are registered/known.
607+
-/
608+
def LMonoTy.aliasDef? [ToFormat IDMeta] (mty : LMonoTy) (Env : TEnv IDMeta)
609+
: Except Format (LMonoTy × TEnv IDMeta) := do
564610
match mty with
565-
| .ftvar _ =>
566-
-- We can't have a free variable be the LHS of an alias definition because
567-
-- then it will unify with every type.
568-
(none, Env)
569-
| .bitvec _ =>
570-
-- A bitvector cannot be a type alias.
571-
(none, Env)
572-
| .tcons name args =>
573-
match Env.context.aliases.find? (fun a => a.name == name && a.typeArgs.length == args.length) with
574-
| none => (none, Env)
575-
| some alias =>
576-
let (lst, Env) := LMonoTys.instantiateEnv alias.typeArgs [(.tcons name (alias.typeArgs.map (fun a => .ftvar a))), alias.type] Env
577-
-- (FIXME): Use `LMonoTys.instantiate_length` to remove the `!` below.
578-
let alias_inst := lst[0]!
579-
let alias_def := lst[1]!
580-
match Constraints.unify [(mty, alias_inst)] Env.stateSubstInfo with
581-
| .error e =>
582-
panic! s!"[LMonoTy.aliasDef?] {format e}"
583-
| .ok S =>
584-
(alias_def.subst S.subst, Env.updateSubst S)
611+
| .tcons name args => LMonoTy.tconsAlias name args Env
612+
| .bitvec _ | .ftvar _ => return (mty, Env)
585613

586614
mutual
587615
/--
588616
De-alias `mty`, including at the subtrees.
589617
-/
590-
partial def LMonoTy.resolveAliases [ToFormat IDMeta] (mty : LMonoTy) (Env : TEnv IDMeta) :
591-
(Option LMonoTy × TEnv IDMeta) :=
592-
let (maybe_mty, Env) := LMonoTy.aliasDef? mty Env
593-
match maybe_mty with
594-
| some (.tcons name args) =>
595-
let (args', Env) := LMonoTys.resolveAliases args Env.context.aliases Env
596-
match args' with
597-
| none => (some (.tcons name args), Env)
598-
| some args' => (some (.tcons name args'), Env)
599-
| some mty' => (some mty', Env)
600-
| none =>
601-
match mty with
602-
| .ftvar _ => (some mty, Env)
603-
| .bitvec _ => (some mty, Env)
604-
| .tcons name mtys =>
605-
let (maybe_mtys, Env) := LMonoTys.resolveAliases mtys Env.context.aliases Env
606-
match maybe_mtys with
607-
| none => (none, Env)
608-
| some mtys' => (some (.tcons name mtys'), Env)
618+
def LMonoTy.resolveAliases [ToFormat IDMeta] (mty : LMonoTy) (Env : TEnv IDMeta) :
619+
Except Format (LMonoTy × TEnv IDMeta) := do
620+
match mty with
621+
| .ftvar _ =>
622+
-- Free variables cannot be type aliases (would unify with every type).
623+
return (mty, Env)
624+
| .bitvec _ =>
625+
-- Bitvectors cannot be type aliases.
626+
return (mty, Env)
627+
| .tcons name args =>
628+
let (args', Env) ← LMonoTys.resolveAliases args Env
629+
let (mty', Env) ← LMonoTy.tconsAlias name args' Env
630+
return (mty', Env)
609631

610632
/--
611633
De-alias `mtys`, including at the subtrees.
612634
-/
613-
partial def LMonoTys.resolveAliases [ToFormat IDMeta] (mtys : LMonoTys) (aliases : List TypeAlias) (Env : (TEnv IDMeta)) :
614-
(Option LMonoTys × (TEnv IDMeta)) :=
615-
match mtys with
616-
| [] => (some [], Env)
617-
| mty :: mrest =>
618-
let (mty', Env) := LMonoTy.resolveAliases mty Env
619-
let (mrest', Env) := LMonoTys.resolveAliases mrest aliases Env
620-
if h : mty'.isSome && mrest'.isSome then
621-
((mty'.get (by simp_all) :: mrest'.get (by simp_all)), Env)
622-
else
623-
(none, Env)
635+
def LMonoTys.resolveAliases [ToFormat IDMeta] (mtys : LMonoTys)
636+
(Env : (TEnv IDMeta)) : Except Format (LMonoTys × (TEnv IDMeta)) := do
637+
match mtys with
638+
| [] => return ([], Env)
639+
| mty :: mrest =>
640+
let (mty', Env) ← LMonoTy.resolveAliases mty Env
641+
let (mrest', Env) ← LMonoTys.resolveAliases mrest Env
642+
return (mty' :: mrest', Env)
624643
end
625644

626645
/--
627646
Resolve type aliases in a list of constructors.
628647
-/
629-
def LConstrs.resolveAliases [ToFormat IDMeta] (constrs : List (LConstr IDMeta)) (Env : TEnv IDMeta) :
630-
List (LConstr IDMeta) × TEnv IDMeta :=
631-
constrs.foldr (fun c (acc, Env) =>
632-
let names := c.args.map (·.fst)
633-
let tys := c.args.map (·.snd)
634-
let (tys', Env) := LMonoTys.resolveAliases tys Env.context.aliases Env
635-
let args' := names.zip (tys'.getD tys)
636-
({ c with args := args' } :: acc, Env)) ([], Env)
637-
638-
theorem LConstrs.resolveAliases_length [ToFormat IDMeta] (constrs : List (LConstr IDMeta)) (Env : TEnv IDMeta) :
639-
(LConstrs.resolveAliases constrs Env).fst.length = constrs.length := by
640-
simp only [LConstrs.resolveAliases]
641-
induction constrs <;> grind
648+
def LConstrs.resolveAliases [ToFormat IDMeta]
649+
(constrs : List (LConstr IDMeta)) (Env : TEnv IDMeta) :
650+
Except Format (List (LConstr IDMeta) × TEnv IDMeta) := do
651+
constrs.foldrM (fun c (acc, Env) => do
652+
let (args', Env) ← go c.args Env
653+
return ({ c with args := args' } :: acc, Env)) ([], Env)
654+
where go args Env := do
655+
let names := args.map (·.fst)
656+
let tys := args.map (·.snd)
657+
let (tys', Env) ← LMonoTys.resolveAliases tys Env
658+
let args' := names.zip tys'
659+
return (args', Env)
660+
661+
theorem LConstrs.resolveAliases_length [ToFormat IDMeta]
662+
(constrs : List (LConstr IDMeta)) (Env : TEnv IDMeta)
663+
(result : List (LConstr IDMeta) × TEnv IDMeta)
664+
(h : LConstrs.resolveAliases constrs Env = .ok result) :
665+
result.fst.length = constrs.length := by
666+
simp only [LConstrs.resolveAliases] at h
667+
induction constrs generalizing result with
668+
| nil => simp_all [List.foldrM, pure, Except.pure]; grind
669+
| cons hd tl ih =>
670+
simp [List.foldrM_cons, Bind.bind, Except.bind, Pure.pure, Except.pure] at h ih
671+
split at h
672+
case h_1 => simp_all
673+
case h_2 x v heq =>
674+
have ih' := @ih v.fst v.snd heq
675+
grind
642676

643677
/--
644678
Resolve type aliases in constructor argument types within a mutual datatype block.
645679
-/
646680
def MutualDatatype.resolveAliases [ToFormat IDMeta] (block : MutualDatatype IDMeta) (Env : TEnv IDMeta) :
647-
MutualDatatype IDMeta × TEnv IDMeta :=
648-
block.foldr (fun d (acc, Env) =>
681+
Except Format (MutualDatatype IDMeta × TEnv IDMeta) := do
682+
block.foldrM (fun d (acc, Env) =>
649683
match h: LConstrs.resolveAliases d.constrs Env with
650-
| (constrs', Env) =>
684+
| .error e => .error e
685+
| .ok (constrs', Env) =>
651686
have h : constrs'.length != 0 := by
652687
rename_i E
653688
have Hlen := LConstrs.resolveAliases_length d.constrs E
654689
rw[h] at Hlen
655690
cases d; grind
656-
({ d with constrs := constrs', constrs_ne := h } :: acc, Env)) ([], Env)
691+
return ({ d with constrs := constrs', constrs_ne := h } :: acc, Env)) ([], Env)
657692

658693
/--
659694
Instantiate and de-alias `ty`, including at the subtrees.
660695
-/
661-
def LTy.resolveAliases [ToFormat IDMeta] (ty : LTy) (Env : TEnv IDMeta) : Option LMonoTy × TEnv IDMeta :=
696+
def LTy.resolveAliases [ToFormat IDMeta] (ty : LTy) (Env : TEnv IDMeta) :
697+
Except Format (LMonoTy × TEnv IDMeta) :=
662698
let (mty, Env') := ty.instantiate Env.genEnv
663699
let Env := {Env with genEnv := Env'}
664700
LMonoTy.resolveAliases mty Env
@@ -696,11 +732,14 @@ perform resolution of type aliases and subsequent checks for registered types.
696732
def LMonoTy.instantiateWithCheck (mty : LMonoTy) (C: LContext T) (Env : TEnv T.IDMeta) :
697733
Except Format (LMonoTy × (TEnv T.IDMeta)) := do
698734
let ftvs := mty.freeVars
699-
let (mtys, Env) := LMonoTys.instantiateEnv ftvs [mty] Env
700-
let mtyi := mtys[0]!
701-
let (mtyi, Env) := match mtyi.resolveAliases Env with
702-
| (some ty', Env) => (ty', Env)
703-
| (none, Env) => (mtyi, Env)
735+
let instTypesWithEnv := LMonoTys.instantiateEnv ftvs [mty] Env
736+
have : 0 < instTypesWithEnv.fst.length := by
737+
simp [instTypesWithEnv, LMonoTys.instantiateEnv, liftGenEnv]
738+
have := @LMonoTys.instantiate_length _ ftvs [mty] Env.genEnv _
739+
grind
740+
let mtyi := instTypesWithEnv.fst[0]'(by exact this)
741+
let Env := instTypesWithEnv.snd
742+
let (mtyi, Env) ← mtyi.resolveAliases Env
704743
if isInstanceOfKnownType mtyi C
705744
then return (mtyi, Env)
706745
else .error f!"Type {mty} is not an instance of a previously registered type!\n\
@@ -712,11 +751,7 @@ for registered types.
712751
-/
713752
def LTy.instantiateWithCheck [ToFormat T.IDMeta] (ty : LTy) (C: LContext T) (Env : TEnv T.IDMeta) :
714753
Except Format (LMonoTy × TEnv T.IDMeta) := do
715-
let (mty, Env) := match ty.resolveAliases Env with
716-
| (some ty', Env) => (ty', Env)
717-
| (none, Env) =>
718-
let (ty, Env') := ty.instantiate Env.genEnv
719-
(ty, {Env with genEnv := Env'})
754+
let (mty, Env) ← ty.resolveAliases Env
720755
if isInstanceOfKnownType mty C
721756
then return (mty, Env)
722757
else .error f!"Type {ty} is not an instance of a previously registered type!\n\
@@ -726,7 +761,8 @@ def LTy.instantiateWithCheck [ToFormat T.IDMeta] (ty : LTy) (C: LContext T) (Env
726761
Instantiate the scheme `ty` and apply the global substitution `Env.state.subst` to
727762
it.
728763
-/
729-
def LTy.instantiateAndSubst (ty : LTy) (C: LContext T) (Env : TEnv T.IDMeta) : Except Format (LMonoTy × TEnv T.IDMeta) := do
764+
def LTy.instantiateAndSubst (ty : LTy) (C: LContext T) (Env : TEnv T.IDMeta)
765+
: Except Format (LMonoTy × TEnv T.IDMeta) := do
730766
let (mty, Env) ← LTy.instantiateWithCheck ty C Env
731767
let mty := LMonoTy.subst Env.stateSubstInfo.subst mty
732768
return (mty, Env)

Strata/Languages/Core/ProgramType.lean

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ def typeCheck (C: Core.Expression.TyContext) (Env : Core.Expression.TyEnv) (prog
3636
let fileRange := Imperative.getFileRange decl.metadata |>.getD FileRange.unknown
3737
-- Add all names from the declaration (multiple for mutual datatypes)
3838
let idents ← C.idents.addListWithError decl.names (fun n =>
39-
(DiagnosticModel.withRange fileRange f!"Error in {decl.kind} {n}: a declaration of this name already exists.")
40-
)
39+
(DiagnosticModel.withRange fileRange f!"Error in {decl.kind} {n}: a declaration of this name already exists."))
4140
let C := {C with idents}
4241
let (decl', C, Env) ←
4342
match decl with
@@ -65,7 +64,7 @@ def typeCheck (C: Core.Expression.TyContext) (Env : Core.Expression.TyEnv) (prog
6564
|>.mapError (fun e => DiagnosticModel.withRange fileRange e)
6665
.ok (.type td, C, Env)
6766
| .data block =>
68-
let (block, Env) := MutualDatatype.resolveAliases block Env
67+
let (block, Env) MutualDatatype.resolveAliases block Env |>.mapError (fun e => DiagnosticModel.withRange fileRange e)
6968
let C ← C.addMutualBlock block
7069
.ok (.type (.data block), C, Env)
7170
catch e =>

Strata/Languages/Core/ProgramWF.lean

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,9 @@ theorem Except.mapError_ok {α β γ} {f : α → β} {e : Except α γ} {v : γ
309309
If a program typechecks successfully, then every identifier in the list of
310310
program decls is not in the original `LContext`
311311
-/
312-
theorem Program.typeCheckFunctionDisjoint : Program.typeCheck.go p C T decls acc = .ok (d', T') → (∀ x, x ∈ Program.getNames.go decls → ¬ C.idents.contains x) := by
312+
theorem Program.typeCheckFunctionDisjoint :
313+
Program.typeCheck.go p C T decls acc = .ok (d', T') →
314+
(∀ x, x ∈ Program.getNames.go decls → ¬ C.idents.contains x) := by
313315
induction decls generalizing acc p d' T' T C with
314316
| nil => simp[Program.getNames.go]
315317
| cons r rs IH =>
@@ -382,7 +384,7 @@ theorem Program.typeCheckFunctionDisjoint : Program.typeCheck.go p C T decls acc
382384
simp only[LContext.addFactoryFunction] at a_notin
383385
grind
384386
| type t =>
385-
cases t with (simp only[] at Hty <;> split_contra_case Hty <;> rename_i Hty <;> split_contra_case Hty <;> rename_i Hty)
387+
cases t with (simp only[] at Hty <;> split_contra_case Hty <;> rename_i Hty; split_contra Hty <;> rename_i Hty)
386388
| con c =>
387389
specialize (IH tcok)
388390
match hx with
@@ -392,7 +394,7 @@ theorem Program.typeCheckFunctionDisjoint : Program.typeCheck.go p C T decls acc
392394
grind
393395
| Or.inr (Exists.intro a (And.intro a_in x_in)) =>
394396
have Hcontains := Identifiers.addListWithErrorContains Hid x
395-
have := addKnownTypeWithErrorIdents Hty
397+
have := addKnownTypeWithErrorIdents (by assumption)
396398
grind
397399
| syn s =>
398400
specialize (IH tcok)
@@ -413,7 +415,8 @@ theorem Program.typeCheckFunctionDisjoint : Program.typeCheck.go p C T decls acc
413415
grind
414416
| Or.inr (Exists.intro a (And.intro a_in x_in)) =>
415417
have Hcontains := Identifiers.addListWithErrorContains Hid x
416-
have := addMutualBlockIdents Hty;
418+
split at Hty <;> simp_all
419+
have := addMutualBlockIdents (by assumption);
417420
grind
418421

419422
/--
@@ -477,14 +480,14 @@ theorem Program.typeCheckFunctionNoDup : Program.typeCheck.go p C T decls acc =
477480
| type td =>
478481
specialize (IH tcok)
479482
apply List.nodup_append.mpr
480-
cases td with (simp only[] at Hty <;> split_contra_case Hty <;> rename_i Hty <;> split_contra_case Hty <;> rename_i Hty)
483+
cases td with (simp only[] at Hty <;> split_contra_case Hty <;> rename_i Hty <;> split_contra Hty <;> rename_i Hty)
481484
| con c =>
482485
constructor; simp[Decl.names, TypeDecl.names]; constructor; apply IH
483486
intros a a_in; simp[Decl.names, TypeDecl.names] at a_in; subst_vars
484487
intros x x_in;
485488
have Hdisj:= Program.typeCheckFunctionDisjoint tcok _ x_in
486489
have x_contains := (Identifiers.addListWithErrorContains Hid x)
487-
have := addKnownTypeWithErrorIdents Hty
490+
have := addKnownTypeWithErrorIdents (by assumption)
488491
simp_all[Decl.names, TypeDecl.names];
489492
grind
490493
| syn s =>
@@ -504,7 +507,8 @@ theorem Program.typeCheckFunctionNoDup : Program.typeCheck.go p C T decls acc =
504507
have Hdisj:= Program.typeCheckFunctionDisjoint tcok _ x_in
505508
have x_contains := (Identifiers.addListWithErrorContains Hid x)
506509
simp_all[Decl.names, TypeDecl.names];
507-
have := addMutualBlockIdents Hty;
510+
split at Hty <;> simp_all
511+
have := addMutualBlockIdents (by assumption);
508512
grind
509513

510514
/--

0 commit comments

Comments
 (0)