Skip to content

Add Matchable trait #10670

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -397,17 +397,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case TypeApply(fn, _) =>
if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of || fn.symbol == defn.Predef_classOf) Pure else exprPurity(fn)
case Apply(fn, args) =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
|| sym.owner == defn.StringClass
|| defn.pureMethods.contains(sym)
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| (fn.symbol.isStableMember && !fn.symbol.is(Lazy))) // constructors of no-inits classes are stable
if isPureApply(tree, fn) then
minOf(exprPurity(fn), args.map(exprPurity)) `min` Pure
else if (fn.symbol.is(Erased)) Pure
else if (fn.symbol.isStableMember) /* && fn.symbol.is(Lazy) */
else if fn.symbol.is(Erased) then
Pure
else if fn.symbol.isStableMember /* && fn.symbol.is(Lazy) */ then
minOf(exprPurity(fn), args.map(exprPurity)) `min` Idempotent
else Impure
else
Impure
case Typed(expr, _) =>
exprPurity(expr)
case Block(stats, expr) =>
Expand Down Expand Up @@ -440,6 +437,17 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

def isPureBinding(tree: Tree)(using Context): Boolean = statPurity(tree) >= Pure

/** Is the application `tree` with function part `fn` known to be pure?
* Function value and arguments can still be impure.
*/
def isPureApply(tree: Tree, fn: Tree)(using Context): Boolean =
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
|| sym.owner == defn.StringClass
|| defn.pureMethods.contains(sym)
tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| fn.symbol.isStableMember && !fn.symbol.is(Lazy) // constructors of no-inits classes are stable

/** The purity level of this reference.
* @return
* PurePath if reference is (nonlazy and stable)
Expand Down
25 changes: 19 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,19 @@ class Definitions {
*/
@tu lazy val AnyClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Any, Abstract, Nil), ensureCtor = false)
def AnyType: TypeRef = AnyClass.typeRef
@tu lazy val AnyValClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyVal, Abstract, List(AnyClass.typeRef)))
@tu lazy val MatchableClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Matchable, Trait, AnyType :: Nil), ensureCtor = false)
def MatchableType: TypeRef = MatchableClass.typeRef
@tu lazy val AnyValClass: ClassSymbol =
val res = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyVal, Abstract, List(AnyType, MatchableType)))
// Mark companion as absent, so that class does not get re-completed
val companion = ScalaPackageVal.info.decl(nme.AnyVal).symbol
companion.moduleClass.markAbsent()
companion.markAbsent()
res

def AnyValType: TypeRef = AnyValClass.typeRef

@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
@tu lazy val Any_!= : TermSymbol = enterMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
@tu lazy val Any_equals: TermSymbol = enterMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
@tu lazy val Any_hashCode: TermSymbol = enterMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType))
Expand All @@ -288,7 +297,7 @@ class Definitions {
@tu lazy val ObjectClass: ClassSymbol = {
val cls = requiredClass("java.lang.Object")
assert(!cls.isCompleted, "race for completing java.lang.Object")
cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope)
cls.info = ClassInfo(cls.owner.thisType, cls, List(AnyType, MatchableType), newScope)
cls.setFlag(NoInits | JavaDefined)

// The companion object doesn't really exist, so it needs to be marked as
Expand Down Expand Up @@ -444,7 +453,7 @@ class Definitions {
MethodType(List(ThrowableType), NothingType))

@tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef))
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyType))
def NothingType: TypeRef = NothingClass.typeRef
@tu lazy val NullClass: ClassSymbol = {
val parent = if (ctx.explicitNulls) AnyType else ObjectType
Expand Down Expand Up @@ -520,7 +529,7 @@ class Definitions {
// but does not define it as an explicit class.
enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final,
List(AnyClass.typeRef), EmptyScope)
List(AnyType), EmptyScope)
@tu lazy val SingletonType: TypeRef = SingletonClass.typeRef

