diff --git a/tests/elabissues/typeclasses_with_emetavariables.lean b/tests/elabissues/typeclasses_with_emetavariables.lean new file mode 100644 index 0000000000..1a12542bef --- /dev/null +++ b/tests/elabissues/typeclasses_with_emetavariables.lean @@ -0,0 +1,40 @@ +/- +The current plan is to allow typeclass resolution to be triggered when there are still +expression metavariables in the goal. + +It will return: + +success: it succeeded without needing to unify a regular metavar with anything +fail: it failed without even getting a chance to unify a regular metavar with anything +wait: it would have needed to unify a regular metavar with something + +If it returns `wait`, the elaborator may try again later after learning more about the goal. + +Note: this only applies to expression metavariables. +See typeclasses_with_umetavariables.lean for a discussion of universe metavariables. +-/ + +class Foo (b : Bool) +class Bar (b : Bool) +class Rig (b : Bool) + +@[instance] axiom FooTrue : Foo true +@[instance] axiom FooToBar (b : Bool) : HasCoe (Foo b) (Bar b) + +/- [success] In the following example, `Foo.mk _ : Foo ?m₁` would coerce to `Bar ?m₁`, +since resolution would succeed without ever needing to unify `?m₁`. -/ +noncomputable def tcSuccess := +[Bar.mk _, Foo.mk _, Bar.mk true] + +/- [failure] In this example, since there is no coercion from `Foo` to `Rig`, +resolution would fail immediately without bothering to wait.-/ +noncomputable def tcFail := +[Rig.mk _, Foo.mk _] + + +@[instance] axiom FooToRig : HasCoe (Foo true) (Rig true) + +/- [wait] In this example, the coercion from `Foo.mk _ : Foo ?m₁` to `Rig ?m₁` +would wait, but then be queried again later and succeed.-/ +noncomputable def tcWait := +[Rig.mk _, Foo.mk _, Rig.mk true] diff --git a/tests/elabissues/typeclasses_with_preconditions.lean b/tests/elabissues/typeclasses_with_preconditions.lean new file mode 100644 index 0000000000..9a68937b48 --- /dev/null +++ b/tests/elabissues/typeclasses_with_preconditions.lean @@ -0,0 +1,60 @@ +/- +The current plan is to allow instances to have preconditions with registered tactics. +Typeclass resolution will delay these proof obligations, effectively assuming that they will succeed. +If typeclass resolution succeeds, it will return a list of (mvar, lctx) pairs to the elaborator, +which will try to synthesize the proofs and throw a good error message if it fails. +-/ + +class Foo (b : Bool) : Type + +class FooTrue (b : Bool) extends Foo b : Type := +(H : b = true) + +/- This requires the tactic framework and auto_param. -/ +-- @[instance] axiom CoeFooFooTrue (b : Bool) (H : b = true . refl) : HasCoe (Foo b) (FooTrue b) + +instance BoolToFoo (b : Bool) : Foo b := Foo.mk b + +def forceFooTrue (b : Bool) (fooTrue : FooTrue b) : Bool := b + +/- Should succeed (once CoeFooFooTrue can be written) -/ +#check forceFooTrue true (Foo.mk true) + +/- Should fail (even after CoeFooFooTrue can be written -/ +#check forceFooTrue false (Foo.mk false) + +/- +This plan has one limitation, that has so far been deemed acceptable: +it will not support classes with different instances depending on the provability of preconditions. +The classic example is multiplying two elements of `ℤ/nℤ` where `n` is not prime. +Here is a toy version of this problem: + +<< +@[class] axiom Prime (p : Nat) : Prop + +@[instance] axiom p2 : Prime 2 +@[instance] axiom p3 : Prime 3 + +@[class] axiom Field : Type → Type +@[class] axiom Ring : Type → Type + +@[instance] axiom FieldToHasDiv (α : Type) [Field α] : HasDiv α +@[instance] axiom FieldToHasMul (α : Type) [Field α] : HasMul α +@[instance] axiom RingToHasMul (α : Type) [Ring α] : HasMul α + +axiom mkType : Nat → Type + +@[instance] axiom PrimeField (n : Nat) (Hp : Prime n . provePrimality) : Field (mkType n) +@[instance] axiom NonPrimeRing (n : Nat) : Ring (mkType n) + +example (α β : mkType 4) : α * β = β * α +>> + +The issue is that (depending on the order the instances are tried), +the instance involving `FieldToHasMul` will succeed in typeclass resolution, +but the proof will fail later on. + +I (@dselsam) still thinks this plan is a good compromise. +For examples like this, the definition in question (i.e. `Prime`) can be made a class instead +and taken as an inst-implicit argument to `PrimeField`. +-/ diff --git a/tests/elabissues/typeclasses_with_umetavariables.lean b/tests/elabissues/typeclasses_with_umetavariables.lean new file mode 100644 index 0000000000..e61d4d44cb --- /dev/null +++ b/tests/elabissues/typeclasses_with_umetavariables.lean @@ -0,0 +1,41 @@ +/- +In constrast to expression metavariables (see: typeclasses_with_emetavariables.lean), +typeclass resolution is not stalled due to the presence of universe metavariables. + +In the current C++ implementation, some (but not all) universe metavariables appearing +in typeclass goals are converted to temporary metavariables. +-/ + +class Foo.{u, v} (α : Type.{u}) : Type.{v} +class Bar.{u} (α : Type) : Type.{u} + +@[instance] axiom foo5 : Foo.{0, 5} Unit + +def synthFoo {X : Type} [inst : Foo X] : Foo X := inst + +set_option trace.class_instances true +set_option pp.all true + +/- +Currently, universe variables in the level params of the *outer-most head symbol* +of the class are converted to temporary metavariables.-/ +noncomputable def f1 : Foo Unit := synthFoo + +def synthFooBar.{u, v} {X : Type*} [inst : Foo.{u, v} (Bar.{u} X)] : Foo.{u, v} (Bar.{u} X) := inst +@[instance] axiom fooBar5.{u} : Foo.{5, u} (Bar.{5} Unit) + +/- +This *nested* universe parameter is not converted into a temporary metavariable, and so this fails.-/ +noncomputable def f2 : Foo (Bar Unit) := synthFooBar + +/- +Here is the trace: +<< +[class_instances] (0) ?x_0 : Foo.{?u_0 ?u_1} (Bar.{?l__fresh.4.77} Unit) := fooBar5.{?u_2} +failed is_def_eq +[class_instances] (0) ?x_0 : Foo.{?u_0 ?u_1} (Bar.{?l__fresh.4.77} Unit) := foo5 +failed is_def_eq +>> + +Note that the universe metavariable in question is converted to a temporary metavariable in only one of the two occurrences. +-/