Skip to content

Commit d5fec8f

Browse files
authored
Ignore selection prototypes when typing type applications (#24489)
Ignore selection prototypes at first when typing type applications. If we need them later for overloading disambiguation, reveal the ignored type. The reason for doing this is that a selection might come from an extension method, and in this case we should not require the selected name as a member of the result. This change breaks one test (overloading-specifity-2.scala) that explicitly tested that we don't consult implicit arguments for disambiguation since the expected type was a selection that already determined the outcome. This logic no longer holds. We have to see whether this change breaks any code in practice. Fixes #23773
2 parents 608e8d8 + 0436529 commit d5fec8f

File tree

7 files changed

+72
-9
lines changed

7 files changed

+72
-9
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,11 @@ object Types extends TypeUtils {
19561956
/** If this is a proto type, WildcardType, otherwise the type itself */
19571957
def dropIfProto: Type = this
19581958

1959+
/** If this is a (possibly applied) selection proto type, ignore the
1960+
* selection part
1961+
*/
1962+
def ignoreSelectionProto(using Context): Type = this
1963+
19591964
/** If this is an AndType, the number of factors, 1 for all other types */
19601965
def andFactorCount: Int = 1
19611966

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
354354
~ "]"
355355
case IgnoredProto(ignored) =>
356356
"?" ~ ("(ignored: " ~ toText(ignored) ~ ")").provided(printDebug)
357-
case tp @ PolyProto(targs, resType) =>
358-
"[applied to [" ~ toTextGlobal(targs, ", ") ~ "] returning " ~ toText(resType)
357+
case tp @ PolyProto(targs, resultType) =>
358+
"[applied to ["
359+
~ toTextGlobal(targs, ", ")
360+
~ "] returning "
361+
~ toText(resultType)
362+
~ "]"
359363
case _ =>
360364
super.toText(tp)
361365
}

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,7 @@ trait Applications extends Compatibility {
14221422
val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_))
14231423
record("typedTypeApply")
14241424

1425-
typedExpr(tree.fun, PolyProto(typedArgs, pt)) match {
1425+
typedExpr(tree.fun, PolyProto(typedArgs, pt.ignoreSelectionProto)) match
14261426
case fun: TypeApply if !ctx.isAfterTyper =>
14271427
val function = fun.fun
14281428
val args = (fun.args ++ tree.args).map(_.show).mkString(", ")
@@ -1446,7 +1446,6 @@ trait Applications extends Compatibility {
14461446
}
14471447
if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply()
14481448
else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs)
1449-
}
14501449
}
14511450

14521451
/** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray.

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,15 @@ object ProtoTypes {
162162
override def viewExists(tp: Type, pt: Type)(using Context): Boolean = false
163163
}
164164

165-
/** A trait for prototypes that match all types */
166-
trait MatchAlways extends ProtoType {
167-
def isMatchedBy(tp1: Type, keepConstraint: Boolean)(using Context): Boolean = true
165+
/** A trait for prototypes that map to themselves */
166+
trait FixedProto extends ProtoType:
168167
def map(tm: TypeMap)(using Context): ProtoType = this
169168
def fold[T](x: T, ta: TypeAccumulator[T])(using Context): T = x
170169
override def toString: String = getClass.toString
171-
}
170+
171+
/** A trait for prototypes that match all types */
172+
trait MatchAlways extends FixedProto:
173+
def isMatchedBy(tp1: Type, keepConstraint: Boolean)(using Context): Boolean = true
172174

