Skip to content

Commit b5b41b4

Browse files
authored
Merge pull request #9167 from dotty-staging/fix-#9160
Have a per-run time budget for import suggestions
2 parents b49c9e7 + dc96bb6 commit b5b41b4

10 files changed

+228
-70
lines changed

compiler/src/dotty/tools/dotc/Run.scala

+14
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ import scala.util.control.NonFatal
3131
/** A compiler run. Exports various methods to compile source files */
3232
class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo {
3333

34+
/** Default timeout to stop looking for further implicit suggestions, in ms.
35+
* This is usually for the first import suggestion; subsequent suggestions
36+
* may get smaller timeouts. @see ImportSuggestions.reduceTimeBudget
37+
*/
38+
private var myImportSuggestionBudget: Int =
39+
Int.MinValue // sentinel value; means whatever is set in command line option
40+
41+
def importSuggestionBudget =
42+
if myImportSuggestionBudget == Int.MinValue then ictx.settings.XimportSuggestionTimeout.value
43+
else myImportSuggestionBudget
44+
45+
def importSuggestionBudget_=(x: Int) =
46+
myImportSuggestionBudget = x
47+
3448
/** If this variable is set to `true`, some core typer operations will
3549
* return immediately. Currently these early abort operations are
3650
* `Typer.typed` and `Implicits.typedImplicit`.

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class ScalaSettings extends Settings.SettingGroup {
8787
val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.")
8888
val XverifySignatures: Setting[Boolean] = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.")
8989
val XignoreScala2Macros: Setting[Boolean] = BooleanSetting("-Xignore-scala2-macros", "Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime.")
90+
val XimportSuggestionTimeout: Setting[Int] = IntSetting("-Ximport-suggestion-timeout", "Timeout (in ms) for searching for import suggestions when errors are reported.", 8000)
9091

9192
val XmixinForceForwarders = ChoiceSetting(
9293
name = "-Xmixin-force-forwarders",

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

+15-5
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ trait ImportSuggestions:
2727
/** Timeout to test a single implicit value as a suggestion, in ms */
2828
private inline val testOneImplicitTimeOut = 500
2929

30-
/** Global timeout to stop looking for further implicit suggestions, in ms */
31-
private inline val suggestImplicitTimeOut = 10000
32-
3330
/** A list of TermRefs referring to the roots where suggestions for
3431
* imports of givens or extension methods that might fix a type error
3532
* are searched.
@@ -145,7 +142,11 @@ trait ImportSuggestions:
145142
*/
146143
private def importSuggestions(pt: Type)(using Context): (List[TermRef], List[TermRef]) =
147144
val timer = new Timer()
148-
val deadLine = System.currentTimeMillis() + suggestImplicitTimeOut
145+
val allotted = ctx.run.importSuggestionBudget
146+
if allotted <= 1 then return (Nil, Nil)
147+
implicits.println(i"looking for import suggestions, timeout = ${allotted}ms")
148+
val start = System.currentTimeMillis()
149+
val deadLine = start + allotted
149150

150151
// Candidates that are already available without explicit import because they
151152
// are already provided by the context (imported or inherited) or because they
@@ -249,9 +250,18 @@ trait ImportSuggestions:
249250
println("caught exception when searching for suggestions")
250251
ex.printStackTrace()
251252
(Nil, Nil)
252-
finally timer.cancel()
253+
finally
254+
timer.cancel()
255+
reduceTimeBudget(((System.currentTimeMillis() - start) min Int.MaxValue).toInt)
253256
end importSuggestions
254257

258+
/** Reduce next timeout for import suggestions by the amount of time it took
259+
* for current search, but but never less than to half of the previous budget.
260+
*/
261+
private def reduceTimeBudget(used: Int)(using Context) =
262+
ctx.run.importSuggestionBudget =
263+
(ctx.run.importSuggestionBudget - used) max (ctx.run.importSuggestionBudget / 2)
264+
255265
/** The `ref` parts of this list of pairs, discarding subsequent elements that
256266
* have the same String part. Elements are sorted by their String parts.
257267
*/

tests/neg/i9160.scala

+131
Large diffs are not rendered by default.

tests/neg/missing-implicit-2.check

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- Error: tests/neg/missing-implicit-2.scala:4:24 ----------------------------------------------------------------------
2+
4 |val f = Future[Unit] { } // error
3+
| ^
4+
| Cannot find an implicit ExecutionContext. You might pass
5+
| an (implicit ec: ExecutionContext) parameter to your method.
6+
|
7+
| The ExecutionContext is used to configure how and on which
8+
| thread pools Futures will run, so the specific ExecutionContext
9+
| that is selected is important.
10+
|
11+
| If your application does not define an ExecutionContext elsewhere,
12+
| consider using Scala's global ExecutionContext by defining
13+
| the following:
14+
|
15+
| implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
16+
|
17+
| The following import might fix the problem:
18+
|
19+
| import concurrent.ExecutionContext.Implicits.global
20+
|
21+
-- [E007] Type Mismatch Error: tests/neg/missing-implicit-2.scala:6:25 -------------------------------------------------
22+
6 |val b: java.lang.Byte = (1: Byte) // error, but no hint
23+
| ^^^^^^^
24+
| Found: Byte
25+
| Required: Byte²
26+
|
27+
| where: Byte is a class in package scala
28+
| Byte² is a class in package java.lang

tests/neg/missing-implicit-2.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Predef.{byte2Byte => _, _}
2+
import scala.concurrent.Future
3+
4+
val f = Future[Unit] { } // error
5+
6+
val b: java.lang.Byte = (1: Byte) // error, but no hint

tests/neg/missing-implicit-3.check

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-- [E007] Type Mismatch Error: tests/neg/missing-implicit-3.scala:6:44 -------------------------------------------------
2+
6 |val d: scala.concurrent.duration.Duration = (10, DAYS) // error
3+
| ^^^^^^^^^^
4+
| Found: (Int, java.util.concurrent.TimeUnit)
5+
| Required: concurrent².duration.Duration
6+
|
7+
| where: concurrent is a package in package java.util
8+
| concurrent² is a package in package scala
9+
|
10+
|
11+
| The following import might fix the problem:
12+
|
13+
| import concurrent.duration.pairIntToDuration
14+
|
15+
-- [E008] Not Found Error: tests/neg/missing-implicit-3.scala:8:48 -----------------------------------------------------
16+
8 |val d2: scala.concurrent.duration.Duration = 10.days // error
17+
| ^^^^^^^
18+
| value days is not a member of Int, but could be made available as an extension method.
19+
|
20+
| One of the following imports might fix the problem:
21+
|
22+
| import concurrent.duration.DurationInt
23+
| import concurrent.duration.DurationLong
24+
| import concurrent.duration.DurationDouble
25+
|

tests/neg/missing-implicit-3.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Predef.{byte2Byte => _, _}
2+
import math.Numeric
3+
4+
val DAYS = scala.concurrent.duration.DAYS
5+
6+
val d: scala.concurrent.duration.Duration = (10, DAYS) // error
7+
8+
val d2: scala.concurrent.duration.Duration = 10.days // error

tests/neg/missing-implicit.check

-53
Original file line numberDiff line numberDiff line change
@@ -17,56 +17,3 @@
1717
|
1818
| import math.Numeric.Implicits.infixNumericOps
1919
|
20-
-- Error: tests/neg/missing-implicit.scala:10:24 -----------------------------------------------------------------------
21-
10 |val f = Future[Unit] { } // error
22-
| ^
23-
| Cannot find an implicit ExecutionContext. You might pass
24-
| an (implicit ec: ExecutionContext) parameter to your method.
25-
|
26-
| The ExecutionContext is used to configure how and on which
27-
| thread pools Futures will run, so the specific ExecutionContext
28-
| that is selected is important.
29-
|
30-
| If your application does not define an ExecutionContext elsewhere,
31-
| consider using Scala's global ExecutionContext by defining
32-
| the following:
33-
|
34-
| implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
35-
|
36-
| The following import might fix the problem:
37-
|
38-
| import concurrent.ExecutionContext.Implicits.global
39-
|
40-
-- [E007] Type Mismatch Error: tests/neg/missing-implicit.scala:12:25 --------------------------------------------------
41-
12 |val b: java.lang.Byte = (1: Byte) // error, but no hint
42-
| ^^^^^^^
43-
| Found: Byte
44-
| Required: Byte²
45-
|
46-
| where: Byte is a class in package scala
47-
| Byte² is a class in package java.lang
48-
-- [E007] Type Mismatch Error: tests/neg/missing-implicit.scala:16:44 --------------------------------------------------
49-
16 |val d: scala.concurrent.duration.Duration = (10, DAYS) // error
50-
| ^^^^^^^^^^
51-
| Found: (Int, java.util.concurrent.TimeUnit)
52-
| Required: concurrent².duration.Duration
53-
|
54-
| where: concurrent is a package in package java.util
55-
| concurrent² is a package in package scala
56-
|
57-
|
58-
| The following import might fix the problem:
59-
|
60-
| import concurrent.duration.pairIntToDuration
61-
|
62-
-- [E008] Not Found Error: tests/neg/missing-implicit.scala:18:48 ------------------------------------------------------
63-
18 |val d2: scala.concurrent.duration.Duration = 10.days // error
64-
| ^^^^^^^
65-
| value days is not a member of Int, but could be made available as an extension method.
66-
|
67-
| One of the following imports might fix the problem:
68-
|
69-
| import concurrent.duration.DurationInt
70-
| import concurrent.duration.DurationLong
71-
| import concurrent.duration.DurationDouble
72-
|

tests/neg/missing-implicit.scala

-12
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,3 @@ import math.Numeric
44
def consume[T: Numeric](xs: List[T], limit: T): List[T] = xs match
55
case x :: xs1 if limit > 0 => consume(xs1, limit - x) // error // error
66
case _ => xs
7-
8-
import scala.concurrent.Future
9-
10-
val f = Future[Unit] { } // error
11-
12-
val b: java.lang.Byte = (1: Byte) // error, but no hint
13-
14-
val DAYS = scala.concurrent.duration.DAYS
15-
16-
val d: scala.concurrent.duration.Duration = (10, DAYS) // error
17-
18-
val d2: scala.concurrent.duration.Duration = 10.days // error

0 commit comments

Comments
 (0)