@tu lazy val CollectionSeqType: TypeRef = requiredClassRef("scala.collection.Seq")
Expand Down Expand Up @@ -1144,6 +1153,8 @@ class Definitions {

// ----- Symbol sets ---------------------------------------------------

@tu lazy val topClasses: Set[Symbol] = Set(AnyClass, MatchableClass, ObjectClass, AnyValClass)

@tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0)
val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass))
def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n)
Expand Down Expand Up @@ -1372,7 +1383,7 @@ class Definitions {
@tu lazy val ShadowableImportNames: Set[TermName] = Set("Predef".toTermName)

/** Class symbols for which no class exist at runtime */
@tu lazy val NotRuntimeClasses: Set[Symbol] = Set(AnyClass, AnyValClass, NullClass, NothingClass)
@tu lazy val NotRuntimeClasses: Set[Symbol] = Set(AnyClass, MatchableClass, AnyValClass, NullClass, NothingClass)

@tu lazy val SpecialClassTagClasses: Set[Symbol] = Set(UnitClass, AnyClass, AnyValClass)

Expand Down Expand Up @@ -1672,6 +1683,7 @@ class Definitions {
@tu lazy val specialErasure: SimpleIdentityMap[Symbol, ClassSymbol] =
SimpleIdentityMap.empty[Symbol]
.updated(AnyClass, ObjectClass)
.updated(MatchableClass, ObjectClass)
.updated(AnyValClass, ObjectClass)
.updated(SingletonClass, ObjectClass)
.updated(TupleClass, ProductClass)
Expand All @@ -1683,6 +1695,7 @@ class Definitions {
@tu lazy val syntheticScalaClasses: List[TypeSymbol] = {
val synth = List(
AnyClass,
MatchableClass,
AnyRefAlias,
AnyKindClass,
andType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
/** If `inst` is a TypeBounds, make sure it does not contain toplevel
* references to `param` (see `Constraint#occursAtToplevel` for a definition
* of "toplevel").
* Any such references are replace by `Nothing` in the lower bound and `Any`
* Any such references are replaced by `Nothing` in the lower bound and `Any`
* in the upper bound.
* References can be direct or indirect through instantiations of other
* parameters in the constraint.
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ object StdNames {
val Import: N = "Import"
val Literal: N = "Literal"
val LiteralAnnotArg: N = "LiteralAnnotArg"
val Matchable: N = "Matchable"
val MatchCase: N = "MatchCase"
val MirroredElemTypes: N = "MirroredElemTypes"
val MirroredElemLabels: N = "MirroredElemLabels"
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2246,9 +2246,7 @@ object SymDenotations {
if (pcls.isCompleting) recur(pobjs1, acc)
else
val pobjMembers = pcls.nonPrivateMembersNamed(name).filterWithPredicate { d =>
// Drop members of `Any` and `Object`
val owner = d.symbol.maybeOwner
(owner ne defn.AnyClass) && (owner ne defn.ObjectClass)
!defn.topClasses.contains(d.symbol.maybeOwner) // Drop members of top classes
}
recur(pobjs1, acc.union(pobjMembers))
case nil =>
Expand Down
22 changes: 18 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ object Types {
def isAnyRef(using Context): Boolean = isRef(defn.ObjectClass, skipRefined = false)
def isAnyKind(using Context): Boolean = isRef(defn.AnyKindClass, skipRefined = false)

def isTopType(using Context): Boolean = dealias match
case tp: TypeRef => defn.topClasses.contains(tp.symbol)
case _ => false

/** Is this type exactly Nothing (no vars, aliases, refinements etc allowed)? */
def isExactlyNothing(using Context): Boolean = this match {
case tp: TypeRef =>
Expand Down Expand Up @@ -512,6 +516,20 @@ object Types {
case _ =>
false

/** Same as hasClassSmbol(MatchableClass), except that we also follow the constraint
* bounds of type variables in the constraint.
*/
def isMatchableBound(using Context): Boolean = dealias match
case tp: TypeRef => tp.symbol == defn.MatchableClass
case tp: TypeParamRef =>
ctx.typerState.constraint.entry(tp) match
case bounds: TypeBounds => bounds.hi.isMatchableBound
case _ => false
case tp: TypeProxy => tp.underlying.isMatchableBound
case tp: AndType => tp.tp1.isMatchableBound || tp.tp2.isMatchableBound
case tp: OrType => tp.tp1.isMatchableBound && tp.tp2.isMatchableBound
case _ => false

/** The term symbol associated with the type */
@tailrec final def termSymbol(using Context): Symbol = this match {
case tp: TermRef => tp.symbol
Expand Down Expand Up @@ -3812,10 +3830,6 @@ object Types {

def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] =
Some((tl.typeParams, tl.resType))

def any(n: Int)(using Context): PolyType =
apply(syntheticParamNames(n))(
pt => List.fill(n)(TypeBounds.empty), pt => defn.AnyType)
}

private object DepStatus {
Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,7 @@ import transform.SymUtils._
val postScript = addenda.find(!_.isEmpty) match
case Some(p) => p
case None =>
if expected.isAny
|| expected.isAnyRef
|| expected.isRef(defn.AnyValClass)
|| found.isBottomType
if expected.isTopType || found.isBottomType
then ""
else ctx.typer.importSuggestionAddendum(ViewProto(found.widen, expected))
val (where, printCtx) = Formatting.disambiguateTypes(found2, expected2)
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class Erasure extends Phase with DenotTransformer {
// After erasure, all former Any members are now Object members
val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info
val extendedScope = decls.cloneScope
for (decl <- defn.AnyClass.classInfo.decls)
if (!decl.isConstructor) extendedScope.enter(decl)
for decl <- defn.AnyClass.classInfo.decls do
if !decl.isConstructor then extendedScope.enter(decl)
ref.copySymDenotation(
info = transformInfo(ref.symbol,
ClassInfo(pre, defn.ObjectClass, ps, extendedScope, selfInfo))
Expand All @@ -71,11 +71,12 @@ class Erasure extends Phase with DenotTransformer {
else {
val oldSymbol = ref.symbol
val newSymbol =
if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor)
if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor) then
//assert(false)
defn.ObjectClass.primaryConstructor
else oldSymbol
val oldOwner = ref.owner
val newOwner = if (oldOwner eq defn.AnyClass) defn.ObjectClass else oldOwner
val newOwner = if oldOwner == defn.AnyClass then defn.ObjectClass else oldOwner
val oldName = ref.name
val newName = ref.targetName
val oldInfo = ref.info
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ trait Applications extends Compatibility {
def typedUnApply(tree: untpd.Apply, selType: Type)(using Context): Tree = {
record("typedUnApply")
val Apply(qual, args) = tree
checkMatchable(selType, tree.srcPos, pattern = true)

def notAnExtractor(tree: Tree): Tree =
// prefer inner errors
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,13 @@ trait Checking {
if preExisting.exists || seen.contains(tname) then
report.error(em"@targetName annotation ${'"'}$tname${'"'} clashes with other definition in same scope", stat.srcPos)
if stat.isDef then seen += tname

def checkMatchable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit =
if !tp.derivesFrom(defn.MatchableClass) && sourceVersion.isAtLeast(`3.1-migration`) then
val kind = if pattern then "pattern selector" else "value"
report.warning(
em"""${kind} should be an instance of Matchable,
|but it has unmatchable type $tp instead""", pos)
}

trait ReChecking extends Checking {
Expand All @@ -1256,6 +1263,7 @@ trait ReChecking extends Checking {
override def checkFullyAppliedType(tree: Tree)(using Context): Unit = ()
override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(using Context): Unit = ()
override def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = true
override def checkMatchable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit = ()
}

trait NoChecking extends ReChecking {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,8 @@ trait Implicits:
case TypeBounds(lo, hi) if lo.ne(hi) && !t.symbol.is(Opaque) => apply(hi)
case _ => t
}
case t: SingletonType =>
apply(t.widen)
case t: RefinedType =>
apply(t.parent)
case _ =>
Expand Down
7 changes: 1 addition & 6 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
case TypeApply(fn, _) =>
if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn)
case Apply(fn, args) =>
def isKnownPureOp(sym: Symbol) =
sym.owner.isPrimitiveValueClass
|| sym.owner == defn.StringClass
|| defn.pureMethods.contains(sym)
val isCaseClassApply = {
val cls = tree.tpe.classSymbol
val meth = fn.symbol
Expand All @@ -557,8 +553,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
meth.owner.linkedClass.is(Case) &&
cls.isNoInitsRealClass
}
if (tree.tpe.isInstanceOf[ConstantType] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| (fn.symbol.isStableMember && !fn.symbol.is(Lazy))) // constructors of no-inits classes are stable
if isPureApply(tree, fn) then
apply(fn) && args.forall(apply)
else if (isCaseClassApply)
args.forall(apply)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
val arg :: Nil = args
val t = arg.tpe & tp2
If(
arg.select(defn.Any_isInstanceOf).appliedToType(tp2),
arg.isInstance(tp2),
ref(defn.SomeClass.companionModule.termRef).select(nme.apply)
.appliedToType(t)
.appliedTo(arg.select(nme.asInstanceOf_).appliedToType(t)),
Expand Down
23 changes: 19 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,10 @@ class Typer extends Namer
case templ: untpd.Template =>
import untpd._
var templ1 = templ
def isEligible(tp: Type) = tp.exists && !tp.typeSymbol.is(Final) && !tp.isRef(defn.AnyClass)
def isEligible(tp: Type) =
tp.exists
&& !tp.typeSymbol.is(Final)
&& (!tp.isTopType || tp.isAnyRef) // Object is the only toplevel class that can be instantiated
if (templ1.parents.isEmpty &&
isFullyDefined(pt, ForceDegree.flipBottom) &&
isSkolemFree(pt) &&
Expand Down Expand Up @@ -768,12 +771,17 @@ class Typer extends Namer
def typedTpt = checkSimpleKinded(typedType(tree.tpt))
def handlePattern: Tree = {
val tpt1 = typedTpt
if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef)
if !ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef then
withMode(Mode.GadtConstraintInference) {
TypeComparer.constrainPatternType(tpt1.tpe, pt)
}
val matched = ascription(tpt1, isWildcard = true)
// special case for an abstract type that comes with a class tag
tryWithTypeTest(ascription(tpt1, isWildcard = true), pt)
val result = tryWithTypeTest(matched, pt)
if (result eq matched) && pt != defn.ImplicitScrutineeTypeRef then
// no check for matchability if TestTest was applied
checkMatchable(pt, tree.srcPos, pattern = true)
result
}
cases(
ifPat = handlePattern,
Expand Down Expand Up @@ -1320,7 +1328,7 @@ class Typer extends Namer
typed(desugar.makeCaseLambda(tree.cases, checkMode, protoFormals.length).withSpan(tree.span), pt)
}
case _ =>
if (tree.isInline) checkInInlineContext("inline match", tree.srcPos)
if tree.isInline then checkInInlineContext("inline match", tree.srcPos)
val sel1 = typedExpr(tree.selector)
val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.span).widen

Expand Down Expand Up @@ -3527,6 +3535,13 @@ class Typer extends Namer
case _ =>
}

// try an Any -> Matchable conversion
if pt.isMatchableBound && !wtp.derivesFrom(defn.MatchableClass) then
checkMatchable(wtp, tree.srcPos, pattern = false)
val target = AndType(tree.tpe.widenExpr, defn.MatchableType)
if target <:< pt then
return readapt(tree.cast(target))

// try an implicit conversion
val prevConstraint = ctx.typerState.constraint
def recover(failure: SearchFailureType) =
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/repl/ReplDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,7 @@ class ReplDriver(settings: Array[String],
info.bounds.hi.finalResultType
.membersBasedOnFlags(required = Method, excluded = Accessor | ParamAccessor | Synthetic | Private)
.filterNot { denot =>
denot.symbol.owner == defn.AnyClass ||
denot.symbol.owner == defn.ObjectClass ||
denot.symbol.isConstructor
defn.topClasses.contains(denot.symbol.owner) || denot.symbol.isConstructor
}

val vals =
Expand Down
1 change: 1 addition & 0 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2534,6 +2534,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
def ScalaPackage: Symbol = dotc.core.Symbols.defn.ScalaPackageVal
def ScalaPackageClass: Symbol = dotc.core.Symbols.defn.ScalaPackageClass
def AnyClass: Symbol = dotc.core.Symbols.defn.AnyClass
def MatchableClass: Symbol = dotc.core.Symbols.defn.MatchableClass
def AnyValClass: Symbol = dotc.core.Symbols.defn.AnyValClass
def ObjectClass: Symbol = dotc.core.Symbols.defn.ObjectClass
def AnyRefClass: Symbol = dotc.core.Symbols.defn.AnyRefAlias
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting {
compileFilesInDir("tests/run-custom-args/tasty-inspector", withTastyInspectorOptions)
)
val tests =
if (scala.util.Properties.isWin) basicTests
if scala.util.Properties.isWin then basicTests
else compileDir("tests/run-custom-args/tasty-interpreter", withTastyInspectorOptions) :: basicTests

aggregateTests(tests: _*).checkRuns()
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ class CompilationTests {
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-Yerased-terms")),
compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")),
compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "3.1")),
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "3.1"))
).checkExpectedErrors()
}

Expand Down
Loading