question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Very slow compile time involving path-dependent types

See original GitHub issue

Compiler version

3.1.2

Minimized code

Given the minimized code:

class /[D, T]
class Delegating[D]

type Aux[E] = Container { type Elements = E }

class Container:
  type Elements = Delegating[Delegates]
  type Delegates

class Resolution[E](value: Aux[E]):
  type Type = Aux[E]

The following code compiles almost instantly (which is expected):

def element0: Container { type Delegates = Unit } = ???

def element16(
    transmittable0: Resolution[?], transmittable1: Resolution[?],
    transmittable2: Resolution[?], transmittable3: Resolution[?],
    transmittable4: Resolution[?], transmittable5: Resolution[?],
    transmittable6: Resolution[?], transmittable7: Resolution[?],
    transmittable8: Resolution[?], transmittable9: Resolution[?],
    transmittable10: Resolution[?], transmittable11: Resolution[?],
    transmittable12: Resolution[?], transmittable13: Resolution[?],
    transmittable14: Resolution[?], transmittable15: Resolution[?])
: Container {
    type Delegates =
      transmittable0.Type / transmittable1.Type /
      transmittable2.Type / transmittable3.Type /
      transmittable4.Type / transmittable5.Type /
      transmittable6.Type / transmittable7.Type /
      transmittable8.Type / transmittable9.Type /
      transmittable10.Type / transmittable11.Type /
      transmittable12.Type / transmittable13.Type /
      transmittable14.Type / transmittable15.Type
  } = ???

def test16 =
  Resolution(
    element16(
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0)))

But the following example, which is just a slightly bigger version of the previous example (22 instead of 16 parameters), takes 10+ seconds to compile on my machine:

def element22(
    transmittable0: Resolution[?], transmittable1: Resolution[?],
    transmittable2: Resolution[?], transmittable3: Resolution[?],
    transmittable4: Resolution[?], transmittable5: Resolution[?],
    transmittable6: Resolution[?], transmittable7: Resolution[?],
    transmittable8: Resolution[?], transmittable9: Resolution[?],
    transmittable10: Resolution[?], transmittable11: Resolution[?],
    transmittable12: Resolution[?], transmittable13: Resolution[?],
    transmittable14: Resolution[?], transmittable15: Resolution[?],
    transmittable16: Resolution[?], transmittable17: Resolution[?],
    transmittable18: Resolution[?], transmittable19: Resolution[?],
    transmittable20: Resolution[?], transmittable21: Resolution[?])
: Container {
    type Delegates =
      transmittable0.Type / transmittable1.Type /
      transmittable2.Type / transmittable3.Type /
      transmittable4.Type / transmittable5.Type /
      transmittable6.Type / transmittable7.Type /
      transmittable8.Type / transmittable9.Type /
      transmittable10.Type / transmittable11.Type /
      transmittable12.Type / transmittable13.Type /
      transmittable14.Type / transmittable15.Type /
      transmittable16.Type / transmittable17.Type /
      transmittable18.Type / transmittable19.Type /
      transmittable20.Type / transmittable21.Type
  } = ???

def test22 =
  Resolution(
    element22(
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0),
      Resolution(element0), Resolution(element0)))

My “real” (not minimized code) has a few more parameters and takes ages to compile (did not finish yet after a several minutes), which makes me think that code in this shape maybe triggers some kind of exponential explosion in the type checker.

Expectation

Scala 2.13 compiles the code instantly. The expectation would be the same for Scala 3.

Note

FYI, I found #14224, which may or may not have the same root cause.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
oderskycommented, Jun 29, 2022

I checked that indeed every increase in element number by one gives twice as many subtype calls. (How?: I turned the inline value Stats.enabled on, compiled with -Ydetailed-stats and looked at the number of “total subtype” entries for various sizes of element.).

The reason, I believe is this: When testing equality A =:= B we test A <:< B and B <:< A. The program here has a deeply nested applied type of an invariant type constructor /. Comparing applied invariant types means testing corresponding type arguments for equality. So at each level we have a branch out factor of 2, for an overall exponential complexity.

1reaction
oderskycommented, Jun 30, 2022

Does it make sense to get rid of <skolem>.SomeAlias early on?

This helps in the particular test case because then the two types that are compared hash-cons after dealiasing to the same type. But the fix in #15556 is more general.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Scala 3: dealing with path dependent types
Types are inferred at compile time, so a.T =:= a1.T is checked at compile time when we can't know yet whether a and...
Read more >
Can a dependent type system catch *all* type error...
A language with a very powerful type system can still have a very dumb compiler (although it will generate extremely slow code).
Read more >
Path dependent types
Well, actually dependent typing is helpful in solving very down-to-earth problems. I bet that everyone programming in a language with sufficiently expressive ...
Read more >
Dependent Types are a Runtime Maybe : r/haskell
Dependent types can actually make a program run faster. (NB: run faster, not compile faster.) This can happen when the types give the...
Read more >
Dependent Types and Compile Time Types
Instead of dealing with polymorphism and dependency of types on values directly in the static semantics, C++ adds an extra untyped language ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found