Skip to content

Commit 046ba60

Browse files
authored
Strip LazyRef before calling simplified, in MT reduction (#18218)
2 parents 2753a17 + 990b65c commit 046ba60

File tree

2 files changed

+122
-1
lines changed

2 files changed

+122
-1
lines changed

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -1984,7 +1984,22 @@ object Types {
19841984
* this method handles this by never simplifying inside a `MethodicType`,
19851985
* except for replacing type parameters with associated type variables.
19861986
*/
1987-
def simplified(using Context): Type = TypeOps.simplify(this, null)
1987+
def simplified(using Context): Type =
1988+
// A recursive match type will have the recursive call
1989+
// wrapped in a LazyRef. For example in i18175, the recursive calls
1990+
// to IsPiped within the definition of IsPiped are all wrapped in LazyRefs.
1991+
// In addition to that, TypeMaps, such as the one that backs TypeOps.simplify,
1992+
// by default will rewrap a LazyRef when applying its function.
1993+
// The result of those two things means that given a big enough input
1994+
// that recurses enough times through one or multiple match types,
1995+
// reducing and simplifying the result of the case bodies,
1996+
// can end up with a large stack of directly-nested lazy refs.
1997+
// And if that nesting level breaches `Config.LogPendingSubTypesThreshold`,
1998+
// then TypeComparer will eventually start returning `false` for `isSubType`.
1999+
// Or, under -Yno-deep-subtypes, start throwing AssertionErrors.
2000+
// So, we eagerly strip that lazy ref here to avoid the stacking.
2001+
val tp = stripLazyRef
2002+
TypeOps.simplify(tp, null)
19882003

19892004
/** Compare `this == that`, assuming corresponding binders in `bs` are equal.
19902005
* The normal `equals` should be equivalent to `equals(that, null`)`.

tests/pos/i18175.scala

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import scala.compiletime.ops.int.{ +, -, Max }
2+
import scala.compiletime.ops.string.{ Substring, Length, Matches, CharAt }
3+
4+
class Regex[P] private() extends Serializable:
5+
def unapply(s: CharSequence)(implicit n: Regex.Sanitizer[P]): Option[P] = ???
6+
7+
object Regex:
8+
def apply[R <: String & Singleton](regex: R): Regex[Compile[R]] = ???
9+
10+
abstract class Sanitizer[T]
11+
object Sanitizer:
12+
given Sanitizer[EmptyTuple] = ???
13+
given stringcase[T <: Tuple: Sanitizer]: Sanitizer[String *: T] = ???
14+
given optioncase[T <: Tuple: Sanitizer]: Sanitizer[Option[String] *: T] = ???
15+
given Sanitizer[String] = ???
16+
given Sanitizer[Option[String]] = ???
17+
18+
type Compile[R <: String] = Matches["", R] match
19+
case _ => Reverse[EmptyTuple, Loop[R, 0, Length[R], EmptyTuple, IsPiped[R, 0, Length[R], 0]]]
20+
21+
type Loop[R <: String, Lo <: Int, Hi <: Int, Acc <: Tuple, Opt <: Int] <: Tuple = Lo match
22+
case Hi => Acc
23+
case _ => CharAt[R, Lo] match
24+
case '\\' => CharAt[R, Lo + 1] match
25+
case 'Q' => Loop[R, ToClosingQE[R, Lo + 2], Hi, Acc, Opt]
26+
case _ => Loop[R, Lo + 2, Hi, Acc, Opt]
27+
case '[' => Loop[R, ToClosingBracket[R, Lo + 1, 0], Hi, Acc, Opt]
28+
case ')' => Loop[R, Lo + 1, Hi, Acc, Max[0, Opt - 1]]
29+
case '(' => Opt match
30+
case 0 => IsMarked[R, ToClosingParenthesis[R, Lo + 1, 0], Hi] match
31+
case true => IsCapturing[R, Lo + 1] match
32+
case false => Loop[R, Lo + 1, Hi, Acc, 1]
33+
case true => Loop[R, Lo + 1, Hi, Option[String] *: Acc, 1]
34+
case false => IsCapturing[R, Lo + 1] match
35+
case false => Loop[R, Lo + 1, Hi, Acc, IsPiped[R, Lo + 1, Hi, 0]]
36+
case true => Loop[R, Lo + 1, Hi, String *: Acc, IsPiped[R, Lo + 1, Hi, 0]]
37+
case _ => IsCapturing[R, Lo + 1] match
38+
case false => Loop[R, Lo + 1, Hi, Acc, Opt + 1]
39+
case true => Loop[R, Lo + 1, Hi, Option[String] *: Acc, Opt + 1]
40+
case _ => Loop[R, Lo + 1, Hi, Acc, Opt]
41+
42+
type IsCapturing[R <: String, At <: Int] <: Boolean = CharAt[R, At] match
43+
case '?' => CharAt[R, At + 1] match
44+
case '<' => CharAt[R, At + 2] match
45+
case '=' | '!' => false
46+
case _ => true
47+
case _ => false
48+
case _ => true
49+
50+
type IsMarked[R <: String, At <: Int, Hi <: Int] <: Boolean = At match
51+
case Hi => false
52+
case _ => CharAt[R, At] match
53+
case '?' | '*' => true
54+
case '{' => CharAt[R, At + 1] match
55+
case '0' => true
56+
case _ => false
57+
case _ => false
58+
59+
type IsPiped[R <: String, At <: Int, Hi <: Int, Lvl <: Int] <: Int = At match
60+
case Hi => 0
61+
case _ => CharAt[R, At] match
62+
case '\\' => CharAt[R, At + 1] match
63+
case 'Q' => IsPiped[R, ToClosingQE[R, At + 2], Hi, Lvl]
64+
case _ => IsPiped[R, At + 2, Hi, Lvl]
65+
case '[' => IsPiped[R, ToClosingBracket[R, At + 1, 0], Hi, Lvl]
66+
case '(' => IsPiped[R, ToClosingParenthesis[R, At + 1, 0], Hi, Lvl + 1]
67+
case '|' => 1
68+
case ')' => 0
69+
case _ => IsPiped[R, At + 1, Hi, Lvl]
70+
71+
type ToClosingParenthesis[R <: String, At <: Int, Lvl <: Int] <: Int = CharAt[R, At] match
72+
case '\\' => CharAt[R, At + 1] match
73+
case 'Q' => ToClosingParenthesis[R, ToClosingQE[R, At + 2], Lvl]
74+
case _ => ToClosingParenthesis[R, At + 2, Lvl]
75+
case '[' => ToClosingParenthesis[R, ToClosingBracket[R, At + 1, 0], Lvl]
76+
case ')' => Lvl match
77+
case 0 => At + 1
78+
case _ => ToClosingParenthesis[R, At + 1, Lvl - 1]
79+
case '(' => ToClosingParenthesis[R, At + 1, Lvl + 1]
80+
case _ => ToClosingParenthesis[R, At + 1, Lvl]
81+
82+
type ToClosingBracket[R <: String, At <: Int, Lvl <: Int] <: Int = CharAt[R, At] match
83+
case '\\' => CharAt[R, At + 1] match
84+
case 'Q' => ToClosingBracket[R, ToClosingQE[R, At + 2], Lvl]
85+
case _ => ToClosingBracket[R, At + 2, Lvl]
86+
case '[' => ToClosingBracket[R, At + 1, Lvl + 1]
87+
case ']' => Lvl match
88+
case 0 => At + 1
89+
case _ => ToClosingBracket[R, At + 1, Lvl - 1]
90+
case _ => ToClosingBracket[R, At + 1, Lvl]
91+
92+
type ToClosingQE[R <: String, At <: Int] <: Int = CharAt[R, At] match
93+
case '\\' => CharAt[R, At + 1] match
94+
case 'E' => At + 2
95+
case _ => ToClosingQE[R, At + 2]
96+
case _ => ToClosingQE[R, At + 1]
97+
98+
type Reverse[Acc <: Tuple, X <: Tuple] <: Tuple = X match
99+
case x *: xs => Reverse[x *: Acc, xs]
100+
case EmptyTuple => Acc
101+
102+
object Test:
103+
def main(args: Array[String]): Unit =
104+
val r75 = Regex("(x|y|z[QW])*(longish|loquatious|excessive|overblown[QW])*")
105+
"xyzQzWlongishoverblownW" match
106+
case r75((Some(g0), Some(g1))) => ??? // failure

0 commit comments

Comments
 (0)