Skip to content

Commit daca114

Browse files
authoredMar 18, 2025
Merge pull request #184 from scala/backport-lts-3.3-22366
Backport "Fix scala#21841: Check more that an `unapplySeq` on a `NonEmptyTuple` is valid." to 3.3 LTS
2 parents cb73316 + d552034 commit daca114

File tree

7 files changed

+154
-31
lines changed

7 files changed

+154
-31
lines changed
 

‎compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+7
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
19441944
else op2
19451945
end necessaryEither
19461946

1947+
inline def rollbackConstraintsUnless(inline op: Boolean): Boolean =
1948+
val saved = constraint
1949+
var result = false
1950+
try result = ctx.gadtState.rollbackGadtUnless(op)
1951+
finally if !result then constraint = saved
1952+
result
1953+
19471954
/** Decompose into conjunction of types each of which has only a single refinement */
19481955
def decomposeRefinements(tp: Type, refines: List[(Name, Type)]): Type = tp match
19491956
case RefinedType(parent, rname, rinfo) =>

‎compiler/src/dotty/tools/dotc/typer/Applications.scala

+27-7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ object Applications {
6767
unapplySeqTypeElemTp(productSelectorTypes(tp, errorPos).last).exists
6868
}
6969

70+
/** Does `tp` fit the "product-seq match" conditions for a `NonEmptyTuple` as
71+
* an unapply result type for a pattern with `numArgs` subpatterns?
72+
* This is the case if (1) `tp` derives from `NonEmptyTuple`.
73+
* (2) `tp.tupleElementTypes` exists.
74+
* (3) `tp.tupleElementTypes.last` conforms to Seq match
75+
*/
76+
def isNonEmptyTupleSeqMatch(tp: Type, numArgs: Int, errorPos: SrcPos = NoSourcePosition)(using Context): Boolean = {
77+
tp.derivesFrom(defn.NonEmptyTupleClass)
78+
&& tp.tupleElementTypes.exists { elemTypes =>
79+
val arity = elemTypes.size
80+
arity > 0 && arity <= numArgs + 1 &&
81+
unapplySeqTypeElemTp(elemTypes.last).exists
82+
}
83+
}
84+
7085
/** Does `tp` fit the "get match" conditions as an unapply result type?
7186
* This is the case of `tp` has a `get` member as well as a
7287
* parameterless `isEmpty` member of result type `Boolean`.
@@ -143,12 +158,17 @@ object Applications {
143158
}
144159
else tp :: Nil
145160

146-
def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = {
147-
val selTps = productSelectorTypes(tp, pos)
148-
val arity = selTps.length
149-
val elemTp = unapplySeqTypeElemTp(selTps.last)
150-
(0 until argsNum).map(i => if (i < arity - 1) selTps(i) else elemTp).toList
151-
}
161+
def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] =
162+
seqSelectors(productSelectorTypes(tp, pos), argsNum)
163+
164+
def nonEmptyTupleSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] =
165+
seqSelectors(tp.tupleElementTypes.get, argsNum)
166+
167+
private def seqSelectors(selectorTypes: List[Type], argsNum: Int)(using Context): List[Type] =
168+
val arity = selectorTypes.length
169+
val elemTp = unapplySeqTypeElemTp(selectorTypes.last)
170+
(0 until argsNum).map(i => if (i < arity - 1) selectorTypes(i) else elemTp).toList
171+
end seqSelectors
152172

153173
def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: SrcPos)(using Context): List[Type] = {
154174
def getName(fn: Tree): Name =
@@ -169,7 +189,7 @@ object Applications {
169189
val elemTp = unapplySeqTypeElemTp(tp)
170190
if (elemTp.exists) args.map(Function.const(elemTp))
171191
else if (isProductSeqMatch(tp, args.length, pos)) productSeqSelectors(tp, args.length, pos)
172-
else if tp.derivesFrom(defn.NonEmptyTupleClass) then foldApplyTupleType(tp)
192+
else if isNonEmptyTupleSeqMatch(tp, args.length, pos) then nonEmptyTupleSeqSelectors(tp, args.length, pos)
173193
else fallback
174194
}
175195

‎tests/coverage/run/i16940/test.scoverage.check

+30-13
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,23 @@ Test
9393
Object
9494
<empty>.Test
9595
<init>
96+
387
97+
390
98+
20
99+
Seq
100+
Ident
101+
false
102+
0
103+
false
104+
Seq
105+
106+
5
107+
i16940/i16940.scala
108+
<empty>
109+
Test
110+
Object
111+
<empty>.Test
112+
<init>
96113
391
97114
421
98115
20
@@ -103,7 +120,7 @@ false
103120
false
104121
brokenSynchronizedBlock(false)
105122

106-
5
123+
6
107124
i16940/i16940.scala
108125
<empty>
109126
Test
@@ -120,7 +137,7 @@ false
120137
false
121138
brokenSynchronizedBlock(true)
122139

123-
6
140+
7
124141
i16940/i16940.scala
125142
<empty>
126143
Test
@@ -137,7 +154,7 @@ false
137154
false
138155
println(test)
139156

140-
7
157+
8
141158
i16940/i16940.scala
142159
<empty>
143160
Test
@@ -154,7 +171,7 @@ false
154171
false
155172
assert(test == 2)
156173

157-
8
174+
9
158175
i16940/i16940.scala
159176
<empty>
160177
Test
@@ -171,7 +188,7 @@ true
171188
false
172189
assert(test == 2)
173190

174-
9
191+
10
175192
i16940/i16940.scala
176193
<empty>
177194
Test
@@ -188,7 +205,7 @@ true
188205
false
189206
assert(test == 2)
190207

191-
10
208+
11
192209
i16940/i16940.scala
193210
<empty>
194211
Test
@@ -205,7 +222,7 @@ false
205222
false
206223
3.seconds
207224

208-
11
225+
12
209226
i16940/i16940.scala
210227
<empty>
211228
i16940$package
@@ -222,7 +239,7 @@ false
222239
false
223240
Future {\n if (option) {\n Thread.sleep(500)\n }\n synchronized {\n val tmp = test\n Thread.sleep(1000)\n test = tmp + 1\n }\n}
224241

225-
12
242+
13
226243
i16940/i16940.scala
227244
<empty>
228245
i16940$package
@@ -239,7 +256,7 @@ false
239256
false
240257
Thread.sleep(500)
241258

242-
13
259+
14
243260
i16940/i16940.scala
244261
<empty>
245262
i16940$package
@@ -256,7 +273,7 @@ true
256273
false
257274
{\n Thread.sleep(500)\n }
258275

259-
14
276+
15
260277
i16940/i16940.scala
261278
<empty>
262279
i16940$package
@@ -273,7 +290,7 @@ true
273290
false
274291

275292

276-
15
293+
16
277294
i16940/i16940.scala
278295
<empty>
279296
i16940$package
@@ -290,7 +307,7 @@ false
290307
false
291308
synchronized {\n val tmp = test\n Thread.sleep(1000)\n test = tmp + 1\n }
292309

293-
16
310+
17
294311
i16940/i16940.scala
295312
<empty>
296313
i16940$package
@@ -307,7 +324,7 @@ false
307324
false
308325
Thread.sleep(1000)
309326

310-
17
327+
18
311328
i16940/i16940.scala
312329
<empty>
313330
i16940$package

‎tests/coverage/run/i18233-min/test.scoverage.check

+40-6
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,23 @@ i18233-min$package
110110
Object
111111
<empty>.i18233-min$package
112112
aList
113+
14
114+
18
115+
2
116+
List
117+
Ident
118+
false
119+
0
120+
false
121+
List
122+
123+
6
124+
i18233-min/i18233-min.scala
125+
<empty>
126+
i18233-min$package
127+
Object
128+
<empty>.i18233-min$package
129+
aList
113130
19
114131
34
115132
2
@@ -120,7 +137,7 @@ false
120137
false
121138
Array[String]()
122139

123-
6
140+
7
124141
i18233-min/i18233-min.scala
125142
<empty>
126143
i18233-min$package
@@ -137,7 +154,7 @@ false
137154
false
138155
def aList
139156

140-
7
157+
8
141158
i18233-min/i18233-min.scala
142159
<empty>
143160
i18233-min$package
@@ -154,7 +171,7 @@ false
154171
false
155172
Array("abc", "def")
156173

157-
8
174+
9
158175
i18233-min/i18233-min.scala
159176
<empty>
160177
i18233-min$package
@@ -171,7 +188,7 @@ false
171188
false
172189
def arr
173190

174-
9
191+
10
175192
i18233-min/i18233-min.scala
176193
<empty>
177194
i18233-min$package
@@ -188,7 +205,24 @@ false
188205
false
189206
List(arr*)
190207

191-
10
208+
11
209+
i18233-min/i18233-min.scala
210+
<empty>
211+
i18233-min$package
212+
Object
213+
<empty>.i18233-min$package
214+
anotherList
215+
91
216+
95
217+
8
218+
List
219+
Ident
220+
false
221+
0
222+
false
223+
List
224+
225+
12
192226
i18233-min/i18233-min.scala
193227
<empty>
194228
i18233-min$package
@@ -205,7 +239,7 @@ false
205239
false
206240
arr
207241

208-
11
242+
13
209243
i18233-min/i18233-min.scala
210244
<empty>
211245
i18233-min$package

‎tests/coverage/run/i18233/test.scoverage.check

+22-5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ Foo
4242
Object
4343
<empty>.Foo
4444
render
45+
54
46+
58
47+
5
48+
List
49+
Ident
50+
false
51+
0
52+
false
53+
List
54+
55+
2
56+
i18233/i18233.scala
57+
<empty>
58+
Foo
59+
Object
60+
<empty>.Foo
61+
render
4562
59
4663
65
4764
5
@@ -52,7 +69,7 @@ false
5269
false
5370
values
5471

55-
2
72+
3
5673
i18233/i18233.scala
5774
<empty>
5875
Foo
@@ -69,7 +86,7 @@ false
6986
false
7087
values.tail
7188

72-
3
89+
4
7390
i18233/i18233.scala
7491
<empty>
7592
Foo
@@ -86,7 +103,7 @@ false
86103
false
87104
List(values.tail*).mkString
88105

89-
4
106+
5
90107
i18233/i18233.scala
91108
<empty>
92109
Foo
@@ -103,7 +120,7 @@ false
103120
false
104121
def render
105122

106-
5
123+
6
107124
i18233/i18233.scala
108125
<empty>
109126
Test
@@ -120,7 +137,7 @@ false
120137
false
121138
println(Foo.render)
122139

123-
6
140+
7
124141
i18233/i18233.scala
125142
<empty>
126143
Test

‎tests/neg/i21841.check

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E108] Declaration Error: tests/neg/i21841.scala:20:13 --------------------------------------------------------------
2+
20 | case v[T](l, r) => () // error
3+
| ^^^^^^^^^^
4+
| Option[(Test.Expr[Test.T], Test.Expr[Test.T])] is not a valid result type of an unapplySeq method of an extractor.
5+
|
6+
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)
Failed to load comments.