Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ object TyperState {

opaque type Snapshot = (Constraint, TypeVars, LevelMap)

class BadTyperStateAssertion(msg: String) extends AssertionError(msg)

extension (ts: TyperState)
def snapshot()(using Context): Snapshot =
(ts.constraint, ts.ownedVars, ts.upLevels)
Expand All @@ -44,7 +42,7 @@ object TyperState {
}

class TyperState() {
import TyperState.{LevelMap, BadTyperStateAssertion}
import TyperState.LevelMap

private var myId: Int = _
def id: Int = myId
Expand Down Expand Up @@ -270,10 +268,8 @@ class TyperState() {
*/
private def includeVar(tvar: TypeVar)(using Context): Unit =
val oldState = tvar.owningState.nn.get

if oldState != null && oldState.isCommittable then
throw BadTyperStateAssertion(
i"$this attempted to take ownership of $tvar which is already owned by committable $oldState")
assert(oldState == null || !oldState.isCommittable,
i"$this attempted to take ownership of $tvar which is already owned by committable $oldState")
tvar.owningState = new WeakReference(this)
ownedVars += tvar

Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,7 @@ object Inferencing {
}

/** The instantiation decision for given poly param computed from the constraint. */
enum Decision:
case Min, Max, ToMax, Skip, Fail

enum Decision { case Min; case Max; case ToMax; case Skip; case Fail }
private def instDecision(tvar: TypeVar, v: Int, minimizeSelected: Boolean, ifBottom: IfBottom)(using Context): Decision =
import Decision.*
val direction = instDirection(tvar.origin)
Expand Down
42 changes: 8 additions & 34 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,17 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
private type SpecialHandlers = List[(ClassSymbol, SpecialHandler)]

val synthesizedClassTag: SpecialHandler = (formal, span) =>
def instArg(tp: Type): Type = tp.dealias match
def instArg(tp: Type): Type = tp.stripTypeVar match
// Special case to avoid instantiating `Int & S` to `Int & Nothing` in
// i16328.scala. The intersection comes from an earlier instantiation
// to an upper bound.
// The dual situation with unions is harder to trigger because lower
// bounds are usually widened during instantiation.
case tp: AndOrType if tp.tp1 =:= tp.tp2 =>
// Special case to avoid instantiating `Int & S` to `Int & Nothing` in
// i16328.scala. The intersection comes from an earlier instantiation
// to an upper bound.
// The dual situation with unions is harder to trigger because lower
// bounds are usually widened during instantiation.
instArg(tp.tp1)
case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) =>
// If tvar has a lower or upper bound:
// 1. If the bound is not another type variable, use this as approximation.
// 2. Otherwise, if the type can be forced to be fully defined, use that type
// as approximation.
// 3. Otherwise leave argument uninstantiated.
// The reason for (2) is that we observed complicated constraints in i23611.scala
// that get better types if a fully defined type is computed than if several type
// variables are approximated incrementally. This is a minimization of some ZIO code.
// So in order to keep backwards compatibility (where before we _only_ did 2) we
// add that special case.
def isGroundConstr(tp: Type): Boolean = tp.dealias match
case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => false
case pref: TypeParamRef if ctx.typerState.constraint.contains(pref) => false
case tp: AndOrType => isGroundConstr(tp.tp1) && isGroundConstr(tp.tp2)
case _ => true
instArg(
if tvar.hasLowerBound then
if isGroundConstr(fullLowerBound(tvar.origin)) then tvar.instantiate(fromBelow = true)
else if isFullyDefined(tp, ForceDegree.all) then tp
else NoType
else if tvar.hasUpperBound then
if isGroundConstr(fullUpperBound(tvar.origin)) then tvar.instantiate(fromBelow = false)
else if isFullyDefined(tp, ForceDegree.all) then tp
else NoType
else
NoType)
case _ =>
tp
if isFullyDefined(tp, ForceDegree.all) then tp
else NoType // this happens in tests/neg/i15372.scala

val tag = formal.argInfos match
case arg :: Nil =>
Expand Down
61 changes: 18 additions & 43 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3869,12 +3869,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer

def addImplicitArgs(using Context) =
def hasDefaultParams = methPart(tree).symbol.hasDefaultParams
def findDefaultArgument(argIndex: Int): Tree =
def appPart(t: Tree): Tree = t match
case Block(_, expr) => appPart(expr)
case Inlined(_, _, expr) => appPart(expr)
case t => t
defaultArgument(appPart(tree), n = argIndex, testOnly = false)
def implicitArgs(formals: List[Type], argIndex: Int, pt: Type): List[Tree] = formals match
case Nil => Nil
case formal :: formals1 =>
Expand All @@ -3889,40 +3883,27 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
implicitArgs(formals2, argIndex + 1, pt)

val arg = inferImplicitArg(formal, tree.span.endPos)

lazy val defaultArg = findDefaultArgument(argIndex)
.showing(i"default argument: for $formal, $tree, $argIndex = $result", typr)
def argHasDefault = hasDefaultParams && !defaultArg.isEmpty

def canProfitFromMoreConstraints =
arg.tpe.isInstanceOf[AmbiguousImplicits]
// Ambiguity could be decided by more constraints
|| !isFullyDefined(formal, ForceDegree.none) && !argHasDefault
// More context might constrain type variables which could make implicit scope larger.
// But in this case we should search with additional arguments typed only if there
// is no default argument.

// Try to constrain the result using `pt1`, but back out if a BadTyperStateAssertion
// is thrown. TODO Find out why the bad typer state arises and prevent it. The try-catch
// is a temporary hack to keep projects compiling that would fail otherwise due to
// searching more arguments to instantiate implicits (PR #23532). A failing project
// is described in issue #23609.
def tryConstrainResult(pt: Type): Boolean =
try constrainResult(tree.symbol, wtp, pt)
catch case ex: TyperState.BadTyperStateAssertion => false

arg.tpe match
case failed: SearchFailureType if canProfitFromMoreConstraints =>
val pt1 = pt.deepenProtoTrans
if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && tryConstrainResult(pt1) then
return implicitArgs(formals, argIndex, pt1)
case _ =>

arg.tpe match
case failed: AmbiguousImplicits =>
arg :: implicitArgs(formals1, argIndex + 1, pt)
val pt1 = pt.deepenProtoTrans
if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1)
then implicitArgs(formals, argIndex, pt1)
else arg :: implicitArgs(formals1, argIndex + 1, pt1)
case failed: SearchFailureType =>
if argHasDefault then
lazy val defaultArg =
def appPart(t: Tree): Tree = t match
case Block(stats, expr) => appPart(expr)
case Inlined(_, _, expr) => appPart(expr)
case _ => t
defaultArgument(appPart(tree), argIndex, testOnly = false)
.showing(i"default argument: for $formal, $tree, $argIndex = $result", typr)
if !hasDefaultParams || defaultArg.isEmpty then
// no need to search further, the adapt fails in any case
// the reason why we continue inferring arguments in case of an AmbiguousImplicits
// is that we need to know whether there are further errors.
// If there are none, we have to propagate the ambiguity to the caller.
arg :: formals1.map(dummyArg)
else
// This is tricky. On the one hand, we need the defaultArg to
// correctly type subsequent formal parameters in the same using
// clause in case there are parameter dependencies. On the other hand,
Expand All @@ -3933,12 +3914,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
// `if propFail.exists` where we re-type the whole using clause with named
// arguments for all implicits that were found.
arg :: inferArgsAfter(defaultArg)
else
// no need to search further, the adapt fails in any case
// the reason why we continue inferring arguments in case of an AmbiguousImplicits
// is that we need to know whether there are further errors.
// If there are none, we have to propagate the ambiguity to the caller.
arg :: formals1.map(dummyArg)
case _ =>
arg :: inferArgsAfter(arg)
end implicitArgs
Expand Down
7 changes: 5 additions & 2 deletions tests/neg/i9568.check
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
| No given instance of type => Monad[F] was found for parameter ev of method blaMonad in object Test.
| I found:
|
| Test.blaMonad[F², S]
| Test.blaMonad[F², S](Test.blaMonad[F³, S²])
|
| But method blaMonad in object Test does not match type => Monad[F]
| But method blaMonad in object Test does not match type => Monad[F²]
|
| where: F is a type variable with constraint <: [_] =>> Any
| F² is a type variable with constraint <: [_] =>> Any
| F³ is a type variable with constraint <: [_] =>> Any
| S is a type variable
| S² is a type variable
| .
14 changes: 0 additions & 14 deletions tests/pos/i23526.scala

This file was deleted.

25 changes: 25 additions & 0 deletions tests/pos/i24192.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// https://github.com/scala/scala-collection-contrib/blob/main/src/main/scala/scala/collection/decorators/package.scala
object decorators {
trait IsMap[A]
implicit def mapDecorator[C](coll: C)(implicit map: IsMap[C]): Map[C, map.type] = ???
}
import decorators.mapDecorator // unused, required to reprouce

trait Eq[T]
trait Applicative[F[_]]
given Applicative[Option] = ???

trait Traverse[F[_]]:
// context bound required to reproduce
def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = ???

object Traverse:
def apply[F[_]]: Traverse[F] = ???

trait Segment[Element: Eq]

case class MergeResult[Element: Eq] private (segments: Seq[Segment[Element]]):
def thisFailsToCompile(): Option[MergeResult[Element]] =
Traverse[Seq]
.sequence(Seq.empty[Option[Segment[Element]]])
.map(MergeResult.apply) // error
Loading