Skip to content

Commit 3fa99a7

Browse files
committed
feat: infer Prop for inductive/structure when defining syntactic subsingletons
A `Prop`-valued inductive type is a syntactic subsingleton if it has at most one constructor and all the arguments to the constructor are in `Prop`. Such types have large elimination, so they could be defined in `Type` or `Prop` without any trouble, though users tend to expect that such types define a `Prop` and need to learn to insert `: Prop` manually. Currently, the default universe for types is `Type`. This PR adds a heuristic: if a type is a syntactic subsingleton with exactly one constructor, and the constructor has at least one parameter, then the `inductive` command will prefer creating a `Prop` instead of a `Type`. For `structure`, we ask for at least one field. More generally, for mutual inductives, each type needs to be a syntactic subsingleton, at least one type must have one constructor, and at least one constructor must have at least one parameter. Thanks to @arthur-adjedj for the investigation in leanprover#2695. Closes leanprover#2690
1 parent fdd5aec commit 3fa99a7

File tree

3 files changed

+133
-4
lines changed

3 files changed

+133
-4
lines changed

src/Lean/Elab/Inductive.lean

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,9 @@ where
425425
levelMVarToParam' (type : Expr) : TermElabM Expr := do
426426
Term.levelMVarToParam type (except := fun mvarId => univToInfer? == some mvarId)
427427

428-
def mkResultUniverse (us : Array Level) (rOffset : Nat) : Level :=
428+
def mkResultUniverse (us : Array Level) (rOffset : Nat) (preferProp : Bool) : Level :=
429429
if us.isEmpty && rOffset == 0 then
430-
levelOne
430+
if preferProp then levelZero else levelOne
431431
else
432432
let r := Level.mkNaryMax us.toList
433433
if rOffset == 0 && !r.isZero && !r.isNeverZero then
@@ -512,6 +512,22 @@ where
512512
for ctorParam in ctorParams[numParams:] do
513513
accLevelAtCtor ctor ctorParam r rOffset
514514

515+
/--
516+
Heuristic: we prefer a `Prop` over `Type` if the inductive type could be a syntactic subsingleton.
517+
However, we prefer `Type` in the following cases:
518+
- if there are no constructors
519+
- if each constructor has no parameters
520+
-/
521+
private def isPropCandidate (indTypes : List InductiveType) : MetaM Bool := do
522+
unless indTypes.foldl (fun n indType => max n indType.ctors.length) 0 == 1 do
523+
return false
524+
for indType in indTypes do
525+
for ctor in indType.ctors do
526+
let nparams ← forallTelescopeReducing ctor.type fun ctorParams _ => pure ctorParams.size
527+
unless nparams == 0 do
528+
return true
529+
return false
530+
515531
private def updateResultingUniverse (views : Array InductiveView) (numParams : Nat) (indTypes : List InductiveType) : TermElabM (List InductiveType) := do
516532
let r ← getResultingUniverse indTypes
517533
let rOffset : Nat := r.getOffset
@@ -520,7 +536,7 @@ private def updateResultingUniverse (views : Array InductiveView) (numParams : N
520536
throwError "failed to compute resulting universe level of inductive datatype, provide universe explicitly: {r}"
521537
let us ← collectUniverses views r rOffset numParams indTypes
522538
trace[Elab.inductive] "updateResultingUniverse us: {us}, r: {r}, rOffset: {rOffset}"
523-
let rNew := mkResultUniverse us rOffset
539+
let rNew := mkResultUniverse us rOffset (← isPropCandidate indTypes)
524540
assignLevelMVar r.mvarId! rNew
525541
indTypes.mapM fun indType => do
526542
let type ← instantiateMVars indType.type

src/Lean/Elab/Structure.lean

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,14 +632,21 @@ where
632632
msg := msg ++ "\nrecall that Lean only infers the resulting universe level automatically when there is a unique solution for the universe level constraints, consider explicitly providing the structure resulting universe level"
633633
throwError msg
634634

635+
/--
636+
Heuristic: we prefer a `Prop` instead of a `Type` structure when it could be a syntactic subsingleton.
637+
However, if there are no fields, we prefer `Type`.
638+
-/
639+
private def isPropCandidate (fieldInfos : Array StructFieldInfo) : Bool :=
640+
!fieldInfos.isEmpty
641+
635642
private def updateResultingUniverse (fieldInfos : Array StructFieldInfo) (type : Expr) : TermElabM Expr := do
636643
let r ← getResultUniverse type
637644
let rOffset : Nat := r.getOffset
638645
let r : Level := r.getLevelOffset
639646
match r with
640647
| Level.mvar mvarId =>
641648
let us ← collectUniversesFromFields r rOffset fieldInfos
642-
let rNew := mkResultUniverse us rOffset
649+
let rNew := mkResultUniverse us rOffset (isPropCandidate fieldInfos)
643650
assignLevelMVar mvarId rNew
644651
instantiateMVars type
645652
| _ => throwError "failed to compute resulting universe level of structure, provide universe explicitly"

tests/lean/run/2690.lean

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/-!
2+
# `Prop`-valued `inductive`/`structure` by default
3+
4+
When the inductive types are syntactic subsingletons and could be `Prop`,
5+
they may as well be `Prop`.
6+
7+
Issue: https://github.com/leanprover/lean4/issues/2690
8+
-/
9+
10+
/-!
11+
Subsingleton, but no constructors. Type.
12+
-/
13+
inductive I0 where
14+
/-- info: I0 : Type -/
15+
#guard_msgs in #check I0
16+
17+
/-!
18+
One constructor, has a Prop parameter. Prop.
19+
-/
20+
inductive I1 where
21+
| a (h : True)
22+
/-- info: I1 : Prop -/
23+
#guard_msgs in #check I1
24+
25+
/-!
26+
One constructor, no parameters. Type.
27+
-/
28+
inductive I2 where
29+
| a
30+
/-- info: I2 : Type -/
31+
#guard_msgs in #check I2
32+
33+
/-!
34+
Two constructors. Type
35+
-/
36+
inductive I3 where
37+
| a | b
38+
/-- info: I3 : Type -/
39+
#guard_msgs in #check I3
40+
41+
/-!
42+
Mutually inductives, both syntactic subsingletons,
43+
even if one doesn't have a constructor,
44+
and one has no parameters.
45+
-/
46+
mutual
47+
inductive C1 where
48+
inductive C2 where
49+
| a (h : True)
50+
inductive C3 where
51+
| b
52+
end
53+
/-- info: C1 : Prop -/
54+
#guard_msgs in #check C1
55+
/-- info: C2 : Prop -/
56+
#guard_msgs in #check C2
57+
/-- info: C3 : Prop -/
58+
#guard_msgs in #check C3
59+
60+
/-!
61+
Type parameter (promoted from index), still Prop.
62+
-/
63+
inductive D : Nat → Sort _ where
64+
| a (h : n = n) : D n
65+
/-- info: D : Nat → Prop -/
66+
#guard_msgs in #check D
67+
68+
/-!
69+
Structure with no fields, Type.
70+
-/
71+
structure S1 where
72+
/-- info: S1 : Type -/
73+
#guard_msgs in #check S1
74+
75+
/-!
76+
Structure with a Prop field, Prop.
77+
-/
78+
structure S2 where
79+
h : True
80+
/-- info: S2 : Prop -/
81+
#guard_msgs in #check S2
82+
83+
/-!
84+
Structure with parameter and a Prop field, Prop.
85+
-/
86+
structure S3 (α : Type) where
87+
h : ∀ a : α, a = a
88+
/-- info: S3 (α : Type) : Prop -/
89+
#guard_msgs in #check S3
90+
91+
/-!
92+
Verify: `Decidable` is a `Type`.
93+
-/
94+
class inductive Decidable' (p : Prop) where
95+
| isFalse (h : Not p) : Decidable' p
96+
| isTrue (h : p) : Decidable' p
97+
/-- info: Decidable' (p : Prop) : Type -/
98+
#guard_msgs in #check Decidable'
99+
100+
/-!
101+
Verify: `WellFounded` is a `Prop`.
102+
-/
103+
inductive WellFounded' {α : Sort u} (r : α → α → Prop) where
104+
| intro (h : ∀ a, Acc r a) : WellFounded' r
105+
/-- info: WellFounded'.{u} {α : Sort u} (r : α → α → Prop) : Prop -/
106+
#guard_msgs in #check WellFounded'

0 commit comments

Comments
 (0)