173175
/** A class marking ignored prototypes that can be revealed by `deepenProto` */
174176
abstract case class IgnoredProto(ignored: Type) extends CachedGroundType with MatchAlways:
@@ -179,6 +181,21 @@ object ProtoTypes {
179181
ignored
180182
override def deepenProtoTrans(using Context): Type = ignored.deepenProtoTrans
181183

184+
override def isMatchedBy(tp1: Type, keepConstraint: Boolean)(using Context): Boolean =
185+
def takesParams(tp: Type): Boolean = tp match
186+
case tp: PolyType => takesParams(tp.resType)
187+
case MethodType(pnames) => pnames.nonEmpty && !tp.isImplicitMethod
188+
case _ => false
189+
ignored match
190+
case ignored: SelectionProto if ignored.name != nme.apply =>
191+
// Non-implicit methods that take at least one parameter don't match ignored
192+
// selection protos unless the selection is via `apply`. This is because a
193+
// match of a different selection would require an eta expansion _and_ an
194+
// implicit conversion, which is not allowed. So the prototype would not
195+
// match even if implicit conversions were present. Test case: i23773a.scala.
196+
!takesParams(tp1.widen)
197+
case _ => true
198+
182199
/** Did someone look inside via deepenProto? Used for error deagniostics
183200
* to give a more extensive expected type.
184201
*/
@@ -286,6 +303,9 @@ object ProtoTypes {
286303
override def deepenProtoTrans(using Context): SelectionProto =
287304
derivedSelectionProto(name, memberProto.deepenProtoTrans, compat, nameSpan)
288305

306+
override def ignoreSelectionProto(using Context): IgnoredProto =
307+
IgnoredProto(this)
308+
289309
override def computeHash(bs: Hashable.Binders): Int = {
290310
val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0)
291311
addDelta(doHash(bs, name, memberProto), delta)
@@ -620,6 +640,9 @@ object ProtoTypes {
620640
override def deepenProtoTrans(using Context): FunProto =
621641
derivedFunProto(args, resultType.deepenProtoTrans, constrainResultDeep = true)
622642

643+
override def ignoreSelectionProto(using Context): FunProto =
644+
derivedFunProto(args, resultType.ignoreSelectionProto)
645+
623646
override def withContext(newCtx: Context): ProtoType =
624647
if newCtx `eq` protoCtx then this
625648
else new FunProto(args, resType)(typer, applyKind, state)(using newCtx)
@@ -734,6 +757,9 @@ object ProtoTypes {
734757

735758
override def deepenProtoTrans(using Context): PolyProto =
736759
derivedPolyProto(targs, resultType.deepenProtoTrans)
760+
761+
override def ignoreSelectionProto(using Context): PolyProto =
762+
derivedPolyProto(targs, resultType.ignoreSelectionProto)
737763
}
738764

739765
/** A prototype for expressions [] that are known to be functions:

tests/run/overloading-specifity-2.scala renamed to tests/neg/overloading-specifity-2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ object Test extends App {
2323
def foo[T]: Show[T] = new Show[T](2)
2424
}
2525

26-
assert(a.foo[Int].i == 1) // error: no implicit argument of type Test.Context was found for parameter ctx
26+
assert(a.foo[Int].i == 1) // error: no implicit argument of type Test.Context was found for parameter ctx, was OK
2727
}

tests/pos/i23773.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
trait Foo[T]
2+
3+
def foo[A]: Int = ???
4+
def foo[A: Foo]: Int = ???
5+
6+
extension (x: Int)
7+
def succ: Int = x + 1
8+
9+
val a = foo[Int]
10+
val b = foo[Int].succ // error

tests/pos/i23773a.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait NumericDate
2+
trait JWSDecoded[H]
3+
4+
trait StandardHeaderWrite[H]:
5+
def setAlgorithm(header: H, algorithm: Algorithm): H
6+
7+
object StandardHeaderWrite:
8+
def apply[H](using sh: StandardHeaderWrite[H]): StandardHeaderWrite[H] = ???
9+
// unused - required to reproduce
10+
def apply[H](setAlg: (H, Algorithm) => H): StandardHeaderWrite[H] = ???
11+
12+
final case class JWK(algorithm: Option[Algorithm])
13+
sealed trait Algorithm
14+
15+
def Test[F[_], H](key: JWK, header: H)(using StandardHeaderWrite[H]) = {
16+
key.algorithm
17+
.map(StandardHeaderWrite[H].setAlgorithm(header, _))
18+
.getOrElse(header)
19+
}

0 commit comments

Comments
 (0)