From a714c5576e5f7be6a8087692a84fbf8a0d4733a9 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 9 Jun 2021 16:43:03 +0200 Subject: [PATCH] Handle FunProto with committed TyperState The TyperState of `FunProto#protoCtx` might already be committed by the time we call `FunProto#typedArgs` (this can happen for example with the nested TyperState in a `tryEither`), in that case we can trigger assertion errors if we touch that TyperState again. Fixed by using the nearest uncommited ancestor of the capture TyperState instead. --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 4 ++++ compiler/src/dotty/tools/dotc/core/TyperState.scala | 4 +++- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 4 ++-- tests/neg/i12736a.scala | 6 ++++++ 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i12736a.scala diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 84ba0a452a2e..6ef1856f5cfa 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -526,6 +526,10 @@ object Contexts { final def withOwner(owner: Symbol): Context = if (owner ne this.owner) fresh.setOwner(owner) else this + final def withUncommittedTyperState: Context = + val ts = typerState.uncommittedAncestor + if ts ne typerState then fresh.setTyperState(ts) else this + final def withProperty[T](key: Key[T], value: Option[T]): Context = if (property(key) == value) this else value match { diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 6fd7f48bc54f..0f5ea0ac431d 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -138,9 +138,11 @@ class TyperState() { */ def commit()(using Context): Unit = { Stats.record("typerState.commit") - assert(isCommittable) + assert(isCommittable, s"$this is not committable") + assert(!isCommitted, s"$this is already committed") setCommittable(false) val targetState = ctx.typerState + assert(!targetState.isCommitted, 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/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index eff4a8cb9c94..8a4de6ec13e2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -388,8 +388,8 @@ object ProtoTypes { if state.typedArgs.size == args.length then state.typedArgs else val passedTyperState = ctx.typerState - inContext(protoCtx) { - val protoTyperState = protoCtx.typerState + inContext(protoCtx.withUncommittedTyperState) { + val protoTyperState = ctx.typerState val oldConstraint = protoTyperState.constraint val args1 = args.mapWithIndexConserve((arg, idx) => cacheTypedArg(arg, arg => typer.typed(norm(arg, idx)), force = false)) diff --git a/tests/neg/i12736a.scala b/tests/neg/i12736a.scala new file mode 100644 index 000000000000..509465ea3f07 --- /dev/null +++ b/tests/neg/i12736a.scala @@ -0,0 +1,6 @@ +object Test { + def apply[S](r: Any): Any = r + + def test = + (x: Int) => Test(doesntexist, x) // error +}