From 13801045470f25d7d24acec088ad6cb0cae9d565 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 21 Jul 2021 13:53:07 +0200 Subject: [PATCH] Avoid crash by relaxing TyperState assertion Flushing a reporter might force error messages (in particular when a StoreReporter is flushed into a non-StoreReporter), and the TyperState of the context captured in an error message might already be committed at this point. In ea6449fbd4b62ee6033bb2d156ff3eb685dbb9d1 I tried to deal with this by flushing before committing but that's not sufficient since the reporter we're flushing might contain error messages from a more deeply nested TyperState. So this commit just relaxes the assertion (ideally we would also check that only TyperStates created in a committed TyperState can be committed in one, but keeping track of that would require an extra field in TyperState). --- .../dotty/tools/dotc/core/TyperState.scala | 10 +++++++- tests/neg/i13101.scala | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i13101.scala diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 2c41fc8f0231..b8eff35275a7 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -143,7 +143,15 @@ class TyperState() { reporter.flush() setCommittable(false) val targetState = ctx.typerState - assert(!targetState.isCommitted, s"Attempt to commit $this into already committed $targetState") + + // Committing into an already committed TyperState usually doesn't make + // sense since it means the constraints we're committing won't be propagated + // further, but it can happen if the targetState gets captured in a reported + // Message, because forcing that Message might involve creating and + // committing new TyperStates into the captured one after its been committed. + assert(!targetState.isCommitted || targetState.reporter.hasErrors || targetState.reporter.hasWarnings, + s"Attempt to commit $this into already committed $targetState") + if constraint ne targetState.constraint then Stats.record("typerState.commit.new constraint") constr.println(i"committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}") diff --git a/tests/neg/i13101.scala b/tests/neg/i13101.scala new file mode 100644 index 000000000000..f32ba35ef2c8 --- /dev/null +++ b/tests/neg/i13101.scala @@ -0,0 +1,24 @@ +trait Vehicle +trait Car extends Vehicle + +trait Encoder[A] +object Encoder { + implicit val encodeVehicle: Encoder[Vehicle] = ??? + implicit val encodeCar: Encoder[Car] = ??? +} + +trait Route +trait Printer +trait Marshaller[-A] // must be contravariant + +object Test { + implicit def marshaller[A: Encoder](implicit p: Printer = ???): Marshaller[A] = ??? + // the `Printer` implicit arg seems to be necessary, either with default value, or no implicit in scope + + def foo[A](v: A)(implicit m: Marshaller[A]): Route = ??? + + val route: Route = identity { + val f: (Car => Route) => Route = ??? // ok if s/Car/Vehicle/ + f(vehicle => foo(vehicle)) // error: ambiguous implicit + } +}