Skip to content
Draft
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
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ object Types extends TypeUtils {

def isAny(using Context): Boolean = isRef(defn.AnyClass, skipRefined = false)
def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false)
def isAnyVal(using Context): Boolean = isRef(defn.AnyValClass, skipRefined = false)
def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false)

def isTopType(using Context): Boolean = dealias match
Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1668,15 +1668,14 @@ trait Implicits:
def isUnderSpecifiedArgument(tp: Type): Boolean =
tp.isRef(defn.NothingClass) || tp.isRef(defn.NullClass) || (tp eq NoPrefix)

private def isUnderspecified(tp: Type): Boolean = tp.stripTypeVar match
private def isUnderspecified(tp: Type): Boolean = tp.simplified match
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm willing to bet that we also need a dealiasing here.

case tp: WildcardType =>
!tp.optBounds.exists || isUnderspecified(tp.optBounds.hiBound)
case tp: ViewProto =>
isUnderspecified(tp.resType)
|| tp.resType.isRef(defn.UnitClass)
|| isUnderSpecifiedArgument(tp.argType.widen)
case _ =>
tp.isAny || tp.isAnyRef
case tp => tp.isAny || tp.isAnyRef || tp.isAnyVal
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a specification point of view, it would have been nice to check tp.isTopLevel but that means we need to also include scala.Matchable in the list of underspecified types.


/** Search implicit in context `ctxImplicits` or else in implicit scope
* of expected type if `ctxImplicits == null`.
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4952,10 +4952,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType =>
if tree.tpe.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then
readapt(typed(untpd.Select(untpd.TypedSplice(tree), nme.toTuple)))
else if pt.isRef(defn.AnyValClass, skipRefined = false)
|| pt.isRef(defn.ObjectClass, skipRefined = false)
then
recover(TooUnspecific(pt))
else inferView(tree, pt) match
case SearchSuccess(found, _, _, isExtension) =>
if isExtension then found
Expand Down
23 changes: 23 additions & 0 deletions tests/neg/i21304.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- [E007] Type Mismatch Error: tests/neg/i21304.scala:5:6 --------------------------------------------------------------
5 | foo(10) // error
| ^^
| Found: (10 : Int)
| Required: AnyRef | Null
| Note that implicit conversions were not tried because the result of an implicit conversion
| must be more specific than AnyRef | Null
|
| One of the following imports might fix the problem:
|
| import scala.math.BigDecimal.int2bigDecimal
| import scala.math.BigInt.int2bigInt
| import scala.math.Numeric.IntIsIntegral.mkNumericOps
| import scala.math.Numeric.IntIsIntegral.mkOrderingOps
| import scala.math.Ordering.Int.mkOrderingOps
| import scala.math.Integral.Implicits.infixIntegralOps
| import scala.math.Ordered.orderingToOrdered
| import scala.math.Ordering.Implicits.infixOrderingOps
| import scala.math.Numeric.Implicits.infixNumericOps
| import scala.reflect.Selectable.reflectiveSelectable
Comment on lines +9 to +20
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of them will ever help. An improvement would be to get rid of this list when we are underspecified.

|
|
| longer explanation available when compiling with `-explain`
5 changes: 5 additions & 0 deletions tests/neg/i21304.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def foo(x: AnyRef | Null): Unit =
println(x)

@main def main(): Unit =
foo(10) // error
5 changes: 5 additions & 0 deletions tests/neg/implicits-numeric.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- [E172] Type Error: tests/neg/implicits-numeric.scala:6:28 -----------------------------------------------------------
6 | println(implicitly[AnyVal]) // error
| ^
| No implicit search was attempted for parameter e of method implicitly in object Predef
| since the expected type AnyVal is not specific enough
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ object Test extends App {
implicit def _1: Long = 1L
implicit def _2: Int = 0

println(implicitly[AnyVal])
println(implicitly[AnyVal]) // error
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was completely bogus before, not only it should have been underspecified but also, if we say AnyVal are not underspecified, it should have been an ambiguous resolution (what actually Scala 2.13.16 says).

}
6 changes: 3 additions & 3 deletions tests/pos/hylolib/AnyValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class AnyValue private (
_copy(this.wrapped)

/** Returns `true` iff `this` and `other` have an equivalent value. */
def eq(other: AnyValue): Boolean =
infix def eq(other: AnyValue): Boolean =
_eq(this.wrapped, other.wrapped)

/** Hashes the salient parts of `this` into `hasher`. */
Expand All @@ -49,7 +49,7 @@ object AnyValue {
AnyValue(a.asInstanceOf[Ref[T]].value.copy())

def eq(a: AnyRef, b: AnyRef): Boolean =
a.asInstanceOf[Ref[T]].value `eq` b.asInstanceOf[Ref[T]].value
a.asInstanceOf[Ref[T]].value eq b.asInstanceOf[Ref[T]].value

def hashInto(a: AnyRef, hasher: Hasher): Hasher =
a.asInstanceOf[Ref[T]].value.hashInto(hasher)
Expand All @@ -62,6 +62,6 @@ given AnyValue is Value:

extension (self: AnyValue)
def copy(): AnyValue = self.copy()
def eq(other: AnyValue): Boolean = self `eq` other
infix def eq(other: AnyValue): Boolean = self eq other
def hashInto(hasher: Hasher): Hasher = self.hashInto(hasher)

4 changes: 2 additions & 2 deletions tests/pos/hylolib/AnyValueTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ class AnyValueTests extends munit.FunSuite:

test("eq"):
val a = AnyValue(1)
assert(a `eq` a)
assert(a eq a)
assert(!(a `neq` a))

val b = AnyValue(2)
assert(!(a `eq` b))
assert(!(a eq b))
assert(a `neq` b)

6 changes: 3 additions & 3 deletions tests/pos/hylolib/BitArray.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ object BitArray {
new Position(bucket, offsetInBucket)

/** Returns `true` iff `this` and `other` have an equivalent value. */
def eq(other: Position): Boolean =
infix def eq(other: Position): Boolean =
(this.bucket == other.bucket) && (this.offsetInBucket == other.offsetInBucket)

/** Hashes the salient parts of `self` into `hasher`. */
Expand All @@ -325,8 +325,8 @@ given BitArray.Position is Value:
def copy(): BitArray.Position =
self.copy()

def eq(other: BitArray.Position): Boolean =
self.eq(other)
infix def eq(other: BitArray.Position): Boolean =
self eq other

def hashInto(hasher: Hasher): Hasher =
self.hashInto(hasher)
Expand Down
30 changes: 15 additions & 15 deletions tests/pos/hylolib/Collection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ trait Collection:

/** Returns `true` iff `self` is empty. */
def isEmpty: Boolean =
startPosition `eq` endPosition
startPosition eq endPosition

/** Returns the number of elements in `self`.
*
Expand All @@ -25,7 +25,7 @@ trait Collection:
def count: Int =
val e = endPosition
def loop(p: Position, n: Int): Int =
if p `eq` e then n else loop(self.positionAfter(p), n + 1)
if p eq e then n else loop(self.positionAfter(p), n + 1)
loop(startPosition, 0)

/** Returns the position of `self`'s first element', or `endPosition` if `self` is empty.
Expand Down Expand Up @@ -70,12 +70,12 @@ trait Collection:
*/
def isBefore(i: Position, j: Position): Boolean =
val e = self.endPosition
if i `eq` e then false
else if j `eq` e then true
if i eq e then false
else if j eq e then true
else
def recur(n: Position): Boolean =
if n `eq` j then true
else if n `eq` e then false
if n eq j then true
else if n eq e then false
else recur(self.positionAfter(n))
recur(self.positionAfter(i))

Expand Down Expand Up @@ -110,7 +110,7 @@ extension [Self: Collection](self: Self)
else
val p = self.startPosition
val q = self.positionAfter(p)
val t = Slice(self, Range(q, self.endPosition, (a, b) => (a `eq` b) || self.isBefore(a, b)))
val t = Slice(self, Range(q, self.endPosition, (a, b) => (a eq b) || self.isBefore(a, b)))
Some((self.at(p), t))

def headAndTail2: Option[(Self.Element, Self.Slice2)] =
Expand All @@ -119,7 +119,7 @@ extension [Self: Collection](self: Self)
else
val p = self.startPosition
val q = self.positionAfter(p)
val t = Self.Slice2(self, Range(q, self.endPosition, (a, b) => (a `eq` b) || self.isBefore(a, b)))
val t = Self.Slice2(self, Range(q, self.endPosition, (a, b) => (a eq b) || self.isBefore(a, b)))
Some((self.at(p), t))

/** Applies `combine` on `partialResult` and each element of `self`, in order.
Expand All @@ -130,7 +130,7 @@ extension [Self: Collection](self: Self)
def reduce[T](partialResult: T)(combine: (T, Self.Element) => T): T =
val e = self.endPosition
def loop(p: Self.Position, r: T): T =
if p `eq` e then r
if p eq e then r
else loop(self.positionAfter(p), combine(r, self.at(p)))
loop(self.startPosition, partialResult)

Expand All @@ -146,7 +146,7 @@ extension [Self: Collection](self: Self)
def forEach(action: Self.Element => Boolean): Boolean =
val e = self.endPosition
def loop(p: Self.Position): Boolean =
if p `eq` e then true
if p eq e then true
else if !action(self.at(p)) then false
else loop(self.positionAfter(p))
loop(self.startPosition)
Expand Down Expand Up @@ -194,7 +194,7 @@ extension [Self: Collection](self: Self)
def firstPositionWhere(predicate: Self.Element => Boolean): Option[Self.Position] =
val e = self.endPosition
def loop(p: Self.Position): Option[Self.Position] =
if p `eq` e then None
if p eq e then None
else if predicate(self.at(p)) then Some(p)
else loop(self.positionAfter(p))
loop(self.startPosition)
Expand Down Expand Up @@ -243,7 +243,7 @@ extension [Self: Collection](self: Self)
else
val e = self.endPosition
def loop(p: Self.Position, least: Self.Element): Self.Element =
if p `eq` e then
if p eq e then
least
else
val x = self.at(p)
Expand All @@ -255,9 +255,9 @@ extension [Self: Collection](self: Self)
/** Returns `true` if `self` contains the same elements as `other`, in the same order. */
def elementsEqual[T: Collection { type Element = Self.Element } ](other: T): Boolean =
def loop(i: Self.Position, j: T.Position): Boolean =
if i `eq` self.endPosition then
j `eq` other.endPosition
else if j `eq` other.endPosition then
if i eq self.endPosition then
j eq other.endPosition
else if j eq other.endPosition then
false
else if self.at(i) `neq` other.at(j)then
false
Expand Down
4 changes: 2 additions & 2 deletions tests/pos/hylolib/CoreTraits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ trait Value:
def copy(): Self

/** Returns `true` iff `self` and `other` have an equivalent value. */
def eq(other: Self): Boolean
infix def eq(other: Self): Boolean

def neq(other: Self): Boolean = !self.eq(other)
def neq(other: Self): Boolean = !(self eq other)

/** Hashes the salient parts of `self` into `hasher`. */
def hashInto(hasher: Hasher): Hasher
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/hylolib/HyArray.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ given [T: Value] => HyArray[T] is Value:
def copy(): HyArray[T] =
self.copy()

def eq(other: HyArray[T]): Boolean =
infix def eq(other: HyArray[T]): Boolean =
self.elementsEqual(other)

def hashInto(hasher: Hasher): Hasher =
Expand Down
6 changes: 3 additions & 3 deletions tests/pos/hylolib/Integers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ given Boolean is Value:
// Note: Scala's `Boolean` has value semantics already.
self

def eq(other: Boolean): Boolean =
infix def eq(other: Boolean): Boolean =
self == other

def hashInto(hasher: Hasher): Hasher =
Expand All @@ -22,7 +22,7 @@ given Int is Value:
// Note: Scala's `Int` has value semantics already.
self

def eq(other: Int): Boolean =
infix def eq(other: Int): Boolean =
self == other

def hashInto(hasher: Hasher): Hasher =
Expand All @@ -35,7 +35,7 @@ given Int is Comparable:
def copy(): Int =
self

def eq(other: Int): Boolean =
infix def eq(other: Int): Boolean =
self == other

def hashInto(hasher: Hasher): Hasher =
Expand Down
Loading