From 3c9f377340f0e5665d871fc66cd75286abb3bc66 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 5 Feb 2020 11:55:12 +0100 Subject: [PATCH 01/31] Fix #8203: handle intersection type in parent registration An enum value may have the type `A & B`, in such cases we need to register for both `A` and `B`. --- .../src/dotty/tools/dotc/typer/Namer.scala | 31 +++++++++---------- tests/patmat/i8203.scala | 19 ++++++++++++ 2 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 tests/patmat/i8203.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a57c5b39a244..cba13afd5186 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -901,28 +901,25 @@ class Namer { typer: Typer => def registerIfChild(denot: SymDenotation)(implicit ctx: Context): Unit = { val sym = denot.symbol - def register(child: Symbol, parent: Type) = { - val cls = parent.classSymbol - if (cls.is(Sealed)) - if ((child.isInaccessibleChildOf(cls) || child.isAnonymousClass) && !sym.hasAnonymousChild) - addChild(cls, cls) - else if (!cls.is(ChildrenQueried)) - addChild(cls, child) + def register(child: Symbol, parentCls: ClassSymbol) = { + if (parentCls.is(Sealed)) + if ((child.isInaccessibleChildOf(parentCls) || child.isAnonymousClass) && !sym.hasAnonymousChild) + addChild(parentCls, parentCls) + else if (!parentCls.is(ChildrenQueried)) + addChild(parentCls, child) else - ctx.error(em"""children of $cls were already queried before $sym was discovered. - |As a remedy, you could move $sym on the same nesting level as $cls.""", + ctx.error(em"""children of $parentCls were already queried before $sym was discovered. + |As a remedy, you could move $sym on the same nesting level as $parentCls.""", child.sourcePos) } - if (denot.isClass && !sym.isEnumAnonymClass && !sym.isRefinementClass) - denot.asClass.classParents.foreach { parent => - val child = if (denot.is(Module)) denot.sourceModule else denot.symbol - register(child, parent) - } - else if (denot.is(CaseVal, butNot = Method | Module)) { + if denot.isClass && !sym.isEnumAnonymClass && !sym.isRefinementClass then + val child = if (denot.is(Module)) denot.sourceModule else denot.symbol + denot.asClass.classParents.foreach { parent => register(child, parent.classSymbol.asClass) } + else if denot.is(CaseVal, butNot = Method | Module) then assert(denot.is(Enum), denot) - register(denot.symbol, denot.info) - } + denot.info.classSymbols.foreach { parent => register(denot.symbol, parent) } + end if } /** Intentionally left without `implicit ctx` parameter. We need diff --git a/tests/patmat/i8203.scala b/tests/patmat/i8203.scala new file mode 100644 index 000000000000..484ad2723454 --- /dev/null +++ b/tests/patmat/i8203.scala @@ -0,0 +1,19 @@ +sealed trait Pretty { self: Color => } +sealed trait Dull { self: Color => } +enum Color { + case Pink extends Color with Pretty + case Red extends Color with Dull +} + +def describe(c: Color) = c match { + case Color.Pink => "Amazing!" + case Color.Red => "Yawn..." +} + +def describe2(c: Pretty) = c match { + case Color.Pink => "Amazing!" +} + +def describe3(c: Dull) = c match { + case Color.Red => "Yawn..." +} From 3a1f9f6efe343e00beb9d47667a1170aba0ddfcd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 Feb 2020 17:29:32 +0100 Subject: [PATCH 02/31] Fix #7597: Refine checks whether a deferred term member is implemented The check for a concrete class used to be simply that its `abstractTermMembers` are empty. However, i7597.scala shows that this is not enough. The problem is that abstractTermMembers is based on signatures. It will not include a member as long as there is a concrete member with the same signature. But as #7597 shows it's possible for a concrete member to have the same signature as an abstract one but still not have a matching type. --- .../tools/dotc/core/SymDenotations.scala | 2 +- .../tools/dotc/typer/ImportSuggestions.scala | 4 +- .../dotty/tools/dotc/typer/RefChecks.scala | 60 ++++++++++++------- .../neg/override-java-object-arg.scala | 4 +- .../erased/erased-case-class.scala | 2 +- tests/neg/i7597.scala | 14 +++++ 6 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 tests/neg/i7597.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index fe4fd0aed96c..9334aba928c1 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2377,7 +2377,7 @@ object SymDenotations { /** is the cache valid in current run at given phase? */ def isValidAt(phase: Phase)(implicit ctx: Context): Boolean - /** Render invalid this cache and all cache that depend on it */ + /** Render invalid this cache and all caches that depend on it */ def invalidate(): Unit } diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 7dc92b170e2f..5059b7e474ed 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -6,7 +6,7 @@ import core._ import Contexts._, Types._, Symbols._, Names._, Decorators._, ProtoTypes._ import Flags._ import NameKinds.FlatName -import config.Printers.implicits +import config.Printers.{implicits, implicitsDetailed} import util.Spans.Span import ast.{untpd, tpd} import Implicits.{hasExtMethod, Candidate} @@ -94,7 +94,7 @@ trait ImportSuggestions: def rootsIn(ref: TermRef)(using Context): List[TermRef] = if seen.contains(ref) then Nil else - implicits.println(i"search for suggestions in ${ref.symbol.fullName}") + implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref ref :: rootsStrictlyIn(ref) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index a5315400efb1..fde38930f66b 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -169,7 +169,7 @@ object RefChecks { * TODO This still needs to be cleaned up; the current version is a straight port of what was there * before, but it looks too complicated and method bodies are far too large. */ - private def checkAllOverrides(clazz: Symbol)(implicit ctx: Context): Unit = { + private def checkAllOverrides(clazz: ClassSymbol)(implicit ctx: Context): Unit = { val self = clazz.thisType val upwardsSelf = upwardsThisType(clazz) var hasErrors = false @@ -470,33 +470,52 @@ object RefChecks { } } - def ignoreDeferred(member: SingleDenotation) = - member.isType || { - val mbr = member.symbol - mbr.isSuperAccessor || // not yet synthesized - ShortcutImplicits.isImplicitShortcut(mbr) || // only synthesized when referenced, see Note in ShortcutImplicits - mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr) - } + def ignoreDeferred(mbr: Symbol) = + mbr.isType + || mbr.isSuperAccessor // not yet synthesized + || ShortcutImplicits.isImplicitShortcut(mbr) // only synthesized when referenced, see Note in ShortcutImplicits + || mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr) + + def isImplemented(mbr: Symbol) = + val mbrType = clazz.thisType.memberInfo(mbr) + def (sym: Symbol).isConcrete = sym.exists && !sym.is(Deferred) + clazz.nonPrivateMembersNamed(mbr.name) + .filterWithPredicate( + impl => impl.symbol.isConcrete && mbrType.matchesLoosely(impl.info)) + .exists + + /** The term symbols in this class and its baseclasses that are + * abstract in this class. We can't use memberNames for that since + * a concrete member might have the same signature as an abstract + * member in a base class, yet might not override it. + */ + def missingTermSymbols: List[Symbol] = + val buf = new mutable.ListBuffer[Symbol] + for bc <- clazz.baseClasses + sym <- bc.info.decls.toList + if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) + do buf += sym + buf.toList // 2. Check that only abstract classes have deferred members def checkNoAbstractMembers(): Unit = { // Avoid spurious duplicates: first gather any missing members. - val missing = clazz.thisType.abstractTermMembers.filterNot(ignoreDeferred) + val missing = missingTermSymbols // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. - val grouped = missing.groupBy(_.symbol.underlyingSymbol.name) + val grouped = missing.groupBy(_.underlyingSymbol.name) val missingMethods = grouped.toList flatMap { case (name, syms) => - val withoutSetters = syms filterNot (_.symbol.isSetter) + val withoutSetters = syms filterNot (_.isSetter) if (withoutSetters.nonEmpty) withoutSetters else syms } def stubImplementations: List[String] = { // Grouping missing methods by the declaring class - val regrouped = missingMethods.groupBy(_.symbol.owner).toList - def membersStrings(members: List[SingleDenotation]) = - members.sortBy(_.symbol.name.toString).map(_.showDcl + " = ???") + val regrouped = missingMethods.groupBy(_.owner).toList + def membersStrings(members: List[Symbol]) = + members.sortBy(_.name.toString).map(_.showDcl + " = ???") if (regrouped.tail.isEmpty) membersStrings(regrouped.head._2) @@ -520,10 +539,9 @@ object RefChecks { } for (member <- missing) { - val memberSym = member.symbol def undefined(msg: String) = abstractClassError(false, s"${member.showDcl} is not defined $msg") - val underlying = memberSym.underlyingSymbol + val underlying = member.underlyingSymbol // Give a specific error message for abstract vars based on why it fails: // It could be unimplemented, have only one accessor, or be uninitialized. @@ -531,11 +549,11 @@ object RefChecks { val isMultiple = grouped.getOrElse(underlying.name(ctx), Nil).size > 1 // If both getter and setter are missing, squelch the setter error. - if (memberSym.isSetter && isMultiple) () + if (member.isSetter && isMultiple) () else undefined( - if (memberSym.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" - else if (memberSym.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" - else err.abstractVarMessage(memberSym)) + if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" + else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" + else err.abstractVarMessage(member)) } else if (underlying.is(Method)) { // If there is a concrete method whose name matches the unimplemented @@ -961,7 +979,7 @@ class RefChecks extends MiniPhase { thisPhase => } override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = try { - val cls = ctx.owner + val cls = ctx.owner.asClass checkOverloadedRestrictions(cls) checkParents(cls) if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _)) diff --git a/tests/explicit-nulls/neg/override-java-object-arg.scala b/tests/explicit-nulls/neg/override-java-object-arg.scala index 7a45cb1d6199..ccce4af660c7 100644 --- a/tests/explicit-nulls/neg/override-java-object-arg.scala +++ b/tests/explicit-nulls/neg/override-java-object-arg.scala @@ -7,7 +7,7 @@ import javax.management.{Notification, NotificationEmitter, NotificationListener class Foo { def bar(): Unit = { - val listener = new NotificationListener() { + val listener = new NotificationListener() { // error: object creation impossible override def handleNotification(n: Notification|Null, emitter: Object): Unit = { // error: method handleNotification overrides nothing } } @@ -17,7 +17,7 @@ class Foo { } } - val listener3 = new NotificationListener() { + val listener3 = new NotificationListener() { // error: object creation impossible override def handleNotification(n: Notification, emitter: Object|Null): Unit = { // error: method handleNotification overrides nothing } } diff --git a/tests/neg-custom-args/erased/erased-case-class.scala b/tests/neg-custom-args/erased/erased-case-class.scala index 692534d772b6..d23a09ba24f0 100644 --- a/tests/neg-custom-args/erased/erased-case-class.scala +++ b/tests/neg-custom-args/erased/erased-case-class.scala @@ -1 +1 @@ -case class Foo1(erased x: Int) // error // error +case class Foo1(erased x: Int) // error diff --git a/tests/neg/i7597.scala b/tests/neg/i7597.scala new file mode 100644 index 000000000000..c4c8f189fcfc --- /dev/null +++ b/tests/neg/i7597.scala @@ -0,0 +1,14 @@ +object Test extends App { + def foo[S <: String]: String => Int = + new (String => Int) { def apply(s: S): Int = 0 } // error + + trait Fn[A, B] { + def apply(x: A): B + } + + class C[S <: String] extends Fn[String, Int] { // error + def apply(s: S): Int = 0 + } + + foo("") +} From 4ae65225f356f53d21f846076cef0066f8685823 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 18 Feb 2020 19:09:22 +0100 Subject: [PATCH 03/31] Fix #8333: Check for duplicate symbols in exports Check for duplicate symbols when creating export forwarders. --- .../src/dotty/tools/dotc/typer/Namer.scala | 104 +++++++++--------- tests/neg/i8333.scala | 13 +++ 2 files changed, 66 insertions(+), 51 deletions(-) create mode 100644 tests/neg/i8333.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e4fb2d353f90..d02b520d4143 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -265,6 +265,49 @@ class Namer { typer: Typer => sym } + /** Check that a new definition with given name and privacy status + * in current context would not conflict with existing currently + * compiled definitions. + * The logic here is very subtle and fragile due to the fact that + * we are not allowed to force anything. + */ + def checkNoConflict(name: Name, isPrivate: Boolean, span: Span)(using ctx: Context): Name = + val owner = ctx.owner + var conflictsDetected = false + + def conflict(conflicting: Symbol) = + val where: String = + if conflicting.owner == owner then "" + else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}" + else i" in ${conflicting.owner}" + ctx.error(i"$name is already defined as $conflicting$where", ctx.source.atSpan(span)) + conflictsDetected = true + + def checkNoConflictIn(owner: Symbol) = + val preExisting = owner.unforcedDecls.lookup(name) + if (preExisting.isDefinedInCurrentRun || preExisting.lastKnownDenotation.is(Package)) + && (!preExisting.lastKnownDenotation.is(Private) || preExisting.owner.is(Package)) + then conflict(preExisting) + + def pkgObjs(pkg: Symbol) = + pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol) + + if owner.is(PackageClass) then + checkNoConflictIn(owner) + for pkgObj <- pkgObjs(owner) do + checkNoConflictIn(pkgObj) + else + def preExisting = ctx.effectiveScope.lookup(name) + if (!owner.isClass || name.isTypeName) && preExisting.exists then + conflict(preExisting) + else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then + checkNoConflictIn(owner.owner) + for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do + checkNoConflictIn(pkgObj) + + if conflictsDetected then name.freshened else name + end checkNoConflict + /** If this tree is a member def or an import, create a symbol of it * and store in symOfTree map. */ @@ -302,49 +345,6 @@ class Namer { typer: Typer => typr.println(i"creating symbol for $tree in ${ctx.mode}") - /** Check that a new definition with given name and privacy status - * in current context would not conflict with existing currently - * compiled definitions. - * The logic here is very subtle and fragile due to the fact that - * we are not allowed to force anything. - */ - def checkNoConflict(name: Name, isPrivate: Boolean): Name = - val owner = ctx.owner - var conflictsDetected = false - - def conflict(conflicting: Symbol) = - val where: String = - if conflicting.owner == owner then "" - else if conflicting.owner.isPackageObject then i" in ${conflicting.associatedFile}" - else i" in ${conflicting.owner}" - ctx.error(i"$name is already defined as $conflicting$where", tree.sourcePos) - conflictsDetected = true - - def checkNoConflictIn(owner: Symbol) = - val preExisting = owner.unforcedDecls.lookup(name) - if (preExisting.isDefinedInCurrentRun || preExisting.lastKnownDenotation.is(Package)) - && (!preExisting.lastKnownDenotation.is(Private) || preExisting.owner.is(Package)) - then conflict(preExisting) - - def pkgObjs(pkg: Symbol) = - pkg.denot.asInstanceOf[PackageClassDenotation].packageObjs.map(_.symbol) - - if owner.is(PackageClass) then - checkNoConflictIn(owner) - for pkgObj <- pkgObjs(owner) do - checkNoConflictIn(pkgObj) - else - def preExisting = ctx.effectiveScope.lookup(name) - if (!owner.isClass || name.isTypeName) && preExisting.exists then - conflict(preExisting) - else if owner.isPackageObject && !isPrivate && name != nme.CONSTRUCTOR then - checkNoConflictIn(owner.owner) - for pkgObj <- pkgObjs(owner.owner) if pkgObj != owner do - checkNoConflictIn(pkgObj) - - if conflictsDetected then name.freshened else name - end checkNoConflict - /** Create new symbol or redefine existing symbol under lateCompile. */ def createOrRefine[S <: Symbol]( tree: MemberDef, name: Name, flags: FlagSet, owner: Symbol, infoFn: S => Type, @@ -376,7 +376,7 @@ class Namer { typer: Typer => tree match { case tree: TypeDef if tree.isClassDef => val flags = checkFlags(tree.mods.flags &~ GivenOrImplicit) - val name = checkNoConflict(tree.name, flags.is(Private)).asTypeName + val name = checkNoConflict(tree.name, flags.is(Private), tree.span).asTypeName val cls = createOrRefine[ClassSymbol](tree, name, flags, ctx.owner, cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree), @@ -385,7 +385,7 @@ class Namer { typer: Typer => cls case tree: MemberDef => var flags = checkFlags(tree.mods.flags) - val name = checkNoConflict(tree.name, flags.is(Private)) + val name = checkNoConflict(tree.name, flags.is(Private), tree.span) tree match case tree: ValOrDefDef => if tree.unforcedRhs == EmptyTree @@ -1079,9 +1079,10 @@ class Namer { typer: Typer => if (whyNoForwarder(mbr) == "") { val sym = mbr.symbol val forwarder = - if (mbr.isType) + if mbr.isType then + val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span) ctx.newSymbol( - cls, alias.toTypeName, + cls, forwarderName, Exported | Final, TypeAlias(path.tpe.select(sym)), coord = span) @@ -1096,7 +1097,8 @@ class Namer { typer: Typer => else (EmptyFlags, mbr.info.ensureMethodic) val mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags - ctx.newSymbol(cls, alias, mbrFlags, mbrInfo, coord = span) + val forwarderName = checkNoConflict(alias, isPrivate = false, span) + ctx.newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) } forwarder.info = avoidPrivateLeaks(forwarder) val forwarderDef = @@ -1146,9 +1148,9 @@ class Namer { typer: Typer => forwarders } - val forwarderss = - for (exp @ Export(_, _) <- rest) yield exportForwarders(exp) - forwarderss.foreach(_.foreach(fwdr => fwdr.symbol.entered)) + for case exp @ Export(_, _) <- rest do + for forwarder <- exportForwarders(exp) do + forwarder.symbol.entered } /** Ensure constructor is completed so that any parameter accessors diff --git a/tests/neg/i8333.scala b/tests/neg/i8333.scala new file mode 100644 index 000000000000..12a35ac2d636 --- /dev/null +++ b/tests/neg/i8333.scala @@ -0,0 +1,13 @@ +class A: + type T = Int // can also be class T +class B(x: A, y: A): + export x._ + export y._ // error: duplicate +class C(x: A): + type T = String + export x._ // error: duplicate +class D(x: A): + export x._ // error: duplicate + type T = String + + From dc8a4d8f580bf70beef30385d80e6e4e9afeaae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Micheloud?= Date: Sat, 22 Feb 2020 15:05:28 +0100 Subject: [PATCH 04/31] Fix #8355: REPL tests : fix for two tests failing on Windows --- .../test/dotty/tools/repl/LoadTests.scala | 18 +++++++++++++---- .../dotty/tools/repl/ReplCompilerTests.scala | 20 +++++++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/compiler/test/dotty/tools/repl/LoadTests.scala b/compiler/test/dotty/tools/repl/LoadTests.scala index a94d94183f11..4770fc84f0b8 100644 --- a/compiler/test/dotty/tools/repl/LoadTests.scala +++ b/compiler/test/dotty/tools/repl/LoadTests.scala @@ -1,9 +1,10 @@ package dotty.tools.repl -import java.nio.file.{ Path, Files } +import java.nio.file.{Path, Files} import java.util.Comparator +import java.util.regex.Pattern -import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.{Test, BeforeClass, AfterClass} import org.junit.Assert.assertEquals class LoadTests extends ReplTest { @@ -55,9 +56,9 @@ class LoadTests extends ReplTest { def loadTest(file: String, defs: String, runCode: String, output: String) = eval(s":load ${writeFile(file)}").andThen { implicit s => - assertEquals(defs, storedOutput()) + assertMultiLineEquals(defs, storedOutput()) run(runCode) - assertEquals(output, storedOutput()) + assertMultiLineEquals(output, storedOutput()) } private def eval(code: String): State = @@ -82,4 +83,13 @@ object LoadTests { file } + private val pattern = Pattern.compile("\\r[\\n]?|\\n"); + + // Ensure 'expected' and 'actual' contain the same line separator(s). + private def assertMultiLineEquals(expected: String, actual: String): Unit = { + val expected0 = pattern.matcher(expected).replaceAll(System.lineSeparator) + val actual0 = pattern.matcher(actual).replaceAll(System.lineSeparator) + assertEquals(expected0, actual0) + } + } diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index e738d38a074a..a6c63ef787a6 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -1,9 +1,12 @@ package dotty.tools.repl +import java.util.regex.Pattern + import org.junit.Assert.{assertTrue => assert, _} import org.junit.{Ignore, Test} class ReplCompilerTests extends ReplTest { + import ReplCompilerTests._ private def lines() = storedOutput().trim.linesIterator.toList @@ -157,7 +160,7 @@ class ReplCompilerTests extends ReplTest { |} """.stripMargin) } .andThen { implicit state => - assertEquals( + assertMultiLineEquals( """// defined trait Ord |// defined object IntOrd""".stripMargin, storedOutput().trim @@ -173,7 +176,7 @@ class ReplCompilerTests extends ReplTest { @Test def testSingletonPrint = fromInitialState { implicit state => run("""val a = "hello"; val x: a.type = a""") - assertEquals("val a: String = hello\nval x: a.type = hello", storedOutput().trim) + assertMultiLineEquals("val a: String = hello\nval x: a.type = hello", storedOutput().trim) } @Test def i6574 = fromInitialState { implicit state => @@ -181,3 +184,16 @@ class ReplCompilerTests extends ReplTest { assertEquals("val a: 1 | 0 = 1", storedOutput().trim) } } + +object ReplCompilerTests { + + private val pattern = Pattern.compile("\\r[\\n]?|\\n"); + + // Ensure 'expected' and 'actual' contain the same line separator(s). + private def assertMultiLineEquals(expected: String, actual: String): Unit = { + val expected0 = pattern.matcher(expected).replaceAll(System.lineSeparator) + val actual0 = pattern.matcher(actual).replaceAll(System.lineSeparator) + assertEquals(expected0, actual0) + } + +} From b6737a8f87580060634d99cc5f8c1810fdae8bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Micheloud?= Date: Sat, 22 Feb 2020 15:18:14 +0100 Subject: [PATCH 05/31] small code change to address review --- compiler/test/dotty/tools/repl/LoadTests.scala | 11 +---------- .../test/dotty/tools/repl/ReplCompilerTests.scala | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/compiler/test/dotty/tools/repl/LoadTests.scala b/compiler/test/dotty/tools/repl/LoadTests.scala index 4770fc84f0b8..6786f8da16b5 100644 --- a/compiler/test/dotty/tools/repl/LoadTests.scala +++ b/compiler/test/dotty/tools/repl/LoadTests.scala @@ -8,7 +8,7 @@ import org.junit.{Test, BeforeClass, AfterClass} import org.junit.Assert.assertEquals class LoadTests extends ReplTest { - import LoadTests._ + import LoadTests._, ReplCompilerTests._ @Test def helloworld = loadTest( file = """|def helloWorld = "Hello, World!" @@ -83,13 +83,4 @@ object LoadTests { file } - private val pattern = Pattern.compile("\\r[\\n]?|\\n"); - - // Ensure 'expected' and 'actual' contain the same line separator(s). - private def assertMultiLineEquals(expected: String, actual: String): Unit = { - val expected0 = pattern.matcher(expected).replaceAll(System.lineSeparator) - val actual0 = pattern.matcher(actual).replaceAll(System.lineSeparator) - assertEquals(expected0, actual0) - } - } diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index a6c63ef787a6..2d190836c707 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -190,7 +190,7 @@ object ReplCompilerTests { private val pattern = Pattern.compile("\\r[\\n]?|\\n"); // Ensure 'expected' and 'actual' contain the same line separator(s). - private def assertMultiLineEquals(expected: String, actual: String): Unit = { + def assertMultiLineEquals(expected: String, actual: String): Unit = { val expected0 = pattern.matcher(expected).replaceAll(System.lineSeparator) val actual0 = pattern.matcher(actual).replaceAll(System.lineSeparator) assertEquals(expected0, actual0) From d7ee473c4016235baaefefc06b7e238c500c0388 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Mon, 24 Feb 2020 08:45:32 +0100 Subject: [PATCH 06/31] doc(multi-staging): fix typos --- docs/docs/reference/metaprogramming/staging.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/reference/metaprogramming/staging.md b/docs/docs/reference/metaprogramming/staging.md index 3444e3a0be79..ebb7c92a2995 100644 --- a/docs/docs/reference/metaprogramming/staging.md +++ b/docs/docs/reference/metaprogramming/staging.md @@ -8,13 +8,13 @@ multi-staging programming. We can think of compile-time meta-programming as a two stage compilation process: one that we write the code in top-level splices, that will be used for code generation (macros) and one that will perform all necessecary evaluations at compile-time and an object program that we will run -as usual. What if we could synthesize code at runtime and offer one extra stage -to the programmer? Then we can have a value of type `Expr[T]` at runtime that we +as usual. What if we could synthesize code at run-time and offer one extra stage +to the programmer? Then we can have a value of type `Expr[T]` at run-time that we can essentially treat as a typed-syntax tree that we can either _show_ as a string (pretty-print) or compile and run. If the number of quotes exceeds the -number of splices more than one (effectively handling at run-time values of type -`Expr[Expr[T]]`, `Expr[Expr[Expr[T]]]`, ... we talk about Multi-Stage -Programming). +number of splices by more than one (effectively handling at run-time values of type +`Expr[Expr[T]]`, `Expr[Expr[Expr[T]]]`, ...) then we talk about Multi-Stage +Programming. The motivation behind this _paradigm_ is to let runtime information affect or guide code-generation. @@ -22,11 +22,11 @@ guide code-generation. Intuition: The phase in which code is run is determined by the difference between the number of splice scopes and quote scopes in which it is embedded. - - If there are more splices than quotes, the code is run at "compile-time" i.e. + - If there are more splices than quotes, the code is run at compile-time i.e. as a macro. In the general case, this means running an interpreter that evaluates the code, which is represented as a typed abstract syntax tree. The interpreter can fall back to reflective calls when evaluating an application - of a previously compiled method. If the splice excess is more than one, it + of a previously compiled method. If the splice excess is more than one, it would mean that a macro’s implementation code (as opposed to the code it expands to) invokes other macros. If macros are realized by interpretation, this would lead to towers of interpreters, where the first interpreter would @@ -61,7 +61,7 @@ to be executed at a later stage. To run that code, there is another method in class `Expr` called `run`. Note that `$` and `run` both map from `Expr[T]` to `T` but only `$` is subject to the PCP, whereas `run` is just a normal method. Run provides a `QuoteContext` that can be used to show the expression in the scope of `run`. -On the other hand `withQuoteContext` provides a `QuoteContext` without evauating the expression. +On the other hand `withQuoteContext` provides a `QuoteContext` without evaluating the expression. ```scala package scala.quoted.staging From 946604b5e207d1444906385a5c9dcfa2fd27823e Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Mon, 24 Feb 2020 08:49:56 +0100 Subject: [PATCH 07/31] doc(multi-staging): more typos --- docs/docs/reference/metaprogramming/staging.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/metaprogramming/staging.md b/docs/docs/reference/metaprogramming/staging.md index ebb7c92a2995..6c47e70d6805 100644 --- a/docs/docs/reference/metaprogramming/staging.md +++ b/docs/docs/reference/metaprogramming/staging.md @@ -84,9 +84,9 @@ It will create a project with the necessary dependencies and some examples. ## Example Now take exactly the same example as in [Macros](./macros.md). Assume that we -do not want to pass an array statically but generated code at run-time and pass +do not want to pass an array statically but generate code at run-time and pass the value, also at run-time. Note, how we make a future-stage function of type -`Expr[Array[Int] => Int]` in line 4 below. Using `run { ... }` we can evaluate an +`Expr[Array[Int] => Int]` in line 6 below. Using `run { ... }` we can evaluate an expression at runtime. Within the scope of `run` we can also invoke `show` on an expression to get a source-like representation of the expression. @@ -106,7 +106,7 @@ f.apply(Array(1, 2, 3)) // Returns 6 ``` Note that if we need to run the main (in an object called `Test`) after -compilation we need make available the compiler to the runtime: +compilation we need to make available the compilation to the runtime: ```shell dotc -with-compiler -d out Test.scala From c66a33e1f71808bf8d3fa470f6fc8697fe2d1929 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Thu, 20 Feb 2020 12:05:50 +0100 Subject: [PATCH 08/31] Disable Mill libraries GH actions don't digest them well fttb --- .../scala/dotty/communitybuild/CommunityBuildTest.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index db5e98c9386a..6be615fc65c0 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -344,10 +344,10 @@ class CommunityBuildTest: @Test def ScalaPB = projects.ScalaPB.run() @Test def minitest = projects.minitest.run() @Test def fastparse = projects.fastparse.run() - @Test def utest = projects.utest.run() - @Test def sourcecode = projects.sourcecode.run() - @Test def oslib = projects.oslib.run() - @Test def ujson = projects.ujson.run() + //@Test def utest = projects.utest.run() + //@Test def sourcecode = projects.sourcecode.run() + //@Test def oslib = projects.oslib.run() + //@Test def ujson = projects.ujson.run() // @Test def oslibWatch = projects.oslibWatch.run() @Test def stdLib213 = projects.stdLib213.run() @Test def shapeless = projects.shapeless.run() From 5490f9566f0c0b2cc25468705beb943c10f18bc7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 24 Feb 2020 18:34:03 +0100 Subject: [PATCH 09/31] Fix documentation generation Update reference compiler and use single version of `scala.internal.quoted.CompileTime`. The issue was that `genDocs` used both the bootstrapped and non-bootstrapped version which conflicted. --- .../scala/internal/quoted/CompileTime.scala | 48 ------------------- .../internal/quoted}/quoted/CompileTime.scala | 0 library/src/scala/quoted/util/Var.scala | 2 + project/Build.scala | 2 +- 4 files changed, 3 insertions(+), 49 deletions(-) delete mode 100644 library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala rename library/{src-bootstrapped/scala/internal => src/scala/internal/quoted}/quoted/CompileTime.scala (100%) diff --git a/library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala b/library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala deleted file mode 100644 index 2ecba1d6a9ba..000000000000 --- a/library/src-non-bootstrapped/scala/internal/quoted/CompileTime.scala +++ /dev/null @@ -1,48 +0,0 @@ -package scala.internal.quoted - -import scala.annotation.{Annotation, compileTimeOnly} -import scala.quoted._ - -@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime`") -object CompileTime { - - /** A term quote is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.exprQuote`") - def exprQuote[T](x: T): QuoteContext ?=> Expr[T] = ??? - - /** A term splice is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.exprSplice`") - def exprSplice[T](x: QuoteContext ?=> Expr[T]): T = ??? - - /** A type quote is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.typeQuote`") - def typeQuote[T <: AnyKind]: Type[T] = ??? - - /** A splice in a quoted pattern is desugared by the compiler into a call to this method */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternHole`") - def patternHole[T]: T = ??? - - /** A splice of a name in a quoted pattern is desugared by wrapping getting this annotation */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternBindHole`") - class patternBindHole extends Annotation - - /** A splice of a name in a quoted pattern is that marks the definition of a type splice */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternType`") - class patternType extends Annotation - - /** A type pattern that must be aproximated from above */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.fromAbove`") - class fromAbove extends Annotation - - /** Artifact of pickled type splices - * - * During quote reification a quote `'{ ... F[$t] ... }` will be transformed into - * `'{ @quoteTypeTag type T$1 = $t ... F[T$1] ... }` to have a tree for `$t`. - * This artifact is removed during quote unpickling. - * - * See ReifyQuotes.scala and PickledQuotes.scala - */ - @compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.quoteTypeTag`") - class quoteTypeTag extends Annotation - -} diff --git a/library/src-bootstrapped/scala/internal/quoted/CompileTime.scala b/library/src/scala/internal/quoted/quoted/CompileTime.scala similarity index 100% rename from library/src-bootstrapped/scala/internal/quoted/CompileTime.scala rename to library/src/scala/internal/quoted/quoted/CompileTime.scala diff --git a/library/src/scala/quoted/util/Var.scala b/library/src/scala/quoted/util/Var.scala index 207766fd5ce1..de2325197182 100644 --- a/library/src/scala/quoted/util/Var.scala +++ b/library/src/scala/quoted/util/Var.scala @@ -17,6 +17,7 @@ object Var { /** Create a variable initialized with `init` and used in `body`. * `body` receives a `Var[T]` argument which exposes `get` and `update`. * + * ``` * Var('{7}) { * x => '{ * while(0 < ${x.get}) @@ -33,6 +34,7 @@ object Var { * x = x - 1 * x * } + * ``` */ def apply[T: Type, U: Type](init: Expr[T])(body: Var[T] => Expr[U])(using qctx: QuoteContext): Expr[U] = '{ var x = $init diff --git a/project/Build.scala b/project/Build.scala index 3de2076dc2df..40daa1ec6974 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -62,7 +62,7 @@ object MyScalaJSPlugin extends AutoPlugin { } object Build { - val referenceVersion = "0.22.0-RC1" + val referenceVersion = "0.23.0-bin-20200222-811dc19-NIGHTLY" val baseVersion = "0.23.0" val baseSbtDottyVersion = "0.4.0" From e1b2378c6d0bba9e34c179668885b8600ef616b6 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 13 Feb 2020 18:14:11 +0100 Subject: [PATCH 10/31] Move quote tagging to Staging Fix #8302, fix #8052 and fix #7521 --- .../tools/dotc/core/StagingContext.scala | 13 ++++ .../dotc/core/quoted/PickledQuotes.scala | 3 +- .../dotc/transform/PCPCheckAndHeal.scala | 69 ++++++++++++++++--- .../tools/dotc/transform/ReifyQuotes.scala | 60 ++-------------- .../dotty/tools/dotc/transform/Staging.scala | 4 +- .../tools/dotc/typer/QuotesAndSplices.scala | 2 +- .../test/dotc/pos-test-pickling.blacklist | 1 + library/src/scala/quoted/util/ExprMap.scala | 6 +- .../quote-macro-in-splice/quoted_1.scala | 0 .../quote-macro-in-splice/quoted_2.scala | 0 tests/neg/i7048e.scala | 10 +-- tests/neg/i8052.scala | 19 +++++ tests/pos/i7521.scala | 10 +++ tests/pos/i8052.scala | 21 ++++++ tests/pos/i8302.scala | 8 +++ tests/pos/using-quote-context.scala | 6 ++ tests/run-staging/i5247.check | 2 +- tests/run-staging/quote-owners-2.check | 4 +- 18 files changed, 162 insertions(+), 76 deletions(-) rename tests/{ => disabled}/run-staging/quote-macro-in-splice/quoted_1.scala (100%) rename tests/{ => disabled}/run-staging/quote-macro-in-splice/quoted_2.scala (100%) create mode 100644 tests/neg/i8052.scala create mode 100644 tests/pos/i7521.scala create mode 100644 tests/pos/i8052.scala create mode 100644 tests/pos/i8302.scala create mode 100644 tests/pos/using-quote-context.scala diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala index 6caba252cc90..0926ca85220d 100644 --- a/compiler/src/dotty/tools/dotc/core/StagingContext.scala +++ b/compiler/src/dotty/tools/dotc/core/StagingContext.scala @@ -1,8 +1,13 @@ package dotty.tools.dotc.core +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.util.Property +import dotty.tools.dotc.transform.PCPCheckAndHeal import scala.collection.mutable @@ -16,6 +21,8 @@ object StagingContext { */ private val QuoteContextStack = new Property.Key[List[tpd.Tree]] + private val TaggedTypes = new Property.Key[PCPCheckAndHeal.QuoteTypeTags] + /** All enclosing calls that are currently inlined, from innermost to outermost. */ def level(implicit ctx: Context): Int = ctx.property(QuotationLevel).getOrElse(0) @@ -34,6 +41,12 @@ object StagingContext { def spliceContext(implicit ctx: Context): Context = ctx.fresh.setProperty(QuotationLevel, level - 1) + def contextWithQuoteTypeTags(taggedTypes: PCPCheckAndHeal.QuoteTypeTags)(implicit ctx: Context) = + ctx.fresh.setProperty(TaggedTypes, taggedTypes) + + def getQuoteTypeTags(implicit ctx: Context): PCPCheckAndHeal.QuoteTypeTags = + ctx.property(TaggedTypes).get + /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. * The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice. */ diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 8ebc2386353a..bcbe4625d8c8 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -52,7 +52,8 @@ object PickledQuotes { private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() { override def apply(tp: Type): Type = { val tp1 = tp match { - case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => tp.dealias + case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => + tp.symbol.info.hiBound case _ => tp } mapOver(tp1) diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index dda62deed5f2..37d86c95cf08 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -21,6 +21,7 @@ import dotty.tools.dotc.transform.TreeMapWithStages._ import dotty.tools.dotc.typer.Checking import dotty.tools.dotc.typer.Implicits.SearchFailureType import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.core.Annotations._ import scala.collection.mutable import dotty.tools.dotc.util.SourcePosition @@ -54,10 +55,21 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( /** Transform quoted trees while maintaining phase correctness */ override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val taggedTypes = new PCPCheckAndHeal.QuoteTypeTags(quote.span)(using ctx) + if (ctx.property(InAnnotation).isDefined) ctx.error("Cannot have a quote in an annotation", quote.sourcePos) - val body1 = transform(body)(quoteContext) - super.transformQuotation(body1, quote) + + val contextWithQuote = + if level == 0 then contextWithQuoteTypeTags(taggedTypes)(quoteContext) + else quoteContext + val body1 = transform(body)(contextWithQuote) + val body2 = + taggedTypes.getTypeTags match + case Nil => body1 + case tags => tpd.Block(tags, body1).withSpan(body.span) + + super.transformQuotation(body2, quote) } /** Transform splice @@ -73,7 +85,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( // internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...) val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr) cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: qctx :: Nil), body1 :: Nil) - case splice: Select => cpy.Select(splice)(body1, splice.name) + case splice: Select => + val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef]) + ref(tagRef).withSpan(splice.span) } } @@ -120,11 +134,14 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( case tp: TypeRef if tp.symbol.isSplice => if (tp.isTerm) ctx.error(i"splice outside quotes", pos) - tp + if level > 0 then getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef]) + else tp case tp: TypeRef if tp.symbol == defn.QuotedTypeClass.typeParams.head => - // Adapt direct references to the type of the type parameter T of a quoted.Type[T]. - // Replace it with a properly encoded type splice. This is the normal for expected for type splices. - tp.prefix.select(tpnme.splice) + if level > 0 then + // Adapt direct references to the type of the type parameter T of a quoted.Type[T]. + // Replace it with a properly encoded type splice. This is the normal form expected for type splices. + getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef]) + else tp case tp: NamedType => checkSymLevel(tp.symbol, tp, pos) match { case Some(tpRef) => tpRef.tpe @@ -201,7 +218,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( sym.isClass // reference to this in inline methods ) case None => - sym.is(Package) || sym.owner.isStaticOwner || levelOK(sym.owner) + sym.is(Package) || sym.owner.isStaticOwner || + (sym.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) && level > 0) || + levelOK(sym.owner) } /** Try to heal reference to type `T` used in a higher level than its definition. @@ -212,10 +231,11 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp) val tag = ctx.typer.inferImplicitArg(reqType, pos.span) + tag.tpe match case tp: TermRef => checkStable(tp, pos) - Some(tag.select(tpnme.splice)) + Some(ref(getQuoteTypeTags.getTagRef(tp))) case _: SearchFailureType => levelError(sym, tp, pos, i""" @@ -242,3 +262,34 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages( } } +object PCPCheckAndHeal { + import tpd._ + + class QuoteTypeTags(span: Span)(using ctx: Context) { + + private val tags = collection.mutable.LinkedHashMap.empty[Symbol, TypeDef] + + def getTagRef(spliced: TermRef): TypeRef = { + val typeDef = tags.getOrElseUpdate(spliced.symbol, mkTagSymbolAndAssignType(spliced)) + typeDef.symbol.typeRef + } + + def getTypeTags: List[TypeDef] = tags.valuesIterator.toList + + private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { + val splicedTree = tpd.ref(spliced).withSpan(span) + val rhs = splicedTree.select(tpnme.splice).withSpan(span) + val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) + val local = ctx.newSymbol( + owner = ctx.owner, + name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, + flags = Synthetic, + info = TypeAlias(splicedTree.tpe.select(tpnme.splice)), + coord = span).asType + local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot)) + ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) + } + + } + +} diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 9cafe398161c..8c0b652846d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -119,57 +119,6 @@ class ReifyQuotes extends MacroTransform { new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) } - /** Assuming contains types `${}, ..., ${}`, the expression - * - * { @quoteTypeTag type = ${} - * ... - * @quoteTypeTag type = ${} - * - * } - * - * references to `TypeI` in `expr` are rewired to point to the locally - * defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN` - * as splices. - */ - private def addTags(expr: Tree)(implicit ctx: Context): Tree = { - - def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { - val splicedTree = tpd.ref(spliced).withSpan(expr.span) - val rhs = transform(splicedTree.select(tpnme.splice)) - val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) - val local = ctx.newSymbol( - owner = ctx.owner, - name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName, - flags = Synthetic, - info = TypeAlias(splicedTree.tpe.select(tpnme.splice)), - coord = spliced.termSymbol.coord).asType - local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot)) - ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) - } - - val tagDefCache = new mutable.LinkedHashMap[Symbol, TypeDef]() - - def typeTagMap = new TypeMap() { - def apply(tp: Type): Type = tp match { - case tp: TypeRef if tp.symbol.isSplice => - tp.prefix match { - case prefix: TermRef => - val tagDef = tagDefCache.getOrElseUpdate(prefix.symbol, mkTagSymbolAndAssignType(prefix)) - tagDef.symbol.typeRef - } - case AnnotatedType(parent, _) => - apply(parent) // Only keep the Annotated tree - case _ => - mapOver(tp) - } - } - - val tagedTree = new TreeTypeMap(typeMap = typeTagMap).apply(expr) - - if (tagDefCache.isEmpty) expr - else Block(tagDefCache.valuesIterator.toList, tagedTree) - } - /** Split `body` into a core and a list of embedded splices. * Then if inside a splice, make a hole from these parts. * If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or @@ -229,7 +178,7 @@ class ReifyQuotes extends MacroTransform { def pickleAsTasty() = { val meth = if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp) - else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen) + else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen.dealias) val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType) val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType)) meth.appliedTo(pickledQuoteStrings, splicesList) @@ -362,10 +311,10 @@ class ReifyQuotes extends MacroTransform { level == 1 && levelOf(sym).contains(1) && capturers.contains(sym) /** Transform `tree` and return the resulting tree and all `embedded` quotes - * or splices as a pair, after performing the `addTags` transform. + * or splices as a pair. */ private def splitQuote(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { - val tree1 = addTags(transform(tree)) + val tree1 = stipTypeAnnotations(transform(tree)) (tree1, embedded.getTrees) } @@ -374,6 +323,9 @@ class ReifyQuotes extends MacroTransform { (tree1, embedded.getTrees) } + private def stipTypeAnnotations(tree: Tree)(using Context): Tree = + new TreeTypeMap(typeMap = _.stripAnnots).apply(tree) + /** Register `body` as an `embedded` quote or splice * and return a hole with `splices` as arguments and the given type `tpe`. */ diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 0800eca3e598..708bf418925d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -48,7 +48,9 @@ class Staging extends MacroTransform { if (sym.is(ModuleClass)) sym.sourceModule.show else i"${sym.name}.this" val errMsg = s"\nin ${ctx.owner.fullName}" - assert(false, + assert( + ctx.owner.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) || + (sym.isType && levelOf(sym).getOrElse(0) > 0), em"""access to $symStr from wrong staging level: | - the definition is at level ${levelOf(sym).getOrElse(0)}, | - but the access is at level $level.$errMsg""") diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 38a87bb2063f..e813b87c978f 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -87,7 +87,7 @@ trait QuotesAndSplices { if (c.owner eq c.outer.owner) markAsMacro(c.outer) else if (c.owner.isInlineMethod) c.owner.setFlag(Macro) else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) - else assert(false) // Did not find inline def to mark as macro + else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro markAsMacro(ctx) } diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 3d112e422a7a..7bfe50ed231b 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -11,6 +11,7 @@ t3612.scala reference scala-days-2019-slides i7048e.scala +i8052.scala # Stale symbol: package object scala seqtype-cycle diff --git a/library/src/scala/quoted/util/ExprMap.scala b/library/src/scala/quoted/util/ExprMap.scala index 80f83a96c49e..7475fecdc8bf 100644 --- a/library/src/scala/quoted/util/ExprMap.scala +++ b/library/src/scala/quoted/util/ExprMap.scala @@ -64,9 +64,11 @@ trait ExprMap { val tp = tpt.tpe match // TODO improve code case AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), ""), List(tp0: Type)) => + // TODO rewrite without using quotes type T - val a = tp0.seal.asInstanceOf[quoted.Type[T]] - '[Seq[$a]].unseal.tpe + val qtp: quoted.Type[T] = tp0.seal.asInstanceOf[quoted.Type[T]] + given qtp.type = qtp + '[Seq[T]].unseal.tpe case tp => tp Typed.copy(tree)(transformTerm(expr, tp), transformTypeTree(tpt)) case tree: NamedArg => diff --git a/tests/run-staging/quote-macro-in-splice/quoted_1.scala b/tests/disabled/run-staging/quote-macro-in-splice/quoted_1.scala similarity index 100% rename from tests/run-staging/quote-macro-in-splice/quoted_1.scala rename to tests/disabled/run-staging/quote-macro-in-splice/quoted_1.scala diff --git a/tests/run-staging/quote-macro-in-splice/quoted_2.scala b/tests/disabled/run-staging/quote-macro-in-splice/quoted_2.scala similarity index 100% rename from tests/run-staging/quote-macro-in-splice/quoted_2.scala rename to tests/disabled/run-staging/quote-macro-in-splice/quoted_2.scala diff --git a/tests/neg/i7048e.scala b/tests/neg/i7048e.scala index 610d0c7711e9..0b9a67fad9e7 100644 --- a/tests/neg/i7048e.scala +++ b/tests/neg/i7048e.scala @@ -9,7 +9,7 @@ abstract class Test { def foo(using QuoteContext): Expr[Any] = { - val r = '{Option.empty[T]} // error + val r = '{Option.empty[T]} // error: is not stable { val t: Test = this @@ -22,14 +22,14 @@ abstract class Test { { val r1 = '{Option.empty[${T}]} // works val r2 = '{Option.empty[List[${T}]]} // works - // val r3 = '{summon[Type[${T}]]} // access to Test.this from wrong staging level - val r4 = '{summon[${T} <:< Any]} // error + val r3 = '{summon[Type[${T}]]} // error: is not stable + val r4 = '{summon[${T} <:< Any]} // error: is not stable } { - val s = '{Option.empty[${T}]} + val s = '{Option.empty[${T}]} // works val r = '{identity($s)} // works - val r2 = '{identity(${s: Expr[Option[T]]})} // error // error + val r2 = '{identity(${s: Expr[Option[T]]})} // error // error : is not stable } r diff --git a/tests/neg/i8052.scala b/tests/neg/i8052.scala new file mode 100644 index 000000000000..6f5cddb8b415 --- /dev/null +++ b/tests/neg/i8052.scala @@ -0,0 +1,19 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ + +object Macro2 { + trait TC[T] { + def test(): Unit + } + + object TC { + def derived[T: Type](ev: Expr[Mirror.Of[T]])(using qctx: QuoteContext): Expr[TC[T]] = '{ + new TC[T] { + def encode(): Unit = $ev match { + case '{ $m: Mirror.ProductOf[T] } => ??? // error + } + } + } + } +} diff --git a/tests/pos/i7521.scala b/tests/pos/i7521.scala new file mode 100644 index 000000000000..3dfa925dedf5 --- /dev/null +++ b/tests/pos/i7521.scala @@ -0,0 +1,10 @@ +import scala.quoted._ +import scala.annotation.StaticAnnotation + +object Test { + inline def quote[T]: Unit = ${ quoteImpl[T] } + def quoteImpl[T: Type](using qctx: QuoteContext): Expr[Unit] = '{ + class Annot extends StaticAnnotation + var test: T @Annot = ??? + } +} diff --git a/tests/pos/i8052.scala b/tests/pos/i8052.scala new file mode 100644 index 000000000000..f6ac68657c47 --- /dev/null +++ b/tests/pos/i8052.scala @@ -0,0 +1,21 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ + +object Macro2 { + trait TC[T] { + def test(): Unit + } + + object TC { + def derived[T: Type](ev: Expr[Mirror.Of[T]])(using qctx: QuoteContext): Expr[TC[T]] = '{ + new TC[T] { + def encode(): Unit = ${ + ev match { + case '{ $m: Mirror.ProductOf[T] } => ??? + } + } + } + } + } +} diff --git a/tests/pos/i8302.scala b/tests/pos/i8302.scala new file mode 100644 index 000000000000..83ea6a285071 --- /dev/null +++ b/tests/pos/i8302.scala @@ -0,0 +1,8 @@ +import scala.quoted._ +def foo[T](using qctx: QuoteContext, tpe: Type[T]): Expr[Any] = + '{ + type TT = T + val t = '[TT] + ??? + } + diff --git a/tests/pos/using-quote-context.scala b/tests/pos/using-quote-context.scala new file mode 100644 index 000000000000..51205190ecbd --- /dev/null +++ b/tests/pos/using-quote-context.scala @@ -0,0 +1,6 @@ +import scala.quoted._ + +class Test { + def fold[W: Type](s: Expr[W]): QuoteContext ?=> Expr[W] = + '{ ???; $s } +} diff --git a/tests/run-staging/i5247.check b/tests/run-staging/i5247.check index 63524be90ce2..620b21ddf88d 100644 --- a/tests/run-staging/i5247.check +++ b/tests/run-staging/i5247.check @@ -1,2 +1,2 @@ null.asInstanceOf[java.lang.Object] -null.asInstanceOf[scala.List[java.lang.Object]] +null.asInstanceOf[scala.collection.immutable.List[java.lang.Object]] diff --git a/tests/run-staging/quote-owners-2.check b/tests/run-staging/quote-owners-2.check index fc038fc486a3..323ce64b7bc7 100644 --- a/tests/run-staging/quote-owners-2.check +++ b/tests/run-staging/quote-owners-2.check @@ -1,7 +1,7 @@ { def ff: scala.Int = { - val a: scala.List[scala.Int] = { - type T = scala.List[scala.Int] + val a: scala.collection.immutable.List[scala.Int] = { + type T = scala.collection.immutable.List[scala.Int] val b: T = scala.Nil.::[scala.Int](3) (b: scala.collection.immutable.List[scala.Int]) From 28c96ad64c349d662bba5d900ff2ca7efe1c15d3 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Mon, 24 Feb 2020 18:43:36 +0100 Subject: [PATCH 11/31] doc(dep-fun-type): typos --- docs/docs/reference/new-types/dependent-function-types.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/reference/new-types/dependent-function-types.md b/docs/docs/reference/new-types/dependent-function-types.md index dc1154cb8372..9b1bc236ce21 100644 --- a/docs/docs/reference/new-types/dependent-function-types.md +++ b/docs/docs/reference/new-types/dependent-function-types.md @@ -11,13 +11,13 @@ trait Entry { type Key; val key: Key } def extractKey(e: Entry): e.Key = e.key // a dependent method val extractor: (e: Entry) => e.Key = extractKey // a dependent function value // ║ ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ║ -// ║ Dependent ║ -// ║ Function Type ║ -// ╚═══════════════════╝ +// ║ Dependent ║ +// ║ Function Type ║ +// ╚═══════════════╝ ``` Scala already has _dependent methods_, i.e. methods where the result type refers to some of the parameters of the method. Method -`extractKey` is an example. Its result type, `e.Key` refers its +`extractKey` is an example. Its result type, `e.Key` refers to its parameter `e` (we also say, `e.Key` _depends_ on `e`). But so far it was not possible to turn such methods into function values, so that they can be passed as parameters to other functions, or returned as From 082841d9eee4d8bc66cafe57a0dfa64274a5ffe5 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Mon, 24 Feb 2020 19:40:02 +0100 Subject: [PATCH 12/31] doc(operators): typo and add import to examples --- docs/docs/reference/changed-features/operators.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/changed-features/operators.md b/docs/docs/reference/changed-features/operators.md index dee50420d3a2..eadcf4daa94c 100644 --- a/docs/docs/reference/changed-features/operators.md +++ b/docs/docs/reference/changed-features/operators.md @@ -10,6 +10,8 @@ Furthermore, a syntax change allows infix operators to be written on the left in An `@alpha` annotation on a method definition defines an alternate name for the implementation of that method: Example: ```scala +import scala.annotation.alpha + object VecOps { @alpha("append") def (xs: Vec[T]) ++= [T] (ys: Vec[T]): Vec[T] = ... } @@ -47,7 +49,7 @@ The `@alpha` annotation serves a dual purpose: 5. Definitions with names in backticks that are not legal host platform names should have an `@alpha` annotation. Lack of such an annotation will raise a deprecation warning. - 6. @alpha annotations must agree: If two definitions are members of an object or class with the same name and matching types, then either none of them has an `@alpha` annotation, or both have `@alpha` annotations with the same name. + 6. `@alpha` annotations must agree: If two definitions are members of an object or class with the same name and matching types, then either none of them has an `@alpha` annotation, or both have `@alpha` annotations with the same name. 7. There must be a one-to-one relationship between external and internal names: If two definitions are members of an object or class with matching types and both have `@alpha` annotations with the same external name, then their internal method names must also be the same. @@ -56,6 +58,8 @@ The `@alpha` annotation serves a dual purpose: An `@infix` annotation on a method definition allows using the method as an infix operation. Example: ```scala +import scala.annotation.alpha + trait MultiSet[T] { @infix @@ -70,6 +74,7 @@ trait MultiSet[T] { val s1, s2: MultiSet[Int] s1 union s2 // OK +s1 `union` s2 // also OK but unusual s1.union(s2) // also OK s1.difference(s2) // OK @@ -77,6 +82,7 @@ s1 `difference` s2 // OK s1 difference s2 // gives a deprecation warning s1 * s2 // OK +s1 `*` s2 // also OK, but unusual s1.*(s2) // also OK, but unusual ``` Infix operations involving alphanumeric operators are deprecated, unless @@ -118,7 +124,7 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b @infix def (x: A) op (y1: B, y2: B): R // error: two parameters ``` - 4. @infix annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like + 4. `@infix` annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like ```scala @infix type op[X, Y] From 802ed3b38c5ab98a126d50bebfd8b387ad7d472f Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Tue, 25 Feb 2020 15:02:57 +0100 Subject: [PATCH 13/31] doc(untupling): fix list at the end, add newline --- docs/docs/reference/other-new-features/parameter-untupling.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/reference/other-new-features/parameter-untupling.md b/docs/docs/reference/other-new-features/parameter-untupling.md index 41d27b723ac5..c0765aa57350 100644 --- a/docs/docs/reference/other-new-features/parameter-untupling.md +++ b/docs/docs/reference/other-new-features/parameter-untupling.md @@ -31,5 +31,6 @@ function type of the form `((T_1, ..., T_n)) => U`. ### Reference For more info see: + * [More details](./parameter-untupling-spec.md) * [Issue #897](https://github.com/lampepfl/dotty/issues/897). From 8b5ddf5dfdb1292a46ea403f67cc7ab634278174 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Tue, 25 Feb 2020 15:06:11 +0100 Subject: [PATCH 14/31] doc(context-functions): typo `are` instead of `is` --- docs/docs/reference/contextual/context-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/context-functions.md b/docs/docs/reference/contextual/context-functions.md index e8e7034da15b..bd20a6de022a 100644 --- a/docs/docs/reference/contextual/context-functions.md +++ b/docs/docs/reference/contextual/context-functions.md @@ -11,7 +11,7 @@ type Executable[T] = ExecutionContext ?=> T ``` Context function are written using `?=>` as the "arrow" sign. They are applied to synthesized arguments, in -the same way methods with context parameters is applied. For instance: +the same way methods with context parameters are applied. For instance: ```scala given ec as ExecutionContext = ... From b3442396d9865ffabf14a4d2cff8784063b33ef2 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Tue, 25 Feb 2020 15:07:52 +0100 Subject: [PATCH 15/31] doc(multi-staging): move setup/flags to create a new project --- .../docs/reference/metaprogramming/staging.md | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/docs/docs/reference/metaprogramming/staging.md b/docs/docs/reference/metaprogramming/staging.md index 6c47e70d6805..b2b1b0c0411a 100644 --- a/docs/docs/reference/metaprogramming/staging.md +++ b/docs/docs/reference/metaprogramming/staging.md @@ -81,6 +81,19 @@ From [lampepfl/dotty-staging.g8](https://github.com/lampepfl/dotty-staging.g8). It will create a project with the necessary dependencies and some examples. +In case you prefer to create the project on your own, make sure to define the following dependency in your build.sbt + +```scala +libraryDependencies += "ch.epfl.lamp" %% "dotty-staging" % scalaVersion.value +``` + +and in case you use `dotc`/`dotr` directly, then use the `-with-compiler` flag for both: +```shell +dotc -with-compiler -d out Test.scala +dotr -with-compiler -classpath out Test +``` + + ## Example Now take exactly the same example as in [Macros](./macros.md). Assume that we @@ -104,24 +117,3 @@ val f: Array[Int] => Int = run { f.apply(Array(1, 2, 3)) // Returns 6 ``` - -Note that if we need to run the main (in an object called `Test`) after -compilation we need to make available the compilation to the runtime: - -```shell -dotc -with-compiler -d out Test.scala -dotr -with-compiler -classpath out Test -``` - -Or, from SBT: - -```scala -libraryDependencies += "ch.epfl.lamp" %% "dotty-staging" % scalaVersion.value -``` - -## Template project -Using sbt version `1.1.5+`, do: -``` -sbt new lampepfl/dotty-staging.g8 -``` -in the folder where you want to clone the template. From 5dcb63a3717226354e5f5886dc4e1274c631d731 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Tue, 25 Feb 2020 15:09:03 +0100 Subject: [PATCH 16/31] doc(multi-staging): cleanup new lines --- docs/docs/reference/metaprogramming/staging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/metaprogramming/staging.md b/docs/docs/reference/metaprogramming/staging.md index b2b1b0c0411a..f5a59f4f946a 100644 --- a/docs/docs/reference/metaprogramming/staging.md +++ b/docs/docs/reference/metaprogramming/staging.md @@ -88,12 +88,12 @@ libraryDependencies += "ch.epfl.lamp" %% "dotty-staging" % scalaVersion.value ``` and in case you use `dotc`/`dotr` directly, then use the `-with-compiler` flag for both: + ```shell dotc -with-compiler -d out Test.scala dotr -with-compiler -classpath out Test ``` - ## Example Now take exactly the same example as in [Macros](./macros.md). Assume that we From 8df5b702ced994aea7c3bca61fee44a1f50fd49e Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Tue, 25 Feb 2020 15:20:18 +0100 Subject: [PATCH 17/31] doc(macros): fix expansion and some improvements --- docs/docs/reference/metaprogramming/macros.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index e6047779cba7..f34a8a8fc095 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -377,7 +377,7 @@ val program = { ${ Macros.assertImpl('{ x != 0) } } } ``` -The example is only phase correct because Macros is a global value and +The example is only phase correct because `Macros` is a global value and as such not subject to phase consistency checking. Conceptually that’s a bit unsatisfactory. If the PCP is so fundamental, it should be applicable without the global value exception. But in the example as @@ -529,7 +529,7 @@ val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*) var sum = 0 val f = x => '{sum += $x} -${ _root_.Macros.map(arr, '[Int], 'f)} +${ _root_.Macros.map(arr, 'f)('[Int])} sum ``` @@ -616,10 +616,10 @@ It is possible to deconstruct or extract values out of `Expr` using pattern matc #### scala.quoted.matching -In `scala.quoted.matching` contains object that can help extract values from `Expr`. +`scala.quoted.matching` contains objects that can help extracting values from `Expr`. -* `scala.quoted.matching.Const`: matches an expression a literal value and returns the value. -* `scala.quoted.matching.Value`: matches an expression a value and returns the value. +* `scala.quoted.matching.Const`: matches an expression of a literal value and returns the value. +* `scala.quoted.matching.Value`: matches an expression of a value and returns the value. * `scala.quoted.matching.ExprSeq`: matches an explicit sequence of expresions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`. * `scala.quoted.matching.ConstSeq`: matches an explicit sequence of literal values and returns them. * `scala.quoted.matching.ValueSeq`: matches an explicit sequence of values and returns them. From 4922e7ff823a8f0445a5eb4b533d2cab07e24872 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Wed, 26 Feb 2020 06:42:09 +0100 Subject: [PATCH 18/31] doc(operators): wrap one more annotation --- docs/docs/reference/changed-features/operators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/changed-features/operators.md b/docs/docs/reference/changed-features/operators.md index eadcf4daa94c..5ff0d12a3e13 100644 --- a/docs/docs/reference/changed-features/operators.md +++ b/docs/docs/reference/changed-features/operators.md @@ -97,7 +97,7 @@ any unicode character `c` for which `java.lang.Character.isIdentifierPart(c)` re Infix operations involving symbolic operators are always allowed, so `@infix` is redundant for methods with symbolic names. -The @infix annotation can also be given to a type: +The `@infix` annotation can also be given to a type: ``` @infix type or[X, Y] val x: String or Int = ... @@ -132,7 +132,7 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b can be applied using infix syntax, i.e. `A op B`. - 5. To smooth migration to Scala 3.0, alphanumeric operations will only be deprecated from Scala 3.1 onwards, + 5. To smooth migration to Scala 3.0, alphanumeric operators will only be deprecated from Scala 3.1 onwards, or if the `-strict` option is given in Dotty/Scala 3. ## Syntax Change From e829c77bc039a1c0e816af55ffda52ca3d5cf637 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Wed, 26 Feb 2020 06:46:06 +0100 Subject: [PATCH 19/31] doc(trait param): state which rule is violated --- docs/docs/reference/other-new-features/trait-parameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/other-new-features/trait-parameters.md b/docs/docs/reference/other-new-features/trait-parameters.md index e625b6dd8ad0..16a6438173b4 100644 --- a/docs/docs/reference/other-new-features/trait-parameters.md +++ b/docs/docs/reference/other-new-features/trait-parameters.md @@ -26,7 +26,7 @@ class D extends C with Greeting("Bill") // error: parameter passed twice ``` Should this print "Bob" or "Bill"? In fact this program is illegal, -because it violates one of the following rules for trait parameters: +because it violates the second rule of the following for trait parameters: 1. If a class `C` extends a parameterized trait `T`, and its superclass does not, `C` _must_ pass arguments to `T`. From 91bdeef43f2f090cdb72bf6d8d38829e22b028f8 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Wed, 26 Feb 2020 06:50:07 +0100 Subject: [PATCH 20/31] doc(creator apply): typos --- .../reference/other-new-features/creator-applications.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/other-new-features/creator-applications.md b/docs/docs/reference/other-new-features/creator-applications.md index 6f2c5a830008..ce461927bc20 100644 --- a/docs/docs/reference/other-new-features/creator-applications.md +++ b/docs/docs/reference/other-new-features/creator-applications.md @@ -13,13 +13,13 @@ class StringBuilder(s: String) { StringBuilder("abc") // same as new StringBuilder("abc") StringBuilder() // same as new StringBuilder() ``` -Creator applications generalize a functionality provided so far only for case classes, but the mechanism how this is achieved is different. Instead generating an apply method, the compiler adds a new possible interpretation to a function call `f(args)`. The previous rules are: +Creator applications generalize a functionality provided so far only for case classes, but the mechanism how this is achieved is different. Instead of generating an apply method, the compiler adds a new possible interpretation to a function call `f(args)`. The previous rules are: Given a function call `f(args)`, - if `f` is a method applicable to `args`, typecheck `f(args)` unchanged, - otherwise, if `f` has an `apply` method applicable to `args` as a member, continue with `f.apply(args)`, - - otherwise, if `f` is of the form `p.m` and there is an implicit conversion `c` applicable to `p` so that `c(p).m` is applicable to `args`, continue with `c(p).m(args)` + - otherwise, if `f` is of the form `p.m` and there is an implicit conversion `c` applicable to `p` so that `c(p).m` is applicable to `args`, continue with `c(p).m(args)` There's now a fourth rule following these rules: @@ -40,4 +40,4 @@ caused numerous problems, including - overloading ambiguities - overriding errors - - shadowing of user-defined `apply` methods by more specific auto-generated ones. \ No newline at end of file + - shadowing of user-defined `apply` methods by more specific auto-generated ones. From 9964ec808097583d090ea516ce2e6ed5bbc18203 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Wed, 26 Feb 2020 09:30:59 +0100 Subject: [PATCH 21/31] doc(export): add link to note --- docs/docs/reference/other-new-features/export.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/reference/other-new-features/export.md b/docs/docs/reference/other-new-features/export.md index 481f255d966f..11ed1c55c229 100644 --- a/docs/docs/reference/other-new-features/export.md +++ b/docs/docs/reference/other-new-features/export.md @@ -58,7 +58,7 @@ of one of the following forms: A member is _eligible_ if all of the following holds: - - its owner is not a base class of the class(\*) containing the export clause, + - its owner is not a base class of the class[(\*)](#note_class) containing the export clause, - the member does not override a concrete definition that has as owner a base class of the class containing the export clause. - it is accessible at the export clause, @@ -87,6 +87,7 @@ def f: c.T = ... Export clauses can appear in classes or they can appear at the top-level. An export clause cannot appear as a statement in a block. + (\*) Note: Unless otherwise stated, the term "class" in this discussion also includes object and trait definitions. ### Motivation From 92a395295a380fe8086210b9176f0db61c09144d Mon Sep 17 00:00:00 2001 From: Uko Date: Wed, 26 Feb 2020 12:36:36 +0100 Subject: [PATCH 22/31] Fix lampepfl/dotty-knowledge#30 When a community test fails, re-run it 2 more times to avoid the whole suite failure. --- .../dotty/communitybuild/CommunityBuildTest.scala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 6be615fc65c0..2d085ee6afdf 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -298,6 +298,17 @@ class CommunityBuildTest: * @param arguments Arguments to pass to the testing program */ def test(project: String, command: String, arguments: Seq[String]): Unit = { + @annotation.tailrec + def execTimes(task: => Int, timesToRerun: Int): Boolean = + val exitCode = task + if exitCode == 0 + then true + else if timesToRerun == 0 + then false + else + log(s"Rerunning tests in $project because of a previous run failure.") + execTimes(task, timesToRerun - 1) + log(s"Building $project with dotty-bootstrapped $compilerVersion...") val projectDir = communitybuildDir.resolve("community-projects").resolve(project) @@ -312,9 +323,9 @@ class CommunityBuildTest: |""".stripMargin) } - val exitCode = exec(projectDir, command, arguments: _*) + val testsCompletedSuccessfully = execTimes(exec(projectDir, command, arguments: _*), 3) - if (exitCode != 0) { + if (!testsCompletedSuccessfully) { fail(s""" | |$command exited with an error code. To reproduce without JUnit, use: From d7e0212ff77fc7b84d7c864ea4154b2fc350a24e Mon Sep 17 00:00:00 2001 From: Uko Date: Wed, 26 Feb 2020 14:25:17 +0100 Subject: [PATCH 23/31] Fix lampepfl/dotty-knowledge#17 Added a scripted sbt test to check if a @main annotation is detected by sbt --- .../sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt | 12 ++++++++++++ .../dotty-knowledge.i17/project/build.properties | 1 + .../dotty-knowledge.i17/project/plugins.sbt | 1 + .../dotty-knowledge.i17/src/main/scala/Main.scala | 4 ++++ .../sbt-test/sbt-dotty/dotty-knowledge.i17/test | 1 + 5 files changed, 19 insertions(+) create mode 100644 sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties create mode 100644 sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt new file mode 100644 index 000000000000..108f70f83cca --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/build.sbt @@ -0,0 +1,12 @@ +val dottyVersion = "0.22.0-RC1" + +lazy val root = project + .in(file(".")) + .settings( + name := "dotty-simple", + version := "0.1.0", + + scalaVersion := dottyVersion, + + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" + ) diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties new file mode 100644 index 000000000000..a919a9b5f46b --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.8 diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt new file mode 100644 index 000000000000..b7e929c23e19 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.0") diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala new file mode 100644 index 000000000000..e1e0b803d9b0 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/src/main/scala/Main.scala @@ -0,0 +1,4 @@ +@main +def testMethod(): Unit = { + println("Hello world!") +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test new file mode 100644 index 000000000000..62ea636c177f --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/dotty-knowledge.i17/test @@ -0,0 +1 @@ +> run From 0803aff2b464cf3f69e1217891182c6e89efb6d2 Mon Sep 17 00:00:00 2001 From: Uko Date: Wed, 26 Feb 2020 15:21:34 +0100 Subject: [PATCH 24/31] Fix #8362: Fail compilation if a compile time error can't be inlined because of a non-literal string parameter --- .../src/dotty/tools/dotc/typer/Inliner.scala | 25 +++++++++---------- tests/neg/i8362.scala | 5 ++++ 2 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 tests/neg/i8362.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d1c65000726c..45e33741172f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -559,19 +559,19 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { def issueError() = callValueArgss match { case (msgArg :: Nil) :: Nil => - msgArg.tpe match { - case ConstantType(Constant(msg: String)) => - // Usually `error` is called from within a rewrite method. In this - // case we need to report the error at the point of the outermost enclosing inline - // call. This way, a defensively written rewrite methid can always - // report bad inputs at the point of call instead of revealing its internals. - val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call - val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(_).nonEmpty).next - def issueInCtx(implicit ctx: Context) = - ctx.error(msg, callToReport.sourcePos) - issueInCtx(ctxToReport) - case _ => + val message = msgArg.tpe match { + case ConstantType(Constant(msg: String)) => msg + case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" } + // Usually `error` is called from within a rewrite method. In this + // case we need to report the error at the point of the outermost enclosing inline + // call. This way, a defensively written rewrite methid can always + // report bad inputs at the point of call instead of revealing its internals. + val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call + val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(_).nonEmpty).next + def issueInCtx(implicit ctx: Context) = + ctx.error(message, callToReport.sourcePos) + issueInCtx(ctxToReport) case _ => } @@ -1316,4 +1316,3 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } }.apply(Nil, tree) } - diff --git a/tests/neg/i8362.scala b/tests/neg/i8362.scala new file mode 100644 index 000000000000..c1b498fbbb9a --- /dev/null +++ b/tests/neg/i8362.scala @@ -0,0 +1,5 @@ +object Test { + inline def foo: String = scala.compiletime.error(s"") + + def main(args: Array[String]): Unit = println(foo) // error +} From b952d4180cf43bf74e48c821b291f1cc8fd55dc3 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 22 Feb 2020 17:26:48 +0100 Subject: [PATCH 25/31] Avoid overcompilation involving inline or annotation trees We used to pretty-print trees in the API info we send to sbt, but the pretty-printed output seems to be unstable leading to overcompilation, so just use the raw trees instead (this should also be faster). --- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 0af442b2c089..12f8a150f1c5 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -595,12 +595,11 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder if (!inlineBody.isEmpty) { // FIXME: If the body of an inlineable method changes, all the reverse // dependencies of this method need to be recompiled. sbt has no way - // of tracking method bodies, so as a hack we include the pretty-printed - // typed tree of the method as part of the signature we send to sbt. + // of tracking method bodies, so as a hack we include the printed + // tree of the method as part of the signature we send to sbt. // To do this properly we would need a way to hash trees and types in // dotty itself. - val printTypesCtx = ctx.fresh.setSetting(ctx.settings.XprintTypes, true) - annots += marker(inlineBody.show(printTypesCtx)) + annots += marker(inlineBody.toString) } // In the Scala2 ExtractAPI phase we only extract annotations that extend @@ -618,12 +617,12 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // FIXME: To faithfully extract an API we should extract the annotation tree, // sbt instead wants us to extract the annotation type and its arguments, // to do this properly we would need a way to hash trees and types in dotty itself, - // instead we pretty-print the annotation tree. + // instead we use the raw string representation of the annotation tree. // However, we still need to extract the annotation type in the way sbt expect // because sbt uses this information to find tests to run (for example // junit tests are annotated @org.junit.Test). api.Annotation.of( apiType(annot.tree.tpe), // Used by sbt to find tests to run - Array(api.AnnotationArgument.of("FULLTREE", annot.tree.show))) + Array(api.AnnotationArgument.of("FULLTREE", annot.tree.toString))) } } From 12773ecd26db2d8c0c3932b164ce1262878b38c9 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Thu, 27 Feb 2020 14:41:35 +0100 Subject: [PATCH 26/31] revert ascii-doc change --- docs/docs/reference/new-types/dependent-function-types.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/new-types/dependent-function-types.md b/docs/docs/reference/new-types/dependent-function-types.md index 9b1bc236ce21..b7003d7c66fc 100644 --- a/docs/docs/reference/new-types/dependent-function-types.md +++ b/docs/docs/reference/new-types/dependent-function-types.md @@ -11,9 +11,9 @@ trait Entry { type Key; val key: Key } def extractKey(e: Entry): e.Key = e.key // a dependent method val extractor: (e: Entry) => e.Key = extractKey // a dependent function value // ║ ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ⇓ ║ -// ║ Dependent ║ -// ║ Function Type ║ -// ╚═══════════════╝ +// ║ Dependent ║ +// ║ Function Type ║ +// ╚═══════════════════╝ ``` Scala already has _dependent methods_, i.e. methods where the result type refers to some of the parameters of the method. Method From 1e2506b253a444290ab685175b85dd5f3483d6a1 Mon Sep 17 00:00:00 2001 From: Lander Lopez Date: Thu, 12 Dec 2019 15:28:29 +0000 Subject: [PATCH 27/31] Fix exhaustivity issue required to publish bootstrapped --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 71faaa9423e7..096815b68b92 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -725,7 +725,7 @@ class Namer { typer: Typer => moduleDef.getOrElse(name.moduleClassName, EmptyTree) match { case t: TypeDef => createLinks(cdef, t) - case EmptyTree => + case Thicket(_) => } // If a top-level object or class has no companion in the current run, we From 2a6e1d0893cfa7413d895c5ddc8c9efbc0790c2f Mon Sep 17 00:00:00 2001 From: Lander Lopez Date: Sun, 29 Sep 2019 14:38:43 +0100 Subject: [PATCH 28/31] Fix-#4867 Keep Unions when explicit Improves the inference of union types by preserving the union when there is a type ascription with an union. If the term has not been explicity ascribed with an union then the existing semantics of joining the orType is maintained. To determine whether an explicit union ascription exists, a lexical check on the untyped tree is performed. --- compiler/src/dotty/tools/dotc/core/Types.scala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1f23faa69fd5..2a15090724d6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -24,6 +24,7 @@ import util.SimpleIdentitySet import reporting.diagnostic.Message import ast.tpd._ import ast.TreeTypeMap +import ast.untpd import printing.Texts._ import printing.Printer import Hashable._ @@ -39,6 +40,7 @@ import java.lang.ref.WeakReference import scala.annotation.internal.sharable import scala.annotation.threadUnsafe +import dotty.tools.dotc.ast.Trees.Untyped import dotty.tools.dotc.transform.SymUtils._ @@ -1113,7 +1115,7 @@ object Types { * re-lubbing it while allowing type parameters to be constrained further. * Any remaining union types are replaced by their joins. * - * For instance, if `A` is an unconstrained type variable, then + * For instance, if `A` is an unconstrained type variable, then * * ArrayBuffer[Int] | ArrayBuffer[A] * @@ -1122,6 +1124,8 @@ object Types { * * Exception (if `-YexplicitNulls` is set): if this type is a nullable union (i.e. of the form `T | Null`), * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. + * + * Another exception is when there is an explicit type ascription, then the union isn't widened. */ def widenUnion(implicit ctx: Context): Type = widen match { case tp @ OrNull(tp1): OrType => @@ -1136,7 +1140,14 @@ object Types { def widenUnionWithoutNull(implicit ctx: Context): Type = widen match { case tp @ OrType(lhs, rhs) => ctx.typeComparer.lub(lhs.widenUnionWithoutNull, rhs.widenUnionWithoutNull, canConstrain = true) match { - case union: OrType => union.join + case union: OrType => + val keepUnion = ctx.tree match { + case DefDef(_, _, _, untpd.TypedSplice(_), _) => true + case ValDef(name, untpd.InfixOp(_, op, _), _) => op.symbol == ctx.definitions.orType + case _ => false + } + if (keepUnion) union else union.join + case res => res } case tp @ AndType(tp1, tp2) => From 16288bcde07e7d7dfc5575c2340823dda03ea28b Mon Sep 17 00:00:00 2001 From: Lander Lopez Date: Mon, 30 Sep 2019 14:50:31 +0100 Subject: [PATCH 29/31] Fix-#4867 Update union tests to improve inference when ascribed Splits union.scala test from negative to negative and positive tests, exhibiting the preservation of the union when the union has been explicitly ascribed as per fix #4867. --- tests/neg/union.scala | 22 +++++----------------- tests/pos/union.scala | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 tests/pos/union.scala diff --git a/tests/neg/union.scala b/tests/neg/union.scala index c594e83d74bc..fc37b68291a4 100644 --- a/tests/neg/union.scala +++ b/tests/neg/union.scala @@ -3,26 +3,14 @@ object Test { class A class B extends A class C extends A - class D extends A val b = true - val x = if (b) new B else new C - val y: B | C = x // error -} - -object O { - class A - class B - def f[T](x: T, y: T): T = x - - val x: A = f(new A { }, new A) - - val y1: A | B = f(new A { }, new B) // error - val y2: A | B = f[A | B](new A { }, new B) // ok - val z = if (???) new A{} else new B + val x: B | C = if (b) new B else new C - val z1: A | B = z // error + val y: B | C = x + val ok: B | C = y // ok, as x and y are ascribed with unions - val z2: A | B = if (???) new A else new B // ok + val z = x + val error: B | C = z // error, as z is not ascribed with an union } diff --git a/tests/pos/union.scala b/tests/pos/union.scala new file mode 100644 index 000000000000..bdec7d46277c --- /dev/null +++ b/tests/pos/union.scala @@ -0,0 +1,27 @@ +object TestUnion { + + class A + class B extends A + class C extends A + class D extends A + + val b = true + val x: B | C = if (b) new B else new C + val y: B | C = x +} + +object TestUnion2 { + class A + class B + def f[T](x: T, y: T): T = x + + val x: A = f(new A { }, new A) + + val y1: A | B = f(new A { }, new B) + val y2: A | B = f[A | B](new A { }, new B) + + val z: A | B = if (???) new A{} else new B + + val z1: A | B = z + val z2: A | B = if (???) new A else new B +} From 735bed5378453a775b0defd76af8b102fb69202e Mon Sep 17 00:00:00 2001 From: Lander Lopez Date: Fri, 20 Dec 2019 22:23:43 +0000 Subject: [PATCH 30/31] Fix-#4867 provide positive test for fix --- tests/pos/i4867.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/pos/i4867.scala diff --git a/tests/pos/i4867.scala b/tests/pos/i4867.scala new file mode 100644 index 000000000000..3c03340dd067 --- /dev/null +++ b/tests/pos/i4867.scala @@ -0,0 +1,15 @@ +object Main_i4867 { + + given extension [AB](ab: AB) { + def id: AB = ab + } + + def main(args : Array[String]) : Unit = { + val int : Int = 2 + val foundInt : Int = int.id + + val stringOrInt : String | Int = 1 + val foundAny : String | Int = stringOrInt.id + } + +} From bf00a1e8c24ba8623d65509c61ffda4aeba28a26 Mon Sep 17 00:00:00 2001 From: Lander Lopez Date: Thu, 16 Jan 2020 19:56:11 +0000 Subject: [PATCH 31/31] Fix unsafe cast --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index fbb02378df87..6295e2a389f5 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2018,7 +2018,7 @@ object SymDenotations { var names = Set[Name]() def maybeAdd(name: Name) = if (keepOnly(thisType, name)) names += name try { - for (p <- classParents) + for (p <- classParents if p.classSymbol.isClass) for (name <- p.classSymbol.asClass.memberNames(keepOnly)) maybeAdd(name) val ownSyms =