From b12ac2a1c9245de58c5c94923fc3338972d06cae Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 8 Aug 2021 12:07:34 +0200 Subject: [PATCH] Add a phase for recomputing and rechecking all types in a typed Scala program. This is useful for two reasons: = It gives us additional explanation and validation what constitutes a well-typed Scala 3 program. Recheck has less than 300 lines of code, which is a lot less than Typer and associated files. - It can be used as a basis for phases that refine the original types with new kinds of types and new rules. --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 + .../dotty/tools/dotc/config/Printers.scala | 1 + .../tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/core/NamerOps.scala | 20 ++ .../tools/dotc/transform/PreRecheck.scala | 21 ++ .../dotty/tools/dotc/transform/Recheck.scala | 329 ++++++++++++++++++ .../tools/dotc/typer/RefineTypes.overflow | 0 compiler/test/dotty/tools/TestSources.scala | 4 + .../dotty/tools/dotc/CompilationTests.scala | 13 +- .../tools/vulpix/TestConfiguration.scala | 1 + tests/neg/i6635a.scala | 19 + tests/pos/i6635.scala | 7 +- tests/pos/i6635a.scala | 14 + 13 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/PreRecheck.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/Recheck.scala create mode 100644 compiler/src/dotty/tools/dotc/typer/RefineTypes.overflow create mode 100644 tests/neg/i6635a.scala create mode 100644 tests/pos/i6635a.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index b2daaa4701dd..1f5302fe6198 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -101,6 +101,8 @@ class Compiler { new TupleOptimizations, // Optimize generic operations on tuples new LetOverApply, // Lift blocks from receivers of applications new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify. + List(new PreRecheck) :: + List(new TestRecheck) :: List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements. List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types new PureStats, // Remove pure stats from blocks diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 8e13e50e59b7..b71e1e7f188a 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -38,6 +38,7 @@ object Printers { val pickling = noPrinter val quotePickling = noPrinter val plugins = noPrinter + val recheckr = noPrinter val refcheck = noPrinter val simplify = noPrinter val staging = noPrinter diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f3a2d1f2f31f..fcd6bcb34b2e 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -238,6 +238,7 @@ private sealed trait YSettings: val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") + val Yrecheck: Setting[Boolean] = BooleanSetting("-Yrecheck", "Run type rechecks (test only)") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 9444270ccb05..a5d3e95c8f3e 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -177,4 +177,24 @@ object NamerOps: cls.registeredCompanion = modcls modcls.registeredCompanion = cls + /** For secondary constructors, make it known in the context that their type parameters + * are aliases of the class type parameters. This is done by (ab?)-using GADT constraints. + * See pos/i941.scala + */ + def linkConstructorParams(sym: Symbol)(using Context): Context = + if sym.isConstructor && !sym.isPrimaryConstructor then + sym.rawParamss match + case (tparams @ (tparam :: _)) :: _ if tparam.isType => + val rhsCtx = ctx.fresh.setFreshGADTBounds + rhsCtx.gadt.addToConstraint(tparams) + tparams.lazyZip(sym.owner.typeParams).foreach { (psym, tparam) => + val tr = tparam.typeRef + rhsCtx.gadt.addBound(psym, tr, isUpper = false) + rhsCtx.gadt.addBound(psym, tr, isUpper = true) + } + rhsCtx + case _ => + ctx + else ctx + end NamerOps diff --git a/compiler/src/dotty/tools/dotc/transform/PreRecheck.scala b/compiler/src/dotty/tools/dotc/transform/PreRecheck.scala new file mode 100644 index 000000000000..ab27bb3bb306 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PreRecheck.scala @@ -0,0 +1,21 @@ +package dotty.tools.dotc +package transform + +import core.Phases.Phase +import core.DenotTransformers.IdentityDenotTransformer +import core.Contexts.{Context, ctx} + +/** A phase that precedes the rechecker and that allows installing + * new types for local symbols. + */ +class PreRecheck extends Phase, IdentityDenotTransformer: + + def phaseName: String = "preRecheck" + + override def isEnabled(using Context) = next.isEnabled + + override def changesBaseTypes: Boolean = true + + def run(using Context): Unit = () + + override def isCheckable = false diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala new file mode 100644 index 000000000000..bedff8f62498 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -0,0 +1,329 @@ +package dotty.tools +package dotc +package transform + +import core.* +import Symbols.*, Contexts.*, Types.*, ContextOps.*, Decorators.*, SymDenotations.* +import Flags.*, SymUtils.*, NameKinds.* +import Phases.Phase +import DenotTransformers.IdentityDenotTransformer +import NamerOps.{methodType, linkConstructorParams} +import NullOpsDecorator.stripNull +import typer.ErrorReporting.err +import ast.* +import typer.ProtoTypes.* +import config.Printers.recheckr +import util.Property +import StdNames.nme +import reporting.trace + +abstract class Recheck extends Phase, IdentityDenotTransformer: + thisPhase => + + import ast.tpd.* + + def preRecheckPhase = this.prev.asInstanceOf[PreRecheck] + + override def isEnabled(using Context) = ctx.settings.Yrecheck.value + override def changesBaseTypes: Boolean = true + + override def isCheckable = false + // TODO: investigate what goes wrong we Ycheck directly after rechecking. + // One failing test is pos/i583a.scala + + def run(using Context): Unit = + val unit = ctx.compilationUnit + //println(i"recheck types of $unit") + newRechecker().check() + + def newRechecker()(using Context): Rechecker + + class Rechecker(ictx: Context): + val ta = ictx.typeAssigner + + extension (sym: Symbol) def updateInfo(newInfo: Type)(using Context): Unit = + if sym.info ne newInfo then + sym.copySymDenotation().installAfter(thisPhase) // reset + sym.copySymDenotation( + info = newInfo, + initFlags = + if newInfo.isInstanceOf[LazyType] then sym.flags &~ Touched + else sym.flags + ).installAfter(preRecheckPhase) + + /** Hook to be overridden */ + protected def reinfer(tp: Type)(using Context): Type = tp + + def reinferResult(info: Type)(using Context): Type = info match + case info: MethodOrPoly => + info.derivedLambdaType(resType = reinferResult(info.resultType)) + case _ => + reinfer(info) + + def enterDef(stat: Tree)(using Context): Unit = + val sym = stat.symbol + stat match + case stat: ValOrDefDef if stat.tpt.isInstanceOf[InferredTypeTree] => + sym.updateInfo(reinferResult(sym.info)) + case stat: Bind => + sym.updateInfo(reinferResult(sym.info)) + case _ => + + def recheckIdent(tree: Ident)(using Context): Type = + tree.tpe + + /** Keep the symbol of the `select` but re-infer its type */ + def recheckSelect(tree: Select)(using Context): Type = tree match + case Select(qual, name) => + val qualType = recheck(qual).widenIfUnstable + if name.is(OuterSelectName) then tree.tpe + else + //val pre = ta.maybeSkolemizePrefix(qualType, name) + val mbr = qualType.findMember(name, qualType, + excluded = if tree.symbol.is(Private) then EmptyFlags else Private + ).suchThat(tree.symbol ==) + qualType.select(name, mbr) + + def recheckBind(tree: Bind, pt: Type)(using Context): Type = tree match + case Bind(name, body) => + enterDef(tree) + val bodyType = recheck(body, pt) + val sym = tree.symbol + if sym.isType then sym.typeRef else sym.info + + def recheckLabeled(tree: Labeled, pt: Type)(using Context): Type = tree match + case Labeled(bind, expr) => + val bindType = recheck(bind, pt) + val exprType = recheck(expr, defn.UnitType) + bindType + + def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = + if !tree.rhs.isEmpty then recheck(tree.rhs, tree.symbol.info) + sym.termRef + + def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = + tree.paramss.foreach(_.foreach(enterDef)) + val rhsCtx = linkConstructorParams(sym) + if !tree.rhs.isEmpty && !sym.isInlineMethod && !sym.isEffectivelyErased then + recheck(tree.rhs, tree.symbol.localReturnType)(using rhsCtx) + sym.termRef + + def recheckTypeDef(tree: TypeDef, sym: Symbol)(using Context): Type = + recheck(tree.rhs) + sym.typeRef + + def recheckClassDef(tree: TypeDef, impl: Template, sym: ClassSymbol)(using Context): Type = + recheck(impl.constr) + impl.parentsOrDerived.foreach(recheck(_)) + recheck(impl.self) + recheckStats(impl.body) + sym.typeRef + + // Need to remap Object to FromJavaObject since it got lost in ElimRepeated + private def mapJavaArgs(formals: List[Type])(using Context): List[Type] = + val tm = new TypeMap: + def apply(t: Type) = t match + case t: TypeRef if t.symbol == defn.ObjectClass => defn.FromJavaObjectType + case _ => mapOver(t) + formals.mapConserve(tm) + + def recheckApply(tree: Apply, pt: Type)(using Context): Type = + recheck(tree.fun).widen match + case fntpe: MethodType => + assert(sameLength(fntpe.paramInfos, tree.args)) + val formals = + if tree.symbol.is(JavaDefined) then mapJavaArgs(fntpe.paramInfos) + else fntpe.paramInfos + def recheckArgs(args: List[Tree], formals: List[Type], prefs: List[ParamRef]): List[Type] = args match + case arg :: args1 => + val argType = recheck(arg, formals.head) + val formals1 = + if fntpe.isParamDependent + then formals.tail.map(_.substParam(prefs.head, argType)) + else formals.tail + argType :: recheckArgs(args1, formals1, prefs.tail) + case Nil => + assert(formals.isEmpty) + Nil + val argTypes = recheckArgs(tree.args, formals, fntpe.paramRefs) + fntpe.instantiate(argTypes) + + def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = + recheck(tree.fun).widen match + case fntpe: PolyType => + assert(sameLength(fntpe.paramInfos, tree.args)) + val argTypes = tree.args.map(recheck(_)) + fntpe.instantiate(argTypes) + + def recheckTyped(tree: Typed)(using Context): Type = + val tptType = recheck(tree.tpt) + recheck(tree.expr, tptType) + tptType + + def recheckAssign(tree: Assign)(using Context): Type = + val lhsType = recheck(tree.lhs) + recheck(tree.rhs, lhsType.widen) + defn.UnitType + + def recheckBlock(stats: List[Tree], expr: Tree, pt: Type)(using Context): Type = + recheckStats(stats) + val exprType = recheck(expr, pt.dropIfProto) + TypeOps.avoid(exprType, localSyms(stats).filterConserve(_.isTerm)) + + def recheckBlock(tree: Block, pt: Type)(using Context): Type = + recheckBlock(tree.stats, tree.expr, pt) + + def recheckInlined(tree: Inlined, pt: Type)(using Context): Type = + recheckBlock(tree.bindings, tree.expansion, pt) + + def recheckIf(tree: If, pt: Type)(using Context): Type = + recheck(tree.cond, defn.BooleanType) + recheck(tree.thenp, pt) | recheck(tree.elsep, pt) + + def recheckClosure(tree: Closure, pt: Type)(using Context): Type = + if tree.tpt.isEmpty then + tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined)) + else + recheck(tree.tpt) + + def recheckMatch(tree: Match, pt: Type)(using Context): Type = + val selectorType = recheck(tree.selector) + val casesTypes = tree.cases.map(recheck(_, selectorType.widen, pt)) + TypeComparer.lub(casesTypes) + + def recheck(tree: CaseDef, selType: Type, pt: Type)(using Context): Type = + recheck(tree.pat, selType) + recheck(tree.guard, defn.BooleanType) + recheck(tree.body, pt) + + def recheckReturn(tree: Return)(using Context): Type = + recheck(tree.expr, tree.from.symbol.returnProto) + defn.NothingType + + def recheckWhileDo(tree: WhileDo)(using Context): Type = + recheck(tree.cond, defn.BooleanType) + recheck(tree.body, defn.UnitType) + defn.UnitType + + def recheckTry(tree: Try, pt: Type)(using Context): Type = + val bodyType = recheck(tree.expr, pt) + val casesTypes = tree.cases.map(recheck(_, defn.ThrowableType, pt)) + val finalizerType = recheck(tree.finalizer, defn.UnitType) + TypeComparer.lub(bodyType :: casesTypes) + + def recheckSeqLiteral(tree: SeqLiteral, pt: Type)(using Context): Type = + val elemProto = pt.stripNull.elemType match + case NoType => WildcardType + case bounds: TypeBounds => WildcardType(bounds) + case elemtp => elemtp + val declaredElemType = recheck(tree.elemtpt) + val elemTypes = tree.elems.map(recheck(_, elemProto)) + TypeComparer.lub(declaredElemType :: elemTypes) + + def recheckTypeTree(tree: TypeTree)(using Context): Type = tree match + case tree: InferredTypeTree => reinfer(tree.tpe) + case _ => tree.tpe + + def recheckAnnotated(tree: Annotated)(using Context): Type = + tree.tpe match + case tp: AnnotatedType => + val argType = recheck(tree.arg) + tp.derivedAnnotatedType(argType, tp.annot) + + def recheckAlternative(tree: Alternative, pt: Type)(using Context): Type = + val altTypes = tree.trees.map(recheck(_, pt)) + TypeComparer.lub(altTypes) + + def recheckPackageDef(tree: PackageDef)(using Context): Type = + recheckStats(tree.stats) + NoType + + def recheckStats(stats: List[Tree])(using Context): Unit = + stats.foreach(enterDef) + stats.foreach(recheck(_)) + + /** Typecheck tree without adapting it, returning a recheck tree. + * @param initTree the unrecheck tree + * @param pt the expected result type + * @param locked the set of type variables of the current typer state that cannot be interpolated + * at the present time + */ + def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = trace(i"rechecking $tree, ${tree.getClass} with $pt", recheckr, show = true) { + + def recheckNamed(tree: NameTree, pt: Type)(using Context): Type = + val sym = tree.symbol + tree match + case tree: Ident => recheckIdent(tree) + case tree: Select => recheckSelect(tree) + case tree: Bind => recheckBind(tree, pt) + case tree: ValDef => + if tree.isEmpty then NoType + else recheckValDef(tree, sym)(using ctx.localContext(tree, sym)) + case tree: DefDef => + recheckDefDef(tree, sym)(using ctx.localContext(tree, sym)) + case tree: TypeDef => + tree.rhs match + case impl: Template => + recheckClassDef(tree, impl, sym.asClass)(using ctx.localContext(tree, sym)) + case _ => + recheckTypeDef(tree, sym)(using ctx.localContext(tree, sym)) + case tree: Labeled => recheckLabeled(tree, pt) + + def recheckUnnamed(tree: Tree, pt: Type): Type = tree match + case tree: Apply => recheckApply(tree, pt) + case tree: TypeApply => recheckTypeApply(tree, pt) + case _: New | _: This | _: Super | _: Literal => tree.tpe + case tree: Typed => recheckTyped(tree) + case tree: Assign => recheckAssign(tree) + case tree: Block => recheckBlock(tree, pt) + case tree: If => recheckIf(tree, pt) + case tree: Closure => recheckClosure(tree, pt) + case tree: Match => recheckMatch(tree, pt) + case tree: Return => recheckReturn(tree) + case tree: WhileDo => recheckWhileDo(tree) + case tree: Try => recheckTry(tree, pt) + case tree: SeqLiteral => recheckSeqLiteral(tree, pt) + case tree: Inlined => recheckInlined(tree, pt) + case tree: TypeTree => recheckTypeTree(tree) + case tree: Annotated => recheckAnnotated(tree) + case tree: Alternative => recheckAlternative(tree, pt) + case tree: PackageDef => recheckPackageDef(tree) + case tree: Thicket => defn.NothingType + + try + val result = tree match + case tree: NameTree => recheckNamed(tree, pt) + case tree => recheckUnnamed(tree, pt) + checkConforms(result, pt, tree) + result + catch case ex: Exception => + println(i"error while rechecking $tree") + throw ex + } + end recheck + + def checkConforms(tpe: Type, pt: Type, tree: Tree)(using Context): Unit = tree match + case _: DefTree | EmptyTree | _: TypeTree => + case _ => + val actual = tree.tpe.widenExpr + val expected = pt.widenExpr + val isCompatible = + actual <:< expected + || expected.isRepeatedParam + && actual <:< expected.translateFromRepeated(toArray = tree.tpe.isRef(defn.ArrayClass)) + if !isCompatible then + err.typeMismatch(tree, pt) + + def check()(using Context): Unit = + val unit = ictx.compilationUnit + recheck(unit.tpdTree) + + end Rechecker +end Recheck + +class TestRecheck extends Recheck: + def phaseName: String = "recheck" + //override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + def newRechecker()(using Context): Rechecker = Rechecker(ctx) + + diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.overflow b/compiler/src/dotty/tools/dotc/typer/RefineTypes.overflow new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/compiler/test/dotty/tools/TestSources.scala b/compiler/test/dotty/tools/TestSources.scala index 4fbf0e9fc5dd..c3c3937720f8 100644 --- a/compiler/test/dotty/tools/TestSources.scala +++ b/compiler/test/dotty/tools/TestSources.scala @@ -11,17 +11,21 @@ object TestSources { def posFromTastyBlacklistFile: String = "compiler/test/dotc/pos-from-tasty.blacklist" def posTestPicklingBlacklistFile: String = "compiler/test/dotc/pos-test-pickling.blacklist" + def posTestRecheckExcludesFile = "compiler/test/dotc/pos-test-recheck.exludes" def posFromTastyBlacklisted: List[String] = loadList(posFromTastyBlacklistFile) def posTestPicklingBlacklisted: List[String] = loadList(posTestPicklingBlacklistFile) + def posTestRecheckExcluded = loadList(posTestRecheckExcludesFile) // run tests lists def runFromTastyBlacklistFile: String = "compiler/test/dotc/run-from-tasty.blacklist" def runTestPicklingBlacklistFile: String = "compiler/test/dotc/run-test-pickling.blacklist" + def runTestRecheckExcludesFile = "compiler/test/dotc/run-test-recheck.exludes" def runFromTastyBlacklisted: List[String] = loadList(runFromTastyBlacklistFile) def runTestPicklingBlacklisted: List[String] = loadList(runTestPicklingBlacklistFile) + def runTestRecheckExcluded = loadList(runTestRecheckExcludesFile) // load lists diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index a88f94565e32..c4f4acc5b65a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -220,6 +220,15 @@ class CompilationTests { ).checkCompile() } + @Test def recheck: Unit = + given TestGroup = TestGroup("recheck") + aggregateTests( + compileFilesInDir("tests/new", recheckOptions), + compileFilesInDir("tests/pos", recheckOptions, FileFilter.exclude(TestSources.posTestRecheckExcluded)), + compileFilesInDir("tests/run", recheckOptions, FileFilter.exclude(TestSources.runTestRecheckExcluded)) + ).checkCompile() + + /** The purpose of this test is three-fold, being able to compile dotty * bootstrapped, and making sure that TASTY can link against a compiled * version of Dotty, and compiling the compiler using the SemanticDB generation @@ -246,7 +255,7 @@ class CompilationTests { Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm, Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader, ).mkString(File.pathSeparator), - Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb") + Array("-Ycheck-reentrant", "-Yrecheck", "-language:postfixOps", "-Xsemanticdb") ) val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-bootstrapped")) @@ -254,7 +263,7 @@ class CompilationTests { val lib = compileList("lib", librarySources, - defaultOptions.and("-Ycheck-reentrant", + defaultOptions.and("-Ycheck-reentrant", "-Yrecheck", "-language:experimental.erasedDefinitions", // support declaration of scala.compiletime.erasedValue // "-source", "future", // TODO: re-enable once we allow : @unchecked in pattern definitions. Right now, lots of narrowing pattern definitions fail. ))(libGroup) diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index caa7bc911633..f2e33ae8ff65 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -79,6 +79,7 @@ object TestConfiguration { ) val picklingWithCompilerOptions = picklingOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath) + val recheckOptions = defaultOptions.and("-Yrecheck") val scala2CompatMode = defaultOptions.and("-source", "3.0-migration") val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") diff --git a/tests/neg/i6635a.scala b/tests/neg/i6635a.scala new file mode 100644 index 000000000000..a79ea4e7c818 --- /dev/null +++ b/tests/neg/i6635a.scala @@ -0,0 +1,19 @@ +object Test { + abstract class ExprBase { s => + type A + } + + abstract class Lit extends ExprBase { s => + type A = Int + val n: A + } + + // It would be nice if the following could typecheck. We'd need to apply + // a reasoning like this: + // + // Since there is an argument `e2` of type `Lit & e1.type`, it follows that + // e1.type == e2.type Hence, e1.A == e2.A == Int. This looks similar + // to techniques used in GADTs. + // + def castTestFail2a(e1: ExprBase)(e2: Lit & e1.type)(x: e1.A): Int = x // error: Found: (x : e1.A) Required: Int +} diff --git a/tests/pos/i6635.scala b/tests/pos/i6635.scala index dacd1ef5cd8b..406eee6251e6 100644 --- a/tests/pos/i6635.scala +++ b/tests/pos/i6635.scala @@ -27,11 +27,12 @@ object Test { def castTest5a(e1: ExprBase)(e2: LitU with e1.type)(x: e2.A): e1.A = x def castTest5b(e1: ExprBase)(e2: LitL with e1.type)(x: e2.A): e1.A = x - //fail: def castTestFail1(e1: ExprBase)(e2: Lit with e1.type)(x: e2.A): e1.A = x // this is like castTest5a/b, but with Lit instead of LitU/LitL - // the other direction never works: - def castTestFail2a(e1: ExprBase)(e2: Lit with e1.type)(x: e1.A): e2.A = x + + // The next example fails rechecking. It is repeated in i6635a.scala + // def castTestFail2a(e1: ExprBase)(e2: Lit with e1.type)(x: e1.A): e2.A = x def castTestFail2b(e1: ExprBase)(e2: LitL with e1.type)(x: e1.A): e2.A = x + def castTestFail2c(e1: ExprBase)(e2: LitU with e1.type)(x: e1.A): e2.A = x // the problem isn't about order of intersections. diff --git a/tests/pos/i6635a.scala b/tests/pos/i6635a.scala new file mode 100644 index 000000000000..9454e03e3a4a --- /dev/null +++ b/tests/pos/i6635a.scala @@ -0,0 +1,14 @@ +object Test { + abstract class ExprBase { s => + type A + } + + abstract class Lit extends ExprBase { s => + type A = Int + val n: A + } + + // Fails recheck since the result type e2.A is converted to Int to avoid + // a false dependency on e2. + def castTestFail2a(e1: ExprBase)(e2: Lit with e1.type)(x: e1.A): e2.A = x +}