@@ -1247,53 +1247,112 @@ trait Implicits:
1247
1247
|Consider using the scala.util.NotGiven class to implement similar functionality. """ ,
1248
1248
ctx.source.atSpan(span))
1249
1249
1250
- /** A relation that influences the order in which implicits are tried.
1250
+ /** Compare the length of the baseClasses of two symbols (except for objects,
1251
+ * where we use the length of the companion class instead if it's bigger).
1252
+ *
1253
+ * This relation is meant to approximate `Applications#compareOwner` while also
1254
+ * inducing a total ordering: `compareOwner` returns `0` for unrelated symbols
1255
+ * and therefore only induces a partial ordering, meaning it cannot be used
1256
+ * as a sorting function (see `java.util.Comparator#compare`).
1257
+ */
1258
+ def compareBaseClassesLength (sym1 : Symbol , sym2 : Symbol ): Int =
1259
+ def len (sym : Symbol ) =
1260
+ if sym.is(ModuleClass ) && sym.companionClass.exists then
1261
+ Math .max(sym.asClass.baseClassesLength, sym.companionClass.asClass.baseClassesLength)
1262
+ else if sym.isClass then
1263
+ sym.asClass.baseClassesLength
1264
+ else
1265
+ 0
1266
+ len(sym1) - len(sym2)
1267
+
1268
+ /** A relation that influences the order in which eligible implicits are tried.
1269
+ *
1251
1270
* We prefer (in order of importance)
1252
1271
* 1. more deeply nested definitions
1253
1272
* 2. definitions with fewer implicit parameters
1254
- * 3. definitions in subclasses
1273
+ * 3. definitions whose owner has more parents (see `compareBaseClassesLength`)
1255
1274
* The reason for (2) is that we want to fail fast if the search type
1256
1275
* is underconstrained. So we look for "small" goals first, because that
1257
1276
* will give an ambiguity quickly.
1258
1277
*/
1259
- def prefer (cand1 : Candidate , cand2 : Candidate ): Boolean =
1260
- val level1 = cand1.level
1261
- val level2 = cand2.level
1262
- if level1 > level2 then return true
1263
- if level1 < level2 then return false
1264
- val sym1 = cand1.ref.symbol
1265
- val sym2 = cand2.ref.symbol
1278
+ def compareEligibles (e1 : Candidate , e2 : Candidate ): Int =
1279
+ if e1 eq e2 then return 0
1280
+ val cmpLevel = e1.level - e2.level
1281
+ if cmpLevel != 0 then return - cmpLevel // 1.
1282
+ val sym1 = e1.ref.symbol
1283
+ val sym2 = e2.ref.symbol
1266
1284
val arity1 = sym1.info.firstParamTypes.length
1267
1285
val arity2 = sym2.info.firstParamTypes.length
1268
- if arity1 < arity2 then return true
1269
- if arity1 > arity2 then return false
1270
- compareOwner(sym1.owner, sym2.owner) == 1
1286
+ val cmpArity = arity1 - arity2
1287
+ if cmpArity != 0 then return cmpArity // 2.
1288
+ val cmpBcs = compareBaseClassesLength(sym1.owner, sym2.owner)
1289
+ - cmpBcs // 3.
1271
1290
1272
- /** Sort list of implicit references according to `prefer`.
1291
+ /** Check if `ord` respects the contract of `Ordering`.
1292
+ *
1293
+ * More precisely, we check that its `compare` method respects the invariants listed
1294
+ * in https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#compare-T-T-
1295
+ */
1296
+ def validateOrdering (ord : Ordering [Candidate ]): Unit =
1297
+ for
1298
+ x <- eligible
1299
+ y <- eligible
1300
+ cmpXY = Integer .signum(ord.compare(x, y))
1301
+ cmpYX = Integer .signum(ord.compare(y, x))
1302
+ z <- eligible
1303
+ cmpXZ = Integer .signum(ord.compare(x, z))
1304
+ cmpYZ = Integer .signum(ord.compare(y, z))
1305
+ do
1306
+ def reportViolation (msg : String ): Unit =
1307
+ Console .err.println(s " Internal error: comparison function violated ${msg.stripMargin}" )
1308
+ def showCandidate (c : Candidate ): String =
1309
+ s " $c ( ${c.ref.symbol.showLocated}) "
1310
+
1311
+ if cmpXY != - cmpYX then
1312
+ reportViolation(
1313
+ s """ signum(cmp(x, y)) == -signum(cmp(y, x)) given:
1314
+ |x = ${showCandidate(x)}
1315
+ |y = ${showCandidate(y)}
1316
+ |cmpXY = $cmpXY
1317
+ |cmpYX = $cmpYX""" )
1318
+ if cmpXY != 0 && cmpXY == cmpYZ && cmpXZ != cmpXY then
1319
+ reportViolation(
1320
+ s """ transitivity given:
1321
+ |x = ${showCandidate(x)}
1322
+ |y = ${showCandidate(y)}
1323
+ |z = ${showCandidate(z)}
1324
+ |cmpXY = $cmpXY
1325
+ |cmpXZ = $cmpXZ
1326
+ |cmpYZ = $cmpYZ""" )
1327
+ if cmpXY == 0 && cmpXZ != cmpYZ then
1328
+ reportViolation(
1329
+ s """ cmp(x, y) == 0 implies that signum(cmp(x, z)) == signum(cmp(y, z)) given:
1330
+ |x = ${showCandidate(x)}
1331
+ |y = ${showCandidate(y)}
1332
+ |z = ${showCandidate(z)}
1333
+ |cmpXY = $cmpXY
1334
+ |cmpXZ = $cmpXZ
1335
+ |cmpYZ = $cmpYZ""" )
1336
+ end validateOrdering
1337
+
1338
+ /** Sort list of implicit references according to `compareEligibles`.
1273
1339
* This is just an optimization that aims at reducing the average
1274
1340
* number of candidates to be tested.
1275
1341
*/
1276
- def sort (eligible : List [Candidate ]) = eligible match {
1342
+ def sort (eligible : List [Candidate ]) = eligible match
1277
1343
case Nil => eligible
1278
1344
case e1 :: Nil => eligible
1279
1345
case e1 :: e2 :: Nil =>
1280
- if (prefer( e2, e1)) e2 :: e1 :: Nil
1346
+ if compareEligibles( e2, e1) < 0 then e2 :: e1 :: Nil
1281
1347
else eligible
1282
1348
case _ =>
1283
- try eligible.sortWith(prefer)
1349
+ val ord : Ordering [Candidate ] = (a, b) => compareEligibles(a, b)
1350
+ try eligible.sorted(using ord)
1284
1351
catch case ex : IllegalArgumentException =>
1285
- // diagnostic to see what went wrong
1286
- for
1287
- e1 <- eligible
1288
- e2 <- eligible
1289
- if prefer(e1, e2)
1290
- e3 <- eligible
1291
- if prefer(e2, e3) && ! prefer(e1, e3)
1292
- do
1293
- val es = List (e1, e2, e3)
1294
- println(i " transitivity violated for $es%, % \n ${es.map(_.implicitRef.underlyingRef.symbol.showLocated)}%, % " )
1352
+ // This exception being thrown probably means that our comparison
1353
+ // function is broken, check if that's the case
1354
+ validateOrdering(ord)
1295
1355
throw ex
1296
- }
1297
1356
1298
1357
rank(sort(eligible), NoMatchingImplicitsFailure , Nil )
1299
1358
end searchImplicit
0 commit comments