Skip to content

Fix #4867 Union type inference is too eager to widen unions #7829

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 51 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3c9f377
Fix #8203: handle intersection type in parent registration
liufengyun Feb 5, 2020
3a1f9f6
Fix #7597: Refine checks whether a deferred term member is implemented
odersky Feb 17, 2020
4ae6522
Fix #8333: Check for duplicate symbols in exports
odersky Feb 18, 2020
dc8a4d8
Fix #8355: REPL tests : fix for two tests failing on Windows
michelou Feb 22, 2020
b6737a8
small code change to address review
michelou Feb 22, 2020
f51bf1b
Merge pull request #8356 from michelou/dotty-multiline
smarter Feb 22, 2020
93f65c6
Merge pull request #8332 from dotty-staging/fix-#7597
smarter Feb 22, 2020
811dc19
Merge pull request #8341 from dotty-staging/fix-#8333
smarter Feb 22, 2020
d7ee473
doc(multi-staging): fix typos
robstoll Feb 24, 2020
946604b
doc(multi-staging): more typos
robstoll Feb 24, 2020
c66a33e
Disable Mill libraries
anatoliykmetyuk Feb 20, 2020
5490f95
Fix documentation generation
nicolasstucki Feb 24, 2020
3e68e93
Merge pull request #8351 from lampepfl/anatoliykmetyuk-patch-2
nicolasstucki Feb 24, 2020
e1b2378
Move quote tagging to Staging
nicolasstucki Feb 13, 2020
28c96ad
doc(dep-fun-type): typos
robstoll Feb 24, 2020
082841d
doc(operators): typo and add import to examples
robstoll Feb 24, 2020
57efb73
Merge pull request #8365 from dotty-staging/fix-doc-generation
smarter Feb 25, 2020
0e847de
Merge pull request #8342 from dotty-staging/fix-#8302
nicolasstucki Feb 25, 2020
802ed3b
doc(untupling): fix list at the end, add newline
robstoll Feb 25, 2020
8b5ddf5
doc(context-functions): typo `are` instead of `is`
robstoll Feb 25, 2020
b344239
doc(multi-staging): move setup/flags to create a new project
robstoll Feb 25, 2020
5dcb63a
doc(multi-staging): cleanup new lines
robstoll Feb 25, 2020
8df5b70
doc(macros): fix expansion and some improvements
robstoll Feb 25, 2020
04d5068
Merge pull request #8206 from dotty-staging/fix-8203
liufengyun Feb 25, 2020
4922e7f
doc(operators): wrap one more annotation
robstoll Feb 26, 2020
e829c77
doc(trait param): state which rule is violated
robstoll Feb 26, 2020
91bdeef
doc(creator apply): typos
robstoll Feb 26, 2020
9964ec8
doc(export): add link to note
robstoll Feb 26, 2020
92a3952
Fix lampepfl/dotty-knowledge#30
Uko Feb 26, 2020
764a23e
Merge pull request #8382 from Uko/community-test-rerun
anatoliykmetyuk Feb 26, 2020
d7e0212
Fix lampepfl/dotty-knowledge#17
Uko Feb 26, 2020
6ac98d4
Merge pull request #8384 from Uko/test-sbt-method-annotation
anatoliykmetyuk Feb 26, 2020
0803aff
Fix #8362: Fail compilation if a compile time error can't be inlined …
Uko Feb 26, 2020
b952d41
Avoid overcompilation involving inline or annotation trees
smarter Feb 22, 2020
08f876e
Merge pull request #8359 from dotty-staging/overcompilation-print
smarter Feb 26, 2020
1955f75
Merge pull request #8381 from robstoll/patch-36
smarter Feb 26, 2020
84ea41a
Merge pull request #8387 from Uko/compiletime-error-with-s-interpolator
nicolasstucki Feb 27, 2020
f8e65de
Merge pull request #8374 from robstoll/patch-33
smarter Feb 27, 2020
248009c
Merge pull request #8380 from robstoll/patch-35
smarter Feb 27, 2020
c6f46b5
Merge pull request #8379 from robstoll/patch-34
smarter Feb 27, 2020
8f2aabe
Merge pull request #8363 from robstoll/patch-28
smarter Feb 27, 2020
11bd978
Merge pull request #8372 from robstoll/patch-31
smarter Feb 27, 2020
d943616
Merge pull request #8373 from robstoll/patch-32
smarter Feb 27, 2020
12773ec
revert ascii-doc change
robstoll Feb 27, 2020
5134e98
Merge pull request #8367 from robstoll/patch-30
smarter Feb 27, 2020
63343d2
Merge pull request #8366 from robstoll/patch-29
smarter Feb 27, 2020
1e2506b
Fix exhaustivity issue required to publish bootstrapped
landerlo Dec 12, 2019
2a6e1d0
Fix-#4867 Keep Unions when explicit
landerlo Sep 29, 2019
16288bc
Fix-#4867 Update union tests to improve inference when ascribed
landerlo Sep 30, 2019
735bed5
Fix-#4867 provide positive test for fix
landerlo Dec 20, 2019
bf00a1e
Fix unsafe cast
landerlo Jan 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -344,10 +355,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()
Expand Down
13 changes: 13 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StagingContext.scala
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
Expand All @@ -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.
*/
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -2383,7 +2383,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
}

Expand Down
15 changes: 13 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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._

Expand Down Expand Up @@ -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]
*
Expand All @@ -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 =>
Expand All @@ -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) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 5 additions & 6 deletions compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)))
}
}
69 changes: 60 additions & 9 deletions compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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"""
Expand All @@ -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)
}

}

}
Loading