diff --git a/src/dotty/language.scala b/src/dotty/language.scala new file mode 100644 index 000000000000..169b2604c97e --- /dev/null +++ b/src/dotty/language.scala @@ -0,0 +1,16 @@ +package dotty + +object language { + + class Feature + + /** Allow higher-kinded type syntax (not yet checked) */ + val higherKinds = new Feature + + /** Keep union types */ + val keepUnions = new Feature + + /** No auto tupling */ + val noAutoTupling = new Feature + +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 027c3238d91c..0c741a652c58 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -180,8 +180,9 @@ object desugar { constr1.mods, constr1.name, tparams, vparamss, constr1.tpt, constr1.rhs) // a reference to the class type, with all parameters given. - val classTypeRef: Tree = { - // Dotty deviation: Without type annotation infers Ident | AppliedTypeTree, which + val classTypeRef/*: Tree*/ = { + // -language:keepUnions difference: classTypeRef needs type annotation, otherwise + // infers Ident | AppliedTypeTree, which // renders the :\ in companions below untypable. val tycon = Ident(cdef.name) withPos cdef.pos.startPos val tparams = impl.constr.tparams @@ -728,7 +729,7 @@ object desugar { */ private object VarPattern { def unapply(tree: Tree)(implicit ctx: Context): Option[VarInfo] = tree match { - case id: Ident => Some((id, TypeTree())) // Dotty deviation: No auto-tupling + case id: Ident => Some(id, TypeTree()) case Typed(id: Ident, tpt) => Some((id, tpt)) case _ => None } diff --git a/src/dotty/tools/dotc/config/Settings.scala b/src/dotty/tools/dotc/config/Settings.scala index 7f2ad2a4fbd7..b3bf2f1348be 100644 --- a/src/dotty/tools/dotc/config/Settings.scala +++ b/src/dotty/tools/dotc/config/Settings.scala @@ -60,7 +60,7 @@ object Settings { copy(aliases = aliases :+ abbrv)(idx) def dependsOn[U](setting: Setting[U], value: U): Setting[T] = - copy(depends = depends :+ ((setting, value)))(idx) // Dotty deviation: no auto-tupling + copy(depends = depends :+ (setting, value))(idx) def valueIn(state: SettingsState): T = state.value(idx).asInstanceOf[T] diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 68daea44059f..8780b0bf6268 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -273,7 +273,7 @@ object Contexts { newctx.implicitsCache = null newctx.setCreationTrace() // Dotty deviation: Scala2x allows access to private members implicitCache and setCreationTrace - // even from a subclass prefix. Dotty (and Java) do not. I think that's a bug in Scala2x. + // even from a subclass prefix. Dotty (and Java) do not. It's confirmed as a bug in Scala2x. newctx.asInstanceOf[FreshContext] } diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 5f6698b33aaa..a58bfc05816d 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -215,6 +215,7 @@ class Definitions { lazy val JavaSerializableClass = ctx.requiredClass("java.lang.Serializable") lazy val ComparableClass = ctx.requiredClass("java.lang.Comparable") lazy val ProductClass = ctx.requiredClass("scala.Product") + lazy val LanguageModuleClass = ctx.requiredModule("dotty.language").moduleClass.asClass // Annotation base classes lazy val AnnotationClass = ctx.requiredClass("scala.annotation.Annotation") @@ -285,13 +286,14 @@ class Definitions { object FunctionType { def apply(args: List[Type], resultType: Type) = FunctionClass(args.length).typeRef.appliedTo(args ::: resultType :: Nil) - def unapply(ft: Type): Option[(List[Type], Type)] = { // Dotty deviation: Type annotation needed because inferred type - // is Some[(List[Type], Type)] | None, which is not a legal unapply type. + def unapply(ft: Type)/*: Option[(List[Type], Type)]*/ = { + // -language:keepUnions difference: unapply needs result type because inferred type + // is Some[(List[Type], Type)] | None, which is not a legal unapply type. val tsym = ft.typeSymbol lazy val targs = ft.argInfos if ((FunctionClasses contains tsym) && (targs.length - 1 <= MaxFunctionArity) && - (FunctionClass(targs.length - 1) == tsym)) Some((targs.init, targs.last)) // Dotty deviation: no auto-tupling + (FunctionClass(targs.length - 1) == tsym)) Some(targs.init, targs.last) else None } } diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 0cbcfa5a74b9..6cd9da4b5874 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -375,6 +375,7 @@ object StdNames { val hashCode_ : N = "hashCode" val hash_ : N = "hash" val head: N = "head" + val higherKinds: N = "higherKinds" val identity: N = "identity" val implicitly: N = "implicitly" val in: N = "in" @@ -387,6 +388,7 @@ object StdNames { val isInstanceOf_ : N = "isInstanceOf" val isInstanceOf_Ob : N = "$isInstanceOf" val java: N = "java" + val keepUnions: N = "keepUnions" val key: N = "key" val lang: N = "lang" val length: N = "length" @@ -415,6 +417,7 @@ object StdNames { val next: N = "next" val nmeNewTermName: N = "newTermName" val nmeNewTypeName: N = "newTypeName" + val noAutoTupling: N = "noAutoTupling" val normalize: N = "normalize" val notifyAll_ : N = "notifyAll" val notify_ : N = "notify" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 08566e3db72d..362738caf3e8 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -9,7 +9,7 @@ import collection.mutable import collection.immutable.BitSet import scala.reflect.io.AbstractFile import Decorators.SymbolIteratorDecorator -import ast.tpd +import ast._ import annotation.tailrec import util.SimpleMap import util.Stats @@ -195,6 +195,18 @@ object SymDenotations { final def hasAnnotation(cls: Symbol)(implicit ctx: Context) = dropOtherAnnotations(annotations, cls).nonEmpty + /** Optionally, the arguments of the first annotation matching the given class symbol */ + final def getAnnotationArgs(cls: Symbol)(implicit ctx: Context): Option[List[tpd.Tree]] = + dropOtherAnnotations(annotations, cls) match { + case annot :: _ => + Some( + annot.tree match { + case Trees.Apply(_, args) => args + case _ => Nil + }) + case nil => None + } + /** Add given annotation to the annotations of this denotation */ final def addAnnotation(annot: Annotation): Unit = annotations = annot :: myAnnotations diff --git a/src/dotty/tools/dotc/core/TypeApplications.scala b/src/dotty/tools/dotc/core/TypeApplications.scala index b4c30d902976..0abd28a716ab 100644 --- a/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/src/dotty/tools/dotc/core/TypeApplications.scala @@ -170,17 +170,20 @@ class TypeApplications(val self: Type) extends AnyVal { /** The type arguments of this type's base type instance wrt.`base`. * Existential types in arguments are disallowed. */ - final def baseArgTypes(base: Symbol)(implicit ctx: Context): List[Type] = baseArgInfos(base) mapConserve noBounds + final def baseArgTypes(base: Symbol)(implicit ctx: Context): List[Type] = + baseArgInfos(base) mapConserve noBounds /** The type arguments of this type's base type instance wrt.`base`. * Existential types in arguments are approximanted by their lower bound. */ - final def baseArgTypesLo(base: Symbol)(implicit ctx: Context): List[Type] = baseArgInfos(base) mapConserve boundsToLo + final def baseArgTypesLo(base: Symbol)(implicit ctx: Context): List[Type] = + baseArgInfos(base) mapConserve boundsToLo /** The type arguments of this type's base type instance wrt.`base`. * Existential types in arguments are approximanted by their upper bound. */ - final def baseArgTypesHi(base: Symbol)(implicit ctx: Context): List[Type] = baseArgInfos(base) mapConserve boundsToHi + final def baseArgTypesHi(base: Symbol)(implicit ctx: Context): List[Type] = + baseArgInfos(base) mapConserve boundsToHi /** The first type argument of the base type instance wrt `base` of this type */ final def firstBaseArgInfo(base: Symbol)(implicit ctx: Context): Type = base.typeParams match { @@ -193,8 +196,11 @@ class TypeApplications(val self: Type) extends AnyVal { /** The base type including all type arguments of this type. * Existential types in arguments are returned as TypeBounds instances. */ - final def baseTypeWithArgs(base: Symbol)(implicit ctx: Context): Type = - self.baseTypeRef(base).appliedTo(baseArgInfos(base)) + final def baseTypeWithArgs(base: Symbol)(implicit ctx: Context): Type = self.dealias match { + case AndType(tp1, tp2) => tp1.baseTypeWithArgs(base) & tp2.baseTypeWithArgs(base) + case OrType(tp1, tp2) => tp1.baseTypeWithArgs(base) | tp2.baseTypeWithArgs(base) + case _ => self.baseTypeRef(base).appliedTo(baseArgInfos(base)) + } /** Translate a type of the form From[T] to To[T], keep other types as they are. * `from` and `to` must be static classes, both with one type parameter, and the same variance. @@ -205,7 +211,7 @@ class TypeApplications(val self: Type) extends AnyVal { else self /** If this is an encoding of a (partially) applied type, return its arguments, - * otherwise return Nil. + * otherwise return Nil. * Existential types in arguments are returned as TypeBounds instances. */ final def argInfos(implicit ctx: Context): List[Type] = { diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index da8263ac1a3f..cfb9477c3a11 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -890,6 +890,19 @@ class TypeComparer(initctx: Context) extends DotClass { /** Try to distribute `&` inside type, detect and handle conflicts */ private def distributeAnd(tp1: Type, tp2: Type): Type = tp1 match { + // opportunistically merge same-named refinements + // this does not change anything semantically (i.e. merging or not merging + // gives =:= types), but it keeps the type smaller. + case tp1: RefinedType => + tp2 match { + case tp2: RefinedType if tp1.refinedName == tp2.refinedName => + tp1.derivedRefinedType( + tp1.parent & tp2.parent, + tp1.refinedName, + tp1.refinedInfo & tp2.refinedInfo) + case _ => + NoType + } case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 & tp2 @@ -935,18 +948,6 @@ class TypeComparer(initctx: Context) extends DotClass { case _ => rt1 & tp2 } - case tp1: RefinedType => - // opportunistically merge same-named refinements - // this does not change anything semantically (i.e. merging or not merging - // gives =:= types), but it keeps the type smaller. - tp2 match { - case tp2: RefinedType if tp1.refinedName == tp2.refinedName => - tp1.derivedRefinedType( - tp1.parent & tp2.parent, tp1.refinedName, - tp1.refinedInfo & tp2.refinedInfo) - case _ => - NoType - } case tp1: TypeVar if tp1.isInstantiated => tp1.underlying & tp2 case tp1: AnnotatedType => @@ -957,6 +958,16 @@ class TypeComparer(initctx: Context) extends DotClass { /** Try to distribute `|` inside type, detect and handle conflicts */ private def distributeOr(tp1: Type, tp2: Type): Type = tp1 match { + case tp1: RefinedType => + tp2 match { + case tp2: RefinedType if tp1.refinedName == tp2.refinedName => + tp1.derivedRefinedType( + tp1.parent | tp2.parent, + tp1.refinedName, + tp1.refinedInfo | tp2.refinedInfo) + case _ => + NoType + } case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => tp1 | tp2 diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 152f8d1b7a12..f9ff4270925a 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -5,6 +5,7 @@ import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ import SymDenotations._ import config.Printers._ import Decorators._ +import StdNames._ import util.SimpleMap trait TypeOps { this: Context => @@ -76,6 +77,37 @@ trait TypeOps { this: Context => def apply(tp: Type) = simplify(tp, this) } + /** Approximate union type by intersection of its dominators. + * See Type#approximateUnion for an explanation. + */ + def approximateUnion(tp: Type): Type = { + /** a faster version of cs1 intersect cs2 */ + def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { + val cs2AsSet = new util.HashSet[ClassSymbol](100) + cs2.foreach(cs2AsSet.addEntry) + cs1.filter(cs2AsSet.contains) + } + /** The minimal set of classes in `cs` which derive all other classes in `cs` */ + def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match { + case c :: rest => + val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu + if (cs == c.baseClasses) accu1 else dominators(rest, accu1) + } + if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp + else tp match { + case tp: OrType => + val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) + val doms = dominators(commonBaseClasses, Nil) + doms.map(tp.baseTypeWithArgs).reduceLeft(AndType.apply) + case tp @ AndType(tp1, tp2) => + tp derived_& (approximateUnion(tp1), approximateUnion(tp2)) + case tp: RefinedType => + tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo) + case _ => + tp + } + } + /** A type is volatile if its DNF contains an alternative of the form * {P1, ..., Pn}, {N1, ..., Nk}, where the Pi are parent typerefs and the * Nj are refinement names, and one the 4 following conditions is met: @@ -266,6 +298,44 @@ trait TypeOps { this: Context => } parentRefs } + + /** Is `feature` enabled in class `owner`? + * This is the case if one of the following two alternatives holds: + * + * 1. The feature is imported by a named import + * + * import owner.feature + * + * (the feature may be bunched with others, or renamed, but wildcard imports + * don't count). + * + * 2. The feature is enabled by a compiler option + * + * - language:feature + * + * where is the full name of the owner followed by a "." minus + * the prefix "dotty.language.". + */ + def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = { + def toPrefix(sym: Symbol): String = + if (sym eq defn.LanguageModuleClass) "" else toPrefix(sym.owner) + sym.name + "." + def featureName = toPrefix(owner) + feature + def hasImport(implicit ctx: Context): Boolean = ( + ctx.importInfo != null + && ( (ctx.importInfo.site.widen.typeSymbol eq owner) + && ctx.importInfo.originals.contains(feature) + || + { var c = ctx.outer + while (c.importInfo eq ctx.importInfo) c = c.outer + hasImport(c) + })) + def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_") + hasImport || hasOption + } + + /** Is auto-tupling enabled? */ + def canAutoTuple = + !featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling) } object TypeOps { diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 6dda937d2c0b..9b5e02186199 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -102,8 +102,19 @@ object Types { } /** Is this type an instance of a non-bottom subclass of the given class `cls`? */ - final def derivesFrom(cls: Symbol)(implicit ctx: Context): Boolean = - classSymbol.derivesFrom(cls) + final def derivesFrom(cls: Symbol)(implicit ctx: Context): Boolean = this match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.isClass) sym.derivesFrom(cls) else tp.underlying.derivesFrom(cls) + case tp: TypeProxy => + tp.underlying.derivesFrom(cls) + case tp: AndType => + tp.tp1.derivesFrom(cls) || tp.tp2.derivesFrom(cls) + case tp: OrType => + tp.tp1.derivesFrom(cls) && tp.tp2.derivesFrom(cls) + case _ => + false + } /** A type T is a legal prefix in a type selection T#A if * T is stable or T contains no abstract types @@ -254,6 +265,7 @@ object Types { /** The base classes of this type as determined by ClassDenotation * in linearization order, with the class itself as first element. + * For AndTypes/OrTypes, the union/intersection of the operands' baseclasses. * Inherited by all type proxies. `Nil` for all other types. */ final def baseClasses(implicit ctx: Context): List[ClassSymbol] = track("baseClasses") { @@ -262,6 +274,10 @@ object Types { tp.underlying.baseClasses case tp: ClassInfo => tp.cls.baseClasses + case AndType(tp1, tp2) => + tp1.baseClasses union tp2.baseClasses + case OrType(tp1, tp2) => + tp1.baseClasses intersect tp2.baseClasses case _ => Nil } } @@ -685,6 +701,13 @@ object Types { case _ => Nil } + /** The parameter types in the first parameter section of a PolyType or MethodType, Empty list for others */ + final def firstParamTypes: List[Type] = this match { + case mt: MethodType => mt.paramTypes + case pt: PolyType => pt.resultType.firstParamTypes + case _ => Nil + } + /** Is this either not a method at all, or a parameterless method? */ final def isParameterless: Boolean = this match { case mt: MethodType => false @@ -822,6 +845,19 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) + /** Approximations of union types: We replace a union type Tn | ... | Tn + * by the smallest intersection type of baseclass instances of T1,...,Tn. + * Example: Given + * + * trait C[+T] + * trait D + * class A extends C[A] with D + * class B extends C[B] with D with E + * + * we approximate `A | B` by `C[A | B] with D` + */ + def approximateUnion(implicit ctx: Context) = ctx.approximateUnion(this) + /** customized hash code of this type. * NotCached for uncached types. Cached types * compute hash and use it as the type's hashCode. @@ -1332,6 +1368,10 @@ object Types { if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this else AndType.make(tp1, tp2) + def derived_& (tp1: Type, tp2: Type)(implicit ctx: Context): Type = + if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this + else tp1 & tp2 + def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedAndType(tp1, tp2) @@ -1712,10 +1752,38 @@ object Types { case OrType(tp1, tp2) => isSingleton(tp1) & isSingleton(tp2) case _ => false } + def isFullyDefined(tp: Type): Boolean = tp match { + case tp: TypeVar => tp.isInstantiated && isFullyDefined(tp.instanceOpt) + case tp: TypeProxy => isFullyDefined(tp.underlying) + case tp: AndOrType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2) + case _ => true + } + def isOrType(tp: Type): Boolean = tp.stripTypeVar.dealias match { + case tp: OrType => true + case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2) + case RefinedType(parent, _) => isOrType(parent) + case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi) + case _ => false + } + + // First, solve the constraint. var inst = ctx.typeComparer.approximation(origin, fromBelow) + + // Then, approximate by (1.) and (2.) and simplify as follows. + // 1. If instance is from below and is a singleton type, yet + // upper bound is not a singleton type, widen the instance. if (fromBelow && isSingleton(inst) && !isSingleton(upperBound)) inst = inst.widen - instantiateWith(inst.simplified) + + inst = inst.simplified + + // 2. If instance is from below and is a fully-defined union type, yet upper bound + // is not a union type, approximate the union type from above by an intersection + // of all common base types. + if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) + inst = inst.approximateUnion + + instantiateWith(inst) } /** Unwrap to instance (if instantiated) or origin (if not), until result @@ -1856,7 +1924,7 @@ object Types { def | (that: TypeBounds)(implicit ctx: Context): TypeBounds = { val v = this commonVariance that - if (v == 0 && (this.lo eq this.hi) && (that.lo eq that.hi)) + if (v != 0 && (this.lo eq this.hi) && (that.lo eq that.hi)) if (v > 0) derivedTypeAlias(this.hi | that.hi, v) else derivedTypeAlias(this.lo & that.lo, v) else derivedTypeBounds(this.lo & that.lo, this.hi | that.hi, v) diff --git a/src/dotty/tools/dotc/core/pickling/PickleBuffer.scala b/src/dotty/tools/dotc/core/pickling/PickleBuffer.scala index 2bedcab92cfb..ef2b4acb2700 100644 --- a/src/dotty/tools/dotc/core/pickling/PickleBuffer.scala +++ b/src/dotty/tools/dotc/core/pickling/PickleBuffer.scala @@ -229,8 +229,8 @@ object PickleBuffer { PARAM -> Param, PACKAGE -> Package, MACRO -> Macro, - BYNAMEPARAM -> ((Method, Covariant)), // Dotty deviation: no auto-tupling - LABEL -> ((Label, Contravariant)), // Dotty deviation: no auto-tupling + BYNAMEPARAM -> (Method, Covariant), + LABEL -> (Label, Contravariant), ABSOVERRIDE -> AbsOverride, LOCAL -> Local, JAVA -> JavaDefined, @@ -238,16 +238,16 @@ object PickleBuffer { STABLE -> Stable, STATIC -> Static, CASEACCESSOR -> CaseAccessor, - DEFAULTPARAM -> ((DefaultParameterized, Trait)), // Dotty deviation: no auto-tupling + DEFAULTPARAM -> (DefaultParameterized, Trait), BRIDGE -> Bridge, ACCESSOR -> Accessor, SUPERACCESSOR -> SuperAccessor, PARAMACCESSOR -> ParamAccessor, MODULEVAR -> Scala2ModuleVar, LAZY -> Lazy, - MIXEDIN -> ((MixedIn, Scala2Existential)), // Dotty deviation: no auto-tupling + MIXEDIN -> (MixedIn, Scala2Existential), EXPANDEDNAME -> ExpandedName, - IMPLCLASS -> ((Scala2PreSuper, ImplClass)), // Dotty deviation: no auto-tupling + IMPLCLASS -> (Scala2PreSuper, ImplClass), SPECIALIZED -> Specialized, DEFAULTINIT -> DefaultInit, VBRIDGE -> VBridge, diff --git a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index 483228693d5b..93e24b27f04f 100644 --- a/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -21,7 +21,7 @@ trait UniqueMessagePositions extends Reporter { override def isHidden(d: Diagnostic)(implicit ctx: Context): Boolean = super.isHidden(d) || { d.pos.exists && { - positions get ((ctx.source, d.pos.point)) match { // Dotty deviation: no autotupling + positions get (ctx.source, d.pos.point) match { case Some(s) if s.level >= d.severity.level => true case _ => positions((ctx.source, d.pos.point)) = d.severity; false } diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index feaf678a2210..4d26532d656e 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -434,8 +434,17 @@ trait Applications extends Compatibility { self: Typer => def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { def realApply(implicit ctx: Context): Tree = track("realApply") { - val proto = new FunProto(tree.args, pt, this) + var proto = new FunProto(tree.args, pt, this) val fun1 = typedExpr(tree.fun, proto) + + // Warning: The following line is dirty and fragile. We record that auto-tupling was demanded as + // a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application. + // This crucially relies on he fact that `proto` is used only in a single call of `adapt`, + // otherwise we would get possible cross-talk between different `adapt` calls using the same + // prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with + // a modified tree but this would be more convoluted and less efficient. + if (proto.isTupled) proto = proto.tupled + methPart(fun1).tpe match { case funRef: TermRef => tryEither { implicit ctx => @@ -676,7 +685,10 @@ trait Applications extends Compatibility { self: Typer => var argTypes = unapplyArgs(unapplyApp.tpe) for (argType <- argTypes) assert(!argType.isInstanceOf[TypeBounds], unapplyApp.tpe.show) val bunchedArgs = argTypes match { - case argType :: Nil if argType.isRepeatedParam => untpd.SeqLiteral(args) :: Nil + case argType :: Nil => + if (argType.isRepeatedParam) untpd.SeqLiteral(args) :: Nil + else if (args.lengthCompare(1) > 0 && ctx.canAutoTuple) untpd.Tuple(args) :: Nil + else args case _ => args } if (argTypes.length != bunchedArgs.length) { diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 2c5022726098..c318ddf3699c 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -576,7 +576,7 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") val rhsCtx = ctx.fresh addMode Mode.InferringReturnType - def rhsType = typedAheadExpr(mdef.rhs, rhsProto)(rhsCtx).tpe.widen + def rhsType = typedAheadExpr(mdef.rhs, rhsProto)(rhsCtx).tpe.widen.approximateUnion def lhsType = fullyDefinedType(rhsType, "right-hand side", mdef.pos) inherited orElse lhsType } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index b4068408b4ab..74be3f4cd1c6 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -187,6 +187,20 @@ object ProtoTypes { typer.adapt(targ, formal) } + private var myTupled: Type = NoType + + /** The same proto-type but with all arguments combined in a single tuple */ + def tupled: FunProto = myTupled match { + case pt: FunProto => + pt + case _ => + myTupled = new FunProto(untpd.Tuple(args) :: Nil, resultType, typer) + tupled + } + + /** Somebody called the `tupled` method of this prototype */ + def isTupled: Boolean = myTupled.isInstanceOf[FunProto] + override def toString = s"FunProto(${args mkString ","} => $resultType)" def map(tm: TypeMap)(implicit ctx: Context): FunProto = diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 17c77be8994a..f961a4544dd9 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1072,8 +1072,16 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def adaptToArgs(wtp: Type, pt: FunProto) = wtp match { - case _: MethodType | _: PolyType => tree + def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { + case _: MethodType | _: PolyType => + def isUnary = wtp.firstParamTypes match { + case ptype :: Nil => !ptype.isRepeatedParam + case _ => false + } + if (pt.args.lengthCompare(1) > 0 && isUnary && ctx.canAutoTuple) + adaptToArgs(wtp, pt.tupled) + else + tree case _ => tryInsertApply(tree, pt) { val more = tree match { case Apply(_, _) => " more" diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 54d9cfca65b1..0caef3cae20f 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -43,6 +43,7 @@ class tests extends CompilerTest { @Test def pos_structural() = compileFile(posDir, "structural", twice) @Test def pos_i39 = compileFile(posDir, "i39", twice) @Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", twice) + @Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", twice) */ @Test def pos_all = compileFiles(posDir, twice) @@ -57,6 +58,8 @@ class tests extends CompilerTest { @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1) @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4) @Test def neg_companions = compileFile(negDir, "companions", xerrors = 1) + @Test def neg_autoTupling = compileFile(posDir, "autoTuplingTest", "-language:noAutoTupling" :: Nil, xerrors = 3) + @Test def neg_autoTupling2 = compileFile(negDir, "autoTuplingTest", xerrors = 3) @Test def dotc = compileDir(dotcDir + "tools/dotc", twice) @Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice) diff --git a/tests/neg/autoTuplingTest.scala b/tests/neg/autoTuplingTest.scala new file mode 100644 index 000000000000..d4a698271391 --- /dev/null +++ b/tests/neg/autoTuplingTest.scala @@ -0,0 +1,11 @@ +import dotty.language.noAutoTupling + +object autoTuplingNeg { + + val x = Some(1, 2) + + x match { + case Some(a, b) => a + b + case None => + } +} diff --git a/tests/pos/approximateUnion.scala b/tests/pos/approximateUnion.scala new file mode 100644 index 000000000000..c3fe0e1625f5 --- /dev/null +++ b/tests/pos/approximateUnion.scala @@ -0,0 +1,96 @@ +object approximateUnion { + + trait C[+T] + trait D + trait E + trait X[-T] + + { + trait A extends C[A] with D + trait B extends C[B] with D + + val coin = true + val x = if (coin) new A else new B + val y = Some(if (coin) new A else new B) + + val xtest: C[A | B] & D = x + val ytest: Some[C[A | B] & D] = y + } + + { + trait A extends C[X[A]] with D + trait B extends C[X[B]] with D with E + + val coin = true + val x = if (coin) new A else new B + val y = Some(if (coin) new A else new B) + + val xtest: C[X[A & B]] & D = x + val ytest: Some[C[X[A & B]] & D] = y + } +} + +object approximateUnion2 { + + trait C[T] + trait D + trait E + trait X[-T] + + { + trait A extends C[A] with D + trait B extends C[B] with D + + val coin = true + val x = if (coin) new A else new B + val y = Some(if (coin) new A else new B) + + val xtest: C[_ >: A & B <: A | B] & D = x + val ytest: Some[C[_ >: A & B <: A | B] & D] = y + } + + { + trait A extends C[X[A]] with D + trait B extends C[X[B]] with D with E + + val coin = true + val x = if (coin) new A else new B + val y = Some(if (coin) new A else new B) + + val xtest: C[_ >: X[A | B] <: X[A & B]] & D = x + val ytest: Some[C[_ >: X[A | B] <: X[A & B]]] = y + } +} + +object approximateUnion3 { + + trait C[-T] + trait D + trait E + trait X[-T] + + { + trait A extends C[A] with D + trait B extends C[B] with D + + val coin = true + val x = if (coin) new A else new B + val y = Some(if (coin) new A else new B) + + val xtest: C[A & B] & D = x + val ytest: Some[C[A & B] & D] = y + } + + { + trait A extends C[X[A]] with D + trait B extends C[X[B]] with D with E + + val coin = true + val x = if (coin) new A else new B + val y = Some(if (coin) new A else new B) + + val xtest: C[X[A | B]] & D = x + val ytest2: Some[C[X[A | B]] & D] = y + } +} + diff --git a/tests/pos/autoTuplingTest.scala b/tests/pos/autoTuplingTest.scala new file mode 100644 index 000000000000..523411a1a410 --- /dev/null +++ b/tests/pos/autoTuplingTest.scala @@ -0,0 +1,9 @@ +object autoTupling { + + val x = Some(1, 2) + + x match { + case Some(a, b) => a + b + case None => + } +}