Skip to content

Commit a0e1000

Browse files
authored
Merge pull request #4108 from dotty-staging/add-kind-poly
Add kind polymorphism
2 parents 03489c5 + 91e7fc4 commit a0e1000

File tree

24 files changed

+613
-54
lines changed

24 files changed

+613
-54
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ class ScalaSettings extends Settings.SettingGroup {
123123
val YprofileRunGcBetweenPhases = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_")
124124
//.withPostSetHook( _ => YprofileEnabled.value = true )
125125

126+
// Extremely experimental language features
127+
val YkindPolymorphism = BooleanSetting("-Ykind-polymorphism", "Enable kind polymorphism (see http://dotty.epfl.ch/docs/reference/kind-polymorphism.html). Potentially unsound.")
128+
126129
/** Area-specific debug output */
127130
val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
128131
val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,16 @@ class Definitions {
304304
def ObjectMethods = List(Object_eq, Object_ne, Object_synchronized, Object_clone,
305305
Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI)
306306

307+
lazy val AnyKindClass = {
308+
val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil)
309+
if (ctx.settings.YkindPolymorphism.value) {
310+
// Enable kind-polymorphism by exposing scala.AnyKind
311+
cls.entered
312+
}
313+
cls
314+
}
315+
def AnyKindType = AnyKindClass.typeRef
316+
307317
/** Marker method to indicate an argument to a call-by-name parameter.
308318
* Created by byNameClosures and elimByName, eliminated by Erasure,
309319
*/
@@ -1158,6 +1168,7 @@ class Definitions {
11581168
lazy val syntheticScalaClasses = List(
11591169
AnyClass,
11601170
AnyRefAlias,
1171+
AnyKindClass,
11611172
RepeatedParamClass,
11621173
ByNameParamClass2x,
11631174
AnyValClass,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ object StdNames {
196196
final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator"
197197

198198
final val Any: N = "Any"
199+
final val AnyKind: N = "AnyKind"
199200
final val AnyVal: N = "AnyVal"
200201
final val ExprApi: N = "ExprApi"
201202
final val Mirror: N = "Mirror"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ object SymDenotations {
667667

668668
/** Is this symbol a class references to which that are supertypes of null? */
669669
final def isNullableClass(implicit ctx: Context): Boolean =
670-
isClass && !isValueClass && !(this is ModuleClass) && symbol != defn.NothingClass
670+
isClass && !isValueClass && !is(ModuleClass) && symbol != defn.NothingClass
671671

672672
/** Is this definition accessible as a member of tree with type `pre`?
673673
* @param pre The type of the tree from which the selection is made

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

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class TypeApplications(val self: Type) extends AnyVal {
197197

198198
/** If `self` is a higher-kinded type, its type parameters, otherwise Nil */
199199
final def hkTypeParams(implicit ctx: Context): List[TypeParamInfo] =
200-
if (isHK) typeParams else Nil
200+
if (isLambdaSub) typeParams else Nil
201201

202202
/** If `self` is a generic class, its type parameter symbols, otherwise Nil */
203203
final def typeParamSymbols(implicit ctx: Context): List[TypeSymbol] = typeParams match {
@@ -207,12 +207,19 @@ class TypeApplications(val self: Type) extends AnyVal {
207207
case _ => Nil
208208
}
209209

210-
/** Is self type higher-kinded (i.e. of kind != "*")? */
211-
def isHK(implicit ctx: Context): Boolean = hkResult.exists
210+
/** Is self type bounded by a type lambda or AnyKind? */
211+
def isLambdaSub(implicit ctx: Context): Boolean = hkResult.exists
212212

213-
/** If self type is higher-kinded, its result type, otherwise NoType */
213+
/** Is self type of kind != "*"? */
214+
def hasHigherKind(implicit ctx: Context): Boolean =
215+
typeParams.nonEmpty || self.isRef(defn.AnyKindClass)
216+
217+
/** If self type is higher-kinded, its result type, otherwise NoType.
218+
* Note: The hkResult of an any-kinded type is again AnyKind.
219+
*/
214220
def hkResult(implicit ctx: Context): Type = self.dealias match {
215-
case self: TypeRef => self.info.hkResult
221+
case self: TypeRef =>
222+
if (self.symbol == defn.AnyKindClass) self else self.info.hkResult
216223
case self: AppliedType =>
217224
if (self.tycon.typeSymbol.isClass) NoType else self.superType.hkResult
218225
case self: HKTypeLambda => self.resultType
@@ -226,17 +233,24 @@ class TypeApplications(val self: Type) extends AnyVal {
226233
case _ => NoType
227234
}
228235

229-
/** Do self and other have the same kinds (not counting bounds and variances) */
236+
/** Do self and other have the same kinds (not counting bounds and variances)?
237+
* Note: An any-kinded type "has the same kind" as any other type.
238+
*/
230239
def hasSameKindAs(other: Type)(implicit ctx: Context): Boolean = {
231-
// println(i"check kind $self $other") // DEBUG
240+
def isAnyKind(tp: Type) = tp match {
241+
case tp: TypeRef => tp.symbol == defn.AnyKindClass
242+
case _ => false
243+
}
232244
val selfResult = self.hkResult
233245
val otherResult = other.hkResult
234-
if (selfResult.exists)
235-
otherResult.exists &&
236-
selfResult.hasSameKindAs(otherResult) &&
237-
self.typeParams.corresponds(other.typeParams)((sparam, oparam) =>
238-
sparam.paramInfo.hasSameKindAs(oparam.paramInfo))
239-
else !otherResult.exists
246+
isAnyKind(selfResult) || isAnyKind(otherResult) ||
247+
{ if (selfResult.exists)
248+
otherResult.exists &&
249+
selfResult.hasSameKindAs(otherResult) &&
250+
self.typeParams.corresponds(other.typeParams)((sparam, oparam) =>
251+
sparam.paramInfo.hasSameKindAs(oparam.paramInfo))
252+
else !otherResult.exists
253+
}
240254
}
241255

242256
/** Dealias type if it can be done without forcing the TypeRef's info */
@@ -256,9 +270,9 @@ class TypeApplications(val self: Type) extends AnyVal {
256270
//.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}")
257271
}
258272

259-
/** If self is not higher-kinded, eta expand it. */
260-
def ensureHK(implicit ctx: Context): Type =
261-
if (isHK) self else EtaExpansion(self)
273+
/** If self is not lambda-bound, eta expand it. */
274+
def ensureLambdaSub(implicit ctx: Context): Type =
275+
if (isLambdaSub) self else EtaExpansion(self)
262276

263277
/** Eta expand if `self` is a (non-lambda) class reference and `bound` is a higher-kinded type */
264278
def EtaExpandIfHK(bound: Type)(implicit ctx: Context): Type = {
@@ -355,7 +369,9 @@ class TypeApplications(val self: Type) extends AnyVal {
355369
case _ => false
356370
}
357371
}
358-
if ((dealiased eq stripped) || followAlias) dealiased.instantiate(args)
372+
if ((dealiased eq stripped) || followAlias)
373+
try dealiased.instantiate(args)
374+
catch { case ex: IndexOutOfBoundsException => AppliedType(self, args) }
359375
else AppliedType(self, args)
360376
}
361377
else dealiased.resType match {

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

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,22 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
4848
private[this] var totalCount = 0
4949

5050
private[this] var myAnyClass: ClassSymbol = null
51+
private[this] var myAnyKindClass: ClassSymbol = null
5152
private[this] var myNothingClass: ClassSymbol = null
5253
private[this] var myNullClass: ClassSymbol = null
5354
private[this] var myObjectClass: ClassSymbol = null
5455
private[this] var myAnyType: TypeRef = null
56+
private[this] var myAnyKindType: TypeRef = null
5557
private[this] var myNothingType: TypeRef = null
5658

5759
def AnyClass = {
5860
if (myAnyClass == null) myAnyClass = defn.AnyClass
5961
myAnyClass
6062
}
63+
def AnyKindClass = {
64+
if (myAnyKindClass == null) myAnyKindClass = defn.AnyKindClass
65+
myAnyKindClass
66+
}
6167
def NothingClass = {
6268
if (myNothingClass == null) myNothingClass = defn.NothingClass
6369
myNothingClass
@@ -74,6 +80,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
7480
if (myAnyType == null) myAnyType = AnyClass.typeRef
7581
myAnyType
7682
}
83+
def AnyKindType = {
84+
if (myAnyKindType == null) myAnyKindType = AnyKindClass.typeRef
85+
myAnyKindType
86+
}
7787
def NothingType = {
7888
if (myNothingType == null) myNothingType = NothingClass.typeRef
7989
myNothingType
@@ -367,19 +377,25 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
367377
case _ =>
368378
val cls2 = tp2.symbol
369379
if (cls2.isClass) {
370-
if (cls2.typeParams.nonEmpty && tp1.isHK)
371-
recur(tp1, EtaExpansion(cls2.typeRef))
372-
else {
380+
if (cls2.typeParams.isEmpty) {
381+
if (cls2 eq AnyKindClass) return true
382+
if (tp1.isRef(defn.NothingClass)) return true
383+
if (tp1.isLambdaSub) return false
384+
// Note: We would like to replace this by `if (tp1.hasHigherKind)`
385+
// but right now we cannot since some parts of the standard library rely on the
386+
// idiom that e.g. `List <: Any`. We have to bootstrap without scalac first.
373387
val base = tp1.baseType(cls2)
374-
if (base.exists) {
375-
if (cls2.is(JavaDefined))
376-
// If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol
377-
return base.typeSymbol == cls2
378-
if (base ne tp1)
379-
return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow)
380-
}
388+
if (base.exists && base.ne(tp1))
389+
return isSubType(base, tp2, if (tp1.isRef(cls2)) approx else approx.addLow)
381390
if (cls2 == defn.SingletonClass && tp1.isStable) return true
382391
}
392+
else if (cls2.is(JavaDefined)) {
393+
// If `cls2` is parameterized, we are seeing a raw type, so we need to compare only the symbol
394+
val base = tp1.baseType(cls2)
395+
if (base.typeSymbol == cls2) return true
396+
}
397+
else if (tp1.isLambdaSub && !tp1.isRef(defn.AnyKindClass))
398+
return recur(tp1, EtaExpansion(cls2.typeRef))
383399
}
384400
fourthTry
385401
}
@@ -475,13 +491,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
475491
isSubType(tp1.resType, tp2.resType.subst(tp2, tp1))
476492
finally comparedTypeLambdas = saved
477493
case _ =>
478-
if (tp1.isHK) {
479-
val tparams1 = tp1.typeParams
494+
val tparams1 = tp1.typeParams
495+
if (tparams1.nonEmpty)
480496
return recur(
481497
HKTypeLambda.fromParams(tparams1, tp1.appliedTo(tparams1.map(_.paramRef))),
482-
tp2
483-
)
484-
}
498+
tp2)
485499
else tp2 match {
486500
case EtaExpansion(tycon2) if tycon2.symbol.isClass =>
487501
return recur(tp1, tycon2)
@@ -540,7 +554,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
540554
def compareTypeBounds = tp1 match {
541555
case tp1 @ TypeBounds(lo1, hi1) =>
542556
((lo2 eq NothingType) || isSubType(lo2, lo1)) &&
543-
((hi2 eq AnyType) || isSubType(hi1, hi2))
557+
((hi2 eq AnyType) && !hi1.isLambdaSub || (hi2 eq AnyKindType) || isSubType(hi1, hi2))
544558
case tp1: ClassInfo =>
545559
tp2 contains tp1
546560
case _ =>
@@ -610,7 +624,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
610624
case EtaExpansion(tycon1) => recur(tycon1, tp2)
611625
case _ => tp2 match {
612626
case tp2: HKTypeLambda => false // this case was covered in thirdTry
613-
case _ => tp2.isHK && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs))
627+
case _ => tp2.isLambdaSub && isSubType(tp1.resultType, tp2.appliedTo(tp1.paramRefs))
614628
}
615629
}
616630
compareHKLambda
@@ -718,7 +732,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
718732
tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++
719733
tparams1.indices.toList.map(tl.paramRefs(_))))
720734
(ctx.mode.is(Mode.TypevarsMissContext) ||
721-
tryInstantiate(tycon2, tycon1.ensureHK)) &&
735+
tryInstantiate(tycon2, tycon1.ensureLambdaSub)) &&
722736
recur(tp1, tycon1.appliedTo(args2))
723737
}
724738
}
@@ -801,7 +815,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
801815
case param1: TypeParamRef =>
802816
def canInstantiate = tp2 match {
803817
case AppliedType(tycon2, args2) =>
804-
tryInstantiate(param1, tycon2.ensureHK) && isSubArgs(args1, args2, tp1, tycon2.typeParams)
818+
tryInstantiate(param1, tycon2.ensureLambdaSub) && isSubArgs(args1, args2, tp1, tycon2.typeParams)
805819
case _ =>
806820
false
807821
}
@@ -1216,8 +1230,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
12161230
if (tp1 eq tp2) tp1
12171231
else if (!tp1.exists) tp2
12181232
else if (!tp2.exists) tp1
1219-
else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp2
1220-
else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp1
1233+
else if ((tp1 isRef AnyClass) && !tp2.isLambdaSub || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp2
1234+
else if ((tp2 isRef AnyClass) && !tp1.isLambdaSub || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp1
12211235
else tp2 match { // normalize to disjunctive normal form if possible.
12221236
case OrType(tp21, tp22) =>
12231237
tp1 & tp21 | tp1 & tp22
@@ -1265,8 +1279,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
12651279
if (tp1 eq tp2) tp1
12661280
else if (!tp1.exists) tp1
12671281
else if (!tp2.exists) tp2
1268-
else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp1
1269-
else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp2
1282+
else if ((tp1 isRef AnyClass) || (tp1 isRef AnyKindClass) || (tp2 isRef NothingClass)) tp1
1283+
else if ((tp2 isRef AnyClass) || (tp2 isRef AnyKindClass) || (tp1 isRef NothingClass)) tp2
12701284
else {
12711285
val t1 = mergeIfSuper(tp1, tp2, canConstrain)
12721286
if (t1.exists) t1

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1462,7 +1462,9 @@ object messages {
14621462

14631463
case class MissingTypeParameterFor(tpe: Type)(implicit ctx: Context)
14641464
extends Message(MissingTypeParameterForID) {
1465-
val msg = hl"missing type parameter for ${tpe}"
1465+
val msg =
1466+
if (tpe.derivesFrom(defn.AnyKindClass)) hl"${tpe} cannot be used as a value type"
1467+
else hl"missing type parameter for ${tpe}"
14661468
val kind = "Syntax"
14671469
val explanation = ""
14681470
}

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
357357
// TODO: Never dealias. We currently have to dealias because
358358
// sbt main class discovery relies on the signature of the main
359359
// method being fully dealiased. See https://github.com/sbt/zinc/issues/102
360-
val tp2 = if (!tp.isHK) tp.dealiasKeepAnnots else tp
360+
val tp2 = if (!tp.isLambdaSub) tp.dealiasKeepAnnots else tp
361361
tp2 match {
362362
case NoPrefix | NoType =>
363363
Constants.emptyType

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ object Checking {
4545
*/
4646
def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = {
4747
(args, boundss).zipped.foreach { (arg, bound) =>
48-
if (!bound.isHK && arg.tpe.isHK)
48+
if (!bound.isLambdaSub && arg.tpe.isLambdaSub)
4949
// see MissingTypeParameterFor
5050
ctx.error(ex"missing type parameter(s) for $arg", arg.pos)
5151
}
@@ -657,7 +657,7 @@ trait Checking {
657657

658658
/** Check that `tpt` does not define a higher-kinded type */
659659
def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree =
660-
if (tpt.tpe.isHK && !ctx.compilationUnit.isJava) {
660+
if (tpt.tpe.isLambdaSub && !ctx.compilationUnit.isJava) {
661661
// be more lenient with missing type params in Java,
662662
// needed to make pos/java-interop/t1196 work.
663663
errorTree(tpt, MissingTypeParameterFor(tpt.tpe))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ trait TypeAssigner {
255255
*/
256256
def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
257257
var qualType = qual1.tpe.widenIfUnstable
258-
if (qualType.isHK) qualType = errorType(em"$qualType takes type parameters", qual1.pos)
258+
if (qualType.isLambdaSub) qualType = errorType(em"$qualType takes type parameters", qual1.pos)
259259
val ownType = selectionType(qualType, tree.name, tree.pos)
260260
ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.pos)
261261
}

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,24 @@ class Typer extends Namer
12321232
tparam.ensureCompleted() // This is needed to get the test `compileParSetSubset` to work
12331233
case _ =>
12341234
}
1235-
if (desugaredArg.isType) typed(desugaredArg, argPt)
1235+
if (desugaredArg.isType) {
1236+
var res = typed(desugaredArg, argPt)
1237+
arg match {
1238+
case TypeBoundsTree(EmptyTree, EmptyTree)
1239+
if tparam.paramInfo.isLambdaSub &&
1240+
tpt1.tpe.typeParamSymbols.nonEmpty &&
1241+
!ctx.mode.is(Mode.Pattern) =>
1242+
// An unbounded `_` automatically adapts to type parameter bounds. This means:
1243+
// If we have wildcard application C[_], where `C` is a class replace
1244+
// with C[_ >: L <: H] where `L` and `H` are the bounds of the corresponding
1245+
// type parameter in `C`, avoiding any referemces to parameters of `C`.
1246+
// The transform does not apply for patters, where empty bounds translate to
1247+
// wildcard identifiers `_` instead.
1248+
res = res.withType(avoid(tparam.paramInfo, tpt1.tpe.typeParamSymbols))
1249+
case _ =>
1250+
}
1251+
res
1252+
}
12361253
else desugaredArg.withType(UnspecifiedErrorType)
12371254
}
12381255
args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]]
@@ -2146,7 +2163,7 @@ class Typer extends Namer
21462163

21472164
def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = {
21482165
assert(wtp.isImplicitMethod)
2149-
val tvarsToInstantiate = tvarsInParams(tree, locked)
2166+
val tvarsToInstantiate = tvarsInParams(tree, locked).distinct
21502167
wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate))
21512168
val constr = ctx.typerState.constraint
21522169

compiler/test-resources/repl/i2492

Lines changed: 0 additions & 6 deletions
This file was deleted.

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class CompilationTests extends ParallelTesting {
102102
compileFilesInDir("tests/pos", defaultOptions) +
103103
compileFilesInDir("tests/pos-no-optimise", defaultOptions) +
104104
compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes) +
105+
compileFilesInDir("tests/pos-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") +
105106
compileDir("tests/pos/i1137-1", defaultOptions and "-Yemit-tasty") +
106107
compileFile(
107108
// succeeds despite -Xfatal-warnings because of -nowarn
@@ -175,6 +176,7 @@ class CompilationTests extends ParallelTesting {
175176
compileFilesInDir("tests/neg", defaultOptions) +
176177
compileFilesInDir("tests/neg-tailcall", defaultOptions) +
177178
compileFilesInDir("tests/neg-no-optimise", defaultOptions) +
179+
compileFilesInDir("tests/neg-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") +
178180
compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")) +
179181
compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings) +
180182
compileFile("tests/neg-custom-args/i3246.scala", scala2Mode) +

0 commit comments

Comments
 (0)