diff options
47 files changed, 804 insertions, 151 deletions
diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index dd890dc50..b00d7df71 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -26,23 +26,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ => false } - /** The largest subset of {NoInits, PureInterface} that a - * trait enclosing this statement can have as flags. - * Does tree contain an initialization part when seen as a member of a class or trait? - */ - def defKind(tree: Tree): FlagSet = unsplice(tree) match { - case EmptyTree | _: Import => - NoInitsInterface - case tree: TypeDef => - if (tree.isClassDef) NoInits else NoInitsInterface - case tree: DefDef => - if (tree.unforcedRhs == EmptyTree && tree.vparamss.forall(_.forall(_.unforcedRhs == EmptyTree))) NoInitsInterface else NoInits - case tree: ValDef => - if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags - case _ => - EmptyFlags - } - def isOpAssign(tree: Tree) = unsplice(tree) match { case Apply(fn, _ :: _) => unsplice(fn) match { @@ -588,6 +571,16 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => accum(Nil, root) } + /** The largest subset of {NoInits, PureInterface} that a + * trait enclosing this statement can have as flags. + */ + def defKind(tree: Tree): FlagSet = unsplice(tree) match { + case EmptyTree | _: Import => NoInitsInterface + case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface + case tree: DefDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else NoInits + case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags + case _ => EmptyFlags + } /** The top level classes in this tree, including only those module classes that * are not a linked class of some other class in the result. diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 99bc3240f..8ca91590f 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -149,6 +149,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def is(fs: FlagSet): Boolean = flags is fs def is(fc: FlagConjunction): Boolean = flags is fc + def is(fc: FlagSet, butNot: FlagSet): Boolean = flags.is(fc, butNot = butNot) def | (fs: FlagSet): Modifiers = withFlags(flags | fs) def & (fs: FlagSet): Modifiers = withFlags(flags & fs) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 119af9483..dc56ad8b8 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -75,6 +75,15 @@ object Config { /** If this flag is set, take the fast path when comparing same-named type-aliases and types */ final val fastPathForRefinedSubtype = true + /** If this flag is set, and we compute `T1 { X = S1 }` & `T2 { X = S2 }` as a new + * upper bound of a constrained parameter, try to align the refinements by computing + * `S1 =:= S2` (which might instantiate type parameters). + * This rule is contentious because it cuts the constraint set. + * + * For more info, see the comment in `TypeComparer#distributeAnd`. + */ + final val alignArgsInAnd = true + /** If this flag is set, higher-kinded applications are checked for validity */ final val checkHKApplications = false diff --git a/compiler/src/dotty/tools/dotc/core/Constraint.scala b/compiler/src/dotty/tools/dotc/core/Constraint.scala index c99b748b7..50136a26c 100644 --- a/compiler/src/dotty/tools/dotc/core/Constraint.scala +++ b/compiler/src/dotty/tools/dotc/core/Constraint.scala @@ -111,12 +111,6 @@ abstract class Constraint extends Showable { */ def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This - /** Narrow one of the bounds of type parameter `param` - * If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure - * that `param >: bound`. - */ - def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This - /** Is entry associated with `pt` removable? This is the case if * all type parameters of the entry are associated with type variables * which have their `inst` fields set. diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index b3c50fb71..2a1f4ee6e 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -44,6 +44,13 @@ trait ConstraintHandling { try op finally alwaysFluid = saved } + /** If set, align arguments `S1`, `S2`when taking the glb + * `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter. + * Aligning means computing `S1 =:= S2` which may change the current constraint. + * See note in TypeComparer#distributeAnd. + */ + protected var homogenizeArgs = false + /** We are currently comparing polytypes. Used as a flag for * optimization: when `false`, no need to do an expensive `pruneLambdaParams` */ @@ -64,7 +71,8 @@ trait ConstraintHandling { } if (Config.checkConstraintsSeparated) assert(!occursIn(bound), s"$param occurs in $bound") - val c1 = constraint.narrowBound(param, bound, isUpper) + val newBound = narrowedBound(param, bound, isUpper) + val c1 = constraint.updateEntry(param, newBound) (c1 eq constraint) || { constraint = c1 val TypeBounds(lo, hi) = constraint.entry(param) @@ -72,6 +80,20 @@ trait ConstraintHandling { } } + /** Narrow one of the bounds of type parameter `param` + * If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure + * that `param >: bound`. + */ + def narrowedBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): TypeBounds = { + val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param) + val saved = homogenizeArgs + homogenizeArgs = Config.alignArgsInAnd + try + if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) + else oldBounds.derivedTypeBounds(lo | bound, hi) + finally homogenizeArgs = saved + } + protected def addUpperBound(param: PolyParam, bound: Type): Boolean = { def description = i"constraint $param <: $bound to\n$constraint" if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3cab75f93..4d4350f98 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -299,6 +299,8 @@ class Definitions { lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol + lazy val Predef_ConformsR = ScalaPredefModule.requiredClass("$less$colon$less").typeRef + def Predef_Conforms(implicit ctx: Context) = Predef_ConformsR.symbol lazy val Predef_conformsR = ScalaPredefModule.requiredMethodRef("$conforms") def Predef_conforms(implicit ctx: Context) = Predef_conformsR.symbol lazy val Predef_classOfR = ScalaPredefModule.requiredMethodRef("classOf") @@ -336,6 +338,8 @@ class Definitions { def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny) + lazy val Predef_ImplicitConverterR = DottyPredefModule.requiredClass("ImplicitConverter").typeRef + def Predef_ImplicitConverter(implicit ctx: Context) = Predef_ImplicitConverterR.symbol lazy val DottyArraysModuleRef = ctx.requiredModuleRef("dotty.runtime.Arrays") def DottyArraysModule(implicit ctx: Context) = DottyArraysModuleRef.symbol @@ -351,6 +355,7 @@ class Definitions { enterCompleteClassSymbol( ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final, List(AnyClass.typeRef), EmptyScope) + def SingletonType = SingletonClass.typeRef lazy val SeqType: TypeRef = ctx.requiredClassRef("scala.collection.Seq") def SeqClass(implicit ctx: Context) = SeqType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index d2a1c58f5..c1267d8a2 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -287,9 +287,10 @@ object Flags { /** A trait that has only abstract methods as members - * (and therefore can be represented by a Java interface + * and therefore can be represented by a Java interface. + * Warning: flag is set during regular typer pass, should be tested only after typer. */ - final val PureInterface = typeFlag(22, "interface") // TODO when unpickling, reconstitute from context + final val PureInterface = typeFlag(22, "interface") /** Labeled with of abstract & override */ final val AbsOverride = termFlag(22, "abstract override") @@ -338,7 +339,9 @@ object Flags { final val JavaStaticTerm = JavaStatic.toTermFlags final val JavaStaticType = JavaStatic.toTypeFlags - /** Trait does not have fields or initialization code */ + /** Trait does not have fields or initialization code. + * Warning: flag is set during regular typer pass, should be tested only after typer. + */ final val NoInits = typeFlag(32, "<noInits>") /** Variable is accessed from nested function. */ diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 72c7a8e51..61dd5a445 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -354,14 +354,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds, updateEntry(p1, p1Bounds).replace(p2, p1) } - def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = { - val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param) - val newBounds = - if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound) - else oldBounds.derivedTypeBounds(lo | bound, hi) - updateEntry(param, newBounds) - } - // ---------- Removals ------------------------------------------------------------ /** A new constraint which is derived from this constraint by removing diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 766e3733f..5b7dc3d1d 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -97,6 +97,7 @@ object StdNames { val EMPTY: N = "" val EMPTY_PACKAGE: N = Names.EMPTY_PACKAGE.toString val EVIDENCE_PARAM_PREFIX: N = "evidence$" + val DEP_PARAM_PREFIX = "<param>" val EXCEPTION_RESULT_PREFIX: N = "exceptionResult" val EXPAND_SEPARATOR: N = "$$" val IMPL_CLASS_SUFFIX: N = "$class" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 9b9caf8e7..c98b444d9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -158,7 +158,7 @@ object SymDenotations { final def resetFlag(flags: FlagSet): Unit = { myFlags &~= flags } /** Set applicable flags from `flags` which is a subset of {NoInits, PureInterface} */ - final def setApplicableFlags(flags: FlagSet): Unit = { + final def setNoInitsFlags(flags: FlagSet): Unit = { val mask = if (myFlags.is(Trait)) NoInitsInterface else NoInits setFlag(flags & mask) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b97dfe684..b61fccf31 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1300,23 +1300,28 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1: RefinedType => tp2 match { case tp2: RefinedType if tp1.refinedName == tp2.refinedName => - // Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }`, if `S1 =:= S2` - // (possibly by instantiating type parameters), rewrite to `T1 & T2 { X = S1 }`. - // Otherwise rewrite to `T1 & T2 { X B }` where `B` is the conjunction of - // the bounds of `X` in `T1` and `T2`. - // The first rule above is contentious because it cuts the constraint set. - // But without it we would replace the two aliases by - // `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird and is probably - // not what's intended. + // Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }` rewrite to + // `T1 & T2 { X B }` where `B` is the conjunction of the bounds of `X` in `T1` and `T2`. + // + // However, if `homogenizeArgs` is set, and both aliases `X = Si` are + // nonvariant, and `S1 =:= S2` (possibly by instantiating type parameters), + // rewrite instead to `T1 & T2 { X = S1 }`. This rule is contentious because + // it cuts the constraint set. On the other hand, without it we would replace + // the two aliases by `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird + // and is probably not what's intended. val rinfo1 = tp1.refinedInfo val rinfo2 = tp2.refinedInfo val parent = tp1.parent & tp2.parent - val rinfo = - if (rinfo1.isAlias && rinfo2.isAlias && isSameType(rinfo1, rinfo2)) - rinfo1 - else - rinfo1 & rinfo2 - tp1.derivedRefinedType(parent, tp1.refinedName, rinfo) + + def isNonvariantAlias(tp: Type) = tp match { + case tp: TypeAlias => tp.variance == 0 + case _ => false + } + if (homogenizeArgs && + isNonvariantAlias(rinfo1) && isNonvariantAlias(rinfo2)) + isSameType(rinfo1, rinfo2) // establish new constraint + + tp1.derivedRefinedType(parent, tp1.refinedName, rinfo1 & rinfo2) case _ => NoType } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9dd767a8e..b8f81f1bb 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -19,6 +19,7 @@ import util.Positions.{Position, NoPosition} import util.Stats._ import util.{DotClass, SimpleMap} import reporting.diagnostic.Message +import reporting.diagnostic.messages.CyclicReferenceInvolving import ast.tpd._ import ast.TreeTypeMap import printing.Texts._ @@ -1273,10 +1274,14 @@ object Types { def underlying(implicit ctx: Context): Type /** The closest supertype of this type. This is the same as `underlying`, - * except for TypeRefs where the upper bound is returned, and HKApplys, - * where the upper bound of the constructor is re-applied to the arguments. + * except that + * - instead of a TyperBounds type it returns its upper bound, and + * - for HKApplys it returns the upper bound of the constructor re-applied to the arguments. */ - def superType(implicit ctx: Context): Type = underlying + def superType(implicit ctx: Context): Type = underlying match { + case TypeBounds(_, hi) => hi + case st => st + } } // Every type has to inherit one of the following four abstract type classes., @@ -1680,7 +1685,10 @@ object Types { } else newLikeThis(prefix) } - else newLikeThis(prefix) + else prefix match { + case _: WildcardType => WildcardType + case _ => newLikeThis(prefix) + } /** Create a NamedType of the same kind as this type, but with a new prefix. */ @@ -1762,11 +1770,6 @@ object Types { type ThisType = TypeRef override def underlying(implicit ctx: Context): Type = info - - override def superType(implicit ctx: Context): Type = info match { - case TypeBounds(_, hi) => hi - case _ => info - } } final class TermRefWithSignature(prefix: Type, name: TermName, override val sig: Signature) extends TermRef(prefix, name) { @@ -3853,7 +3856,7 @@ object Types { class CyclicReference private (val denot: SymDenotation) extends TypeError(s"cyclic reference involving $denot") { - def show(implicit ctx: Context) = s"cyclic reference involving ${denot.show}" + def toMessage(implicit ctx: Context) = CyclicReferenceInvolving(denot) } object CyclicReference { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index fcba957c0..fdb8a97ae 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -768,7 +768,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle } else EmptyValDef setClsInfo(parentRefs, if (self.isEmpty) NoType else self.tpt.tpe) - cls.setApplicableFlags(fork.indexStats(end)) + cls.setNoInitsFlags(fork.indexStats(end)) val constr = readIndexedDef().asInstanceOf[DefDef] def mergeTypeParamsAndAliases(tparams: List[TypeDef], stats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) = diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 0d1068b8c..6af902d1b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -4,6 +4,7 @@ package printing import core._ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, Denotations._ import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation +import TypeApplications.AppliedType import StdNames.{nme, tpnme} import ast.Trees._, ast._ import typer.Implicits._ @@ -119,10 +120,11 @@ class PlainPrinter(_ctx: Context) extends Printer { } /** The longest sequence of refinement types, starting at given type - * and following parents. + * and following parents, but stopping at applied types. */ private def refinementChain(tp: Type): List[Type] = tp :: (tp match { + case AppliedType(_, _) => Nil case tp: RefinedType => refinementChain(tp.parent.stripTypeVar) case _ => Nil }) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index a530a937f..7b56c8ed4 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -51,6 +51,10 @@ public enum ErrorMessageID { MixedLeftAndRightAssociativeOpsID, CantInstantiateAbstractClassOrTraitID, AnnotatedPrimaryConstructorRequiresModifierOrThisID, + OverloadedOrRecursiveMethodNeedsResultTypeID, + RecursiveValueNeedsResultTypeID, + CyclicReferenceInvolvingID, + CyclicReferenceInvolvingImplicitID, ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 54090074a..4c53fa496 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -18,6 +18,7 @@ import dotc.parsing.Tokens import printing.Highlighting._ import printing.Formatting import ErrorMessageID._ +import dotty.tools.dotc.core.SymDenotations.SymDenotation object messages { @@ -1134,7 +1135,7 @@ object messages { } case class AnnotatedPrimaryConstructorRequiresModifierOrThis(cls: Name)(implicit ctx: Context) - extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) { + extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) { val kind = "Syntax" val msg = hl"""${"private"}, ${"protected"}, or ${"this"} expected for annotated primary constructor""" val explanation = @@ -1147,4 +1148,48 @@ object messages { | ^^^^ |""".stripMargin } + + case class OverloadedOrRecursiveMethodNeedsResultType(tree: Names.TermName)(implicit ctx: Context) + extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) { + val kind = "Syntax" + val msg = hl"""overloaded or recursive method ${tree} needs return type""" + val explanation = + hl"""Case 1: ${tree} is overloaded + |If there are multiple methods named `${tree.name}` and at least one definition of + |it calls another, you need to specify the calling method's return type. + | + |Case 2: ${tree} is recursive + |If `${tree.name}` calls itself on any path, you need to specify its return type. + |""".stripMargin + } + + case class RecursiveValueNeedsResultType(tree: Names.TermName)(implicit ctx: Context) + extends Message(RecursiveValueNeedsResultTypeID) { + val kind = "Syntax" + val msg = hl"""recursive value ${tree.name} needs type""" + val explanation = + hl"""The definition of `${tree.name}` is recursive and you need to specify its type. + |""".stripMargin + } + + case class CyclicReferenceInvolving(denot: SymDenotation)(implicit ctx: Context) + extends Message(CyclicReferenceInvolvingID) { + val kind = "Syntax" + val msg = hl"""cyclic reference involving $denot""" + val explanation = + hl"""|$denot is declared as part of a cycle which makes it impossible for the + |compiler to decide upon ${denot.name}'s type. + |""".stripMargin + } + + case class CyclicReferenceInvolvingImplicit(cycleSym: Symbol)(implicit ctx: Context) + extends Message(CyclicReferenceInvolvingImplicitID) { + val kind = "Syntax" + val msg = hl"""cyclic reference involving implicit $cycleSym""" + val explanation = + hl"""|This happens when the right hand-side of $cycleSym's definition involves an implicit search. + |To avoid this error, give `${cycleSym.name}` an explicit type. + |""".stripMargin + } + } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 222717e7e..a65ef00cc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -213,16 +213,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic => protected def init() = methType match { case methType: MethodType => // apply the result type constraint, unless method type is dependent - if (!methType.isDependent) { - val savedConstraint = ctx.typerState.constraint - if (!constrainResult(methType.resultType, resultType)) - if (ctx.typerState.isCommittable) - // defer the problem until after the application; - // it might be healed by an implicit conversion - assert(ctx.typerState.constraint eq savedConstraint) - else - fail(err.typeMismatchMsg(methType.resultType, resultType)) - } + val resultApprox = resultTypeApprox(methType) + val savedConstraint = ctx.typerState.constraint + if (!constrainResult(resultApprox, resultType)) + if (ctx.typerState.isCommittable) + // defer the problem until after the application; + // it might be healed by an implicit conversion + assert(ctx.typerState.constraint eq savedConstraint) + else + fail(err.typeMismatchMsg(methType.resultType, resultType)) // match all arguments with corresponding formal parameters matchArgs(orderedArgs, methType.paramTypes, 0) case _ => @@ -1100,10 +1099,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Drop any implicit parameter section */ def stripImplicit(tp: Type): Type = tp match { - case mt: ImplicitMethodType if !mt.isDependent => - mt.resultType - // todo: make sure implicit method types are not dependent? - // but check test case in /tests/pos/depmet_implicit_chaining_zw.scala + case mt: ImplicitMethodType => + resultTypeApprox(mt) case pt: PolyType => pt.derivedPolyType(pt.paramNames, pt.paramBounds, stripImplicit(pt.resultType)) case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 0978c2c1e..a1690955f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -28,24 +28,22 @@ object ErrorReporting { def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = { val cycleSym = ex.denot.symbol - def errorMsg(msg: String, cx: Context): String = + def errorMsg(msg: Message, cx: Context): Message = if (cx.mode is Mode.InferringReturnType) { cx.tree match { case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => - em"overloaded or recursive method ${tree.name} needs result type" + OverloadedOrRecursiveMethodNeedsResultType(tree.name) case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => - em"recursive value ${tree.name} needs type" + RecursiveValueNeedsResultType(tree.name) case _ => errorMsg(msg, cx.outer) } } else msg if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) - em"""cyclic reference involving implicit $cycleSym - |This happens when the right hand-side of $cycleSym's definition involves an implicit search. - |To avoid the error, give $cycleSym an explicit type.""" + CyclicReferenceInvolvingImplicit(cycleSym) else - errorMsg(ex.show, ctx) + errorMsg(ex.toMessage, ctx) } def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 759cc62e9..ebbcbcc95 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -82,11 +82,29 @@ object Implicits { case tpw: TermRef => false // can't discard overloaded refs case tpw => - //if (ctx.typer.isApplicable(tp, argType :: Nil, resultType)) - // println(i"??? $tp is applicable to $this / typeSymbol = ${tpw.typeSymbol}") - !tpw.derivesFrom(defn.FunctionClass(1)) || - ref.symbol == defn.Predef_conforms // - // as an implicit conversion, Predef.$conforms is a no-op, so exclude it + // Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views. + // However, Predef.$conforms is not eligible, because it is a no-op. + // + // In principle, it would be cleanest if only implicit methods qualified + // as implicit conversions. We could achieve that by having standard conversions like + // this in Predef: + // + // implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a) + // implicit def convertIfConverter[A, B](x: A)(implicit ev: ImplicitConverter[A, B]): B = ev(a) + // + // (Once `<:<` inherits from `ImplicitConverter` we only need the 2nd one.) + // But clauses like this currently slow down implicit search a lot, because + // they are eligible for all pairs of types, and therefore are tried too often. + // We emulate instead these conversions directly in the search. + // The reason for leaving out `Predef_conforms` is that we know it adds + // nothing since it only relates subtype with supertype. + // + // We keep the old behavior under -language:Scala2. + val isFunctionInS2 = ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1)) + val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter) + val isConforms = + tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms + !(isFunctionInS2 || isImplicitConverter || isConforms) } def discardForValueType(tpw: Type): Boolean = tpw match { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 96660f15c..f2ad1f7c9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -484,46 +484,91 @@ class Namer { typer: Typer => /** Create top-level symbols for statements and enter them into symbol table */ def index(stats: List[Tree])(implicit ctx: Context): Context = { - val classDef = mutable.Map[TypeName, TypeDef]() - val moduleDef = mutable.Map[TypeName, TypeDef]() + // module name -> (stat, moduleCls | moduleVal) + val moduleClsDef = mutable.Map[TypeName, (Tree, TypeDef)]() + val moduleValDef = mutable.Map[TermName, (Tree, ValDef)]() + + /** Remove the subtree `tree` from the expanded tree of `mdef` */ + def removeInExpanded(mdef: Tree, tree: Tree): Unit = { + val Thicket(trees) = expanded(mdef) + mdef.putAttachment(ExpandedTree, Thicket(trees.filter(_ != tree))) + } + + /** Merge the module class `modCls` in the expanded tree of `mdef` with the given stats */ + def mergeModuleClass(mdef: Tree, modCls: TypeDef, stats: List[Tree]): TypeDef = { + var res: TypeDef = null + val Thicket(trees) = expanded(mdef) + val merged = trees.map { tree => + if (tree == modCls) { + val impl = modCls.rhs.asInstanceOf[Template] + res = cpy.TypeDef(modCls)(rhs = cpy.Template(impl)(body = stats ++ impl.body)) + res + } + else tree + } + + mdef.putAttachment(ExpandedTree, Thicket(merged)) + + res + } + + /** Merge `fromCls` of `fromStat` into `toCls` of `toStat` + * if the former is synthetic and the latter not. + * + * Note: + * 1. `fromStat` and `toStat` could be the same stat + * 2. `fromCls` and `toCls` are necessarily different + */ + def mergeIfSynthetic(fromStat: Tree, fromCls: TypeDef, toStat: Tree, toCls: TypeDef): Unit = + if (fromCls.mods.is(Synthetic) && !toCls.mods.is(Synthetic)) { + removeInExpanded(fromStat, fromCls) + val mcls = mergeModuleClass(toStat, toCls, fromCls.rhs.asInstanceOf[Template].body) + moduleClsDef(fromCls.name) = (toStat, mcls) + } /** Merge the definitions of a synthetic companion generated by a case class * and the real companion, if both exist. */ def mergeCompanionDefs() = { - for (cdef @ TypeDef(name, _) <- stats) - if (cdef.isClassDef) { - classDef(name) = cdef - cdef.attachmentOrElse(ExpandedTree, cdef) match { - case Thicket(cls :: mval :: (mcls @ TypeDef(mname, _: Template)) :: crest) - if name.moduleClassName == mname => - moduleDef(name) = mcls - case _ => - } - } - for (mdef @ ModuleDef(name, _) <- stats if !mdef.mods.is(Flags.Package)) { - val typName = name.toTypeName - // Expansion of object is a flattened thicket with the first two elements being: - // module val :: module class :: rest - val Thicket(vdef :: (mcls @ TypeDef(_, impl: Template)) :: rest) = expanded(mdef) - moduleDef(typName) = mcls - classDef get name.toTypeName match { - case Some(cdef) => - cdef.attachmentOrElse(ExpandedTree, cdef) match { - case Thicket(cls :: mval :: TypeDef(mname, compimpl: Template) :: crest) - if name.moduleClassName == mname => - val mcls1 = cpy.TypeDef(mcls)( - rhs = cpy.Template(impl)(body = compimpl.body ++ impl.body)) - mdef.putAttachment(ExpandedTree, Thicket(vdef :: mcls1 :: rest)) - moduleDef(typName) = mcls1 - cdef.putAttachment(ExpandedTree, Thicket(cls :: crest)) + def valid(mdef: MemberDef): Boolean = mdef.mods.is(Module, butNot = Package) + + for (stat <- stats) + expanded(stat) match { + case Thicket(trees) => // companion object always expands to thickets + trees.map { + case mcls @ TypeDef(name, impl: Template) if valid(mcls) => + moduleClsDef.get(name) match { + case Some((stat1, mcls1@TypeDef(_, impl1: Template))) => + mergeIfSynthetic(stat, mcls, stat1, mcls1) + mergeIfSynthetic(stat1, mcls1, stat, mcls) + case None => + moduleClsDef(name) = (stat, mcls) + } + + case vdef @ ValDef(name, _, _) if valid(vdef) => + moduleValDef.get(name) match { + case Some((stat1, vdef1)) => + if (vdef.mods.is(Synthetic) && !vdef1.mods.is(Synthetic)) + removeInExpanded(stat, vdef) + else if (!vdef.mods.is(Synthetic) && vdef1.mods.is(Synthetic)) { + removeInExpanded(stat1, vdef1) + moduleValDef(name) = (stat, vdef) + } + else { + // double definition of objects or case classes, handled elsewhere + } + case None => + moduleValDef(name) = (stat, vdef) + } + case _ => } - case none => + case _ => + } - } } + /** Create links between companion object and companion class */ def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.denotNamed(classTree.name.encode).symbol val modl = ctx.denotNamed(moduleTree.name.encode).symbol @@ -532,8 +577,29 @@ class Namer { typer: Typer => } def createCompanionLinks(implicit ctx: Context): Unit = { + val classDef = mutable.Map[TypeName, TypeDef]() + val moduleDef = mutable.Map[TypeName, TypeDef]() + + def updateCache(cdef: TypeDef): Unit = { + if (!cdef.isClassDef || cdef.mods.is(Package)) return + + if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef + else classDef(cdef.name) = cdef + } + + for (stat <- stats) + expanded(stat) match { + case cdef : TypeDef => updateCache(cdef) + case Thicket(trees) => + trees.map { + case cdef: TypeDef => updateCache(cdef) + case _ => + } + case _ => + } + for (cdef @ TypeDef(name, _) <- classDef.values) { - moduleDef.getOrElse(name, EmptyTree) match { + moduleDef.getOrElse(name.moduleClassName, EmptyTree) match { case t: TypeDef => createLinks(cdef, t) case EmptyTree => @@ -818,8 +884,6 @@ class Namer { typer: Typer => Checking.checkWellFormed(cls) if (isDerivedValueClass(cls)) cls.setFlag(Final) - cls.setApplicableFlags( - (NoInitsInterface /: impl.body)((fs, stat) => fs & defKind(stat))) cls.info = avoidPrivateLeaks(cls, cls.pos) } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index d666b563e..17f13d7c1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -57,19 +57,14 @@ object ProtoTypes { case pt: FunProto => mt match { case mt: MethodType => - mt.isDependent || constrainResult(mt.resultType, pt.resultType) + constrainResult(resultTypeApprox(mt), pt.resultType) case _ => true } case _: ValueTypeOrProto if !disregardProto(pt) => - mt match { - case mt: MethodType => - mt.isDependent || isCompatible(normalize(mt, pt), pt) - case _ => - isCompatible(mt, pt) - } - case _: WildcardType => - isCompatible(mt, pt) + isCompatible(normalize(mt, pt), pt) + case pt: WildcardType if pt.optBounds.exists => + isCompatible(normalize(mt, pt), pt) case _ => true } @@ -394,6 +389,26 @@ object ProtoTypes { /** Same as `constrained(pt, EmptyTree)`, but returns just the created polytype */ def constrained(pt: PolyType)(implicit ctx: Context): PolyType = constrained(pt, EmptyTree)._1 + /** Create a new polyparam that represents a dependent method parameter singleton */ + def newDepPolyParam(tp: Type)(implicit ctx: Context): PolyParam = { + val poly = PolyType(ctx.freshName(nme.DEP_PARAM_PREFIX).toTypeName :: Nil, 0 :: Nil)( + pt => TypeBounds.upper(AndType(tp, defn.SingletonType)) :: Nil, + pt => defn.AnyType) + ctx.typeComparer.addToConstraint(poly, Nil) + PolyParam(poly, 0) + } + + /** The result type of `mt`, where all references to parameters of `mt` are + * replaced by either wildcards (if typevarsMissContext) or polyparams. + */ + def resultTypeApprox(mt: MethodType)(implicit ctx: Context): Type = + if (mt.isDependent) { + def replacement(tp: Type) = + if (ctx.mode.is(Mode.TypevarsMissContext)) WildcardType else newDepPolyParam(tp) + mt.resultType.substParams(mt, mt.paramTypes.map(replacement)) + } + else mt.resultType + /** The normalized form of a type * - unwraps polymorphic types, tracking their parameters in the current constraint * - skips implicit parameters; if result type depends on implicit parameter, @@ -413,22 +428,18 @@ object ProtoTypes { tp.widenSingleton match { case poly: PolyType => normalize(constrained(poly).resultType, pt) case mt: MethodType => - if (mt.isImplicit) - if (mt.isDependent) - mt.resultType.substParams(mt, mt.paramTypes.map(Function.const(WildcardType))) - else mt.resultType - else - if (mt.isDependent) tp - else { - val rt = normalize(mt.resultType, pt) + if (mt.isImplicit) resultTypeApprox(mt) + else if (mt.isDependent) tp + else { + val rt = normalize(mt.resultType, pt) pt match { case pt: IgnoredProto => mt case pt: ApplyingProto => mt.derivedMethodType(mt.paramNames, mt.paramTypes, rt) case _ => val ft = defn.FunctionOf(mt.paramTypes, rt) if (mt.paramTypes.nonEmpty || ft <:< pt) ft else rt - } } + } case et: ExprType => et.resultType case _ => tp } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b2e9d639d..ba14b7498 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1314,6 +1314,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible val dummy = localDummy(cls, impl) val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) + cls.setNoInitsFlags((NoInitsInterface /: body1)((fs, stat) => fs & defKind(stat))) // Expand comments and type usecases cookComments(body1.map(_.symbol), self1.symbol)(localContext(cdef, cls).setNewScope) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index d6e687a1e..f11c6dd96 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -6,7 +6,7 @@ import core.Contexts.Context import diagnostic.messages._ import dotty.tools.dotc.parsing.Tokens import org.junit.Assert._ -import org.junit.Test +import org.junit.{Ignore, Test} class ErrorMessagesTests extends ErrorMessagesTest { // In the case where there are no errors, we can do "expectNoErrors" in the @@ -212,4 +212,97 @@ class ErrorMessagesTests extends ErrorMessagesTest { val AnnotatedPrimaryConstructorRequiresModifierOrThis(cls) :: Nil = messages assertEquals("AnotherClass", cls.show) } + + @Test def overloadedMethodNeedsReturnType = + checkMessagesAfter("frontend") { + """ + |class Scope() { + | def foo(i: Int) = foo(i.toString) + | def foo(s: String) = s + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val OverloadedOrRecursiveMethodNeedsResultType(tree) :: Nil = messages + assertEquals("foo", tree.show) + } + + @Test def recursiveMethodNeedsReturnType = + checkMessagesAfter("frontend") { + """ + |class Scope() { + | def i = i + 5 + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val OverloadedOrRecursiveMethodNeedsResultType(tree) :: Nil = messages + assertEquals("i", tree.show) + } + + @Test def recursiveValueNeedsReturnType = + checkMessagesAfter("frontend") { + """ + |class Scope() { + | lazy val i = i + 5 + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val RecursiveValueNeedsResultType(tree) :: Nil = messages + assertEquals("i", tree.show) + } + + @Test def cyclicReferenceInvolving = + checkMessagesAfter("frontend") { + """ + |class A { + | val x: T = ??? + | type T <: x.type // error: cyclic reference involving value x + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val CyclicReferenceInvolving(denot) :: Nil = messages + assertEquals("value x", denot.show) + } + + @Test def cyclicReferenceInvolvingImplicit = + checkMessagesAfter("frontend") { + """ + |object implicitDefs { + | def foo(implicit x: String) = 1 + | def bar() = { + | implicit val x = foo + | x + | } + |} + """.stripMargin + } + .expect { (ictx, messages) => + implicit val ctx: Context = ictx + val defn = ictx.definitions + + assertMessageCount(1, messages) + val CyclicReferenceInvolvingImplicit(tree) :: Nil = messages + assertEquals("x", tree.name.show) + } + + } diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 12040e0f3..e78fa9239 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -36,4 +36,24 @@ object DottyPredef { implicit def eqNumFloat : Eq[Number, Float] = Eq implicit def eqDoubleNum: Eq[Double, Number] = Eq implicit def eqNumDouble: Eq[Number, Double] = Eq + + /** A class for implicit values that can serve as implicit conversions + * The implicit resolution algorithm will act as if there existed + * the additional implicit definition: + * + * def $implicitConversion[T, U](x: T)(c: ImplicitConverter[T, U]): U = c(x) + * + * However, the presence of this definition would slow down implicit search since + * its outermost type matches any pair of types. Therefore, implicit search + * contains a special case in `Implicits#discardForView` which emulates the + * conversion in a more efficient way. + * + * Note that this is a SAM class - function literals are automatically converted + * to `ImplicitConverter` values. + * + * Also note that in bootstrapped dotty, `Predef.<:<` should inherit from + * `ImplicitConverter`. This would cut the number of special cases in + * `discardForView` from two to one. + */ + abstract class ImplicitConverter[-T, +U] extends Function1[T, U] } diff --git a/tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala b/tests/disabled/pos/depmet_implicit_oopsla_session_simpler.scala index fad5eba40..8cc6fe754 100644 --- a/tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala +++ b/tests/disabled/pos/depmet_implicit_oopsla_session_simpler.scala @@ -1,3 +1,4 @@ +// Disabled because we now get an unsafe instantiation error object Sessions { trait Session { type Dual <: Session @@ -40,5 +41,5 @@ object Sessions { In{z: Int => System.out.println(z) Stop()}})) - def myRun = addServer run addClient + def myRun = addServer run addClient // error: unsafe instantiation } diff --git a/tests/neg/falseView.scala b/tests/neg/falseView.scala new file mode 100644 index 000000000..613abe3f1 --- /dev/null +++ b/tests/neg/falseView.scala @@ -0,0 +1,7 @@ +object Test { + + private implicit val xs: Map[String, Int] = ??? + + val x: Int = "abc" // error + +} diff --git a/tests/pending/pos/depmet_implicit_oopsla_session_2.scala b/tests/pending/pos/depmet_implicit_oopsla_session_2.scala index 29a76d5cf..26fa2a4cc 100644 --- a/tests/pending/pos/depmet_implicit_oopsla_session_2.scala +++ b/tests/pending/pos/depmet_implicit_oopsla_session_2.scala @@ -1,3 +1,8 @@ +// Fails on line 70 with: no implicit argument of type Sessions.Session[ +// | Sessions.In[Int, Sessions.In[Int, Sessions.Out[Int, Sessions.Stop]]]^ +// |]#HasDual[Sessions.Out[Int, Sessions.Out[Int, Sessions.In[Int, Sessions.Stop]]]^ +// | ] found for parameter evidence$1 of method runSession in object Sessions +// This could be related to existential types (the # essentially boils down to one). object Sessions { def ?[T <: AnyRef](implicit w: T): w.type = w diff --git a/tests/pickling/innerclass.scala b/tests/pickling/innerclass.scala new file mode 100644 index 000000000..5881cf6ed --- /dev/null +++ b/tests/pickling/innerclass.scala @@ -0,0 +1,3 @@ +trait Foo { + class Inner(x: Int = 42) +} diff --git a/tests/pos/typerep-stephane.scala b/tests/pos-scala2/typerep-stephane.scala index 2cb899591..2cb899591 100644 --- a/tests/pos/typerep-stephane.scala +++ b/tests/pos-scala2/typerep-stephane.scala diff --git a/tests/pos/viewtest1.scala b/tests/pos-scala2/viewtest1.scala index 38945ad2f..38945ad2f 100644 --- a/tests/pos/viewtest1.scala +++ b/tests/pos-scala2/viewtest1.scala diff --git a/tests/pending/pos/depmet_implicit_norm_ret.scala b/tests/pos/depmet_implicit_norm_ret.scala index 42bfb9fe1..42bfb9fe1 100644 --- a/tests/pending/pos/depmet_implicit_norm_ret.scala +++ b/tests/pos/depmet_implicit_norm_ret.scala diff --git a/tests/pos/i2064.scala b/tests/pos/i2064.scala new file mode 100644 index 000000000..909923372 --- /dev/null +++ b/tests/pos/i2064.scala @@ -0,0 +1,15 @@ +object p { + +class Foo[T] { + // Crashes: + def f(): Foo[T] = (if (true) this else this).bar + + // Works: + // def f(): Foo[T] = new Bar(if (true) this else this).bar +} + +implicit class Bar[A](val self: A) { + def bar(): A = self +} + +} diff --git a/tests/pos/t0786.scala b/tests/pos/t0786.scala index b320de0ed..9346afdff 100644 --- a/tests/pos/t0786.scala +++ b/tests/pos/t0786.scala @@ -15,7 +15,7 @@ object ImplicitProblem { def eval = f(nullval[T]).eval + 1 } - def depth[T](n: T)(implicit ev: T => Rep[T]) = n.eval + def depth[T](n: T)(implicit ev: T => Rep[T]) = ev(n).eval def main(args: Array[String]): Unit = { println(depth(nullval[M[Int]])) // (1) this works diff --git a/tests/pos/t2421_delitedsl.scala b/tests/pos/t2421_delitedsl.scala index 22f1ecd85..bde3593c9 100644 --- a/tests/pos/t2421_delitedsl.scala +++ b/tests/pos/t2421_delitedsl.scala @@ -1,6 +1,9 @@ trait DeliteDSL { abstract class <~<[-From, +To] extends (From => To) + implicit def trivial[A]: A <~< A = new (A <~< A) {def apply(x: A) = x} + implicit def convert_<-<[A, B](x: A)(implicit ev: A <~< B): B = ev(x) + trait Forcible[T] object Forcible { diff --git a/tests/pos/t5070.scala b/tests/pos/t5070.scala index 410afba14..0e5c0ffc0 100644 --- a/tests/pos/t5070.scala +++ b/tests/pos/t5070.scala @@ -13,3 +13,26 @@ class Test { implicitly[a.T](b(a)) // works } + + +class ImplicitVsTypeAliasTezt { + + class Monad[m[_]] { + type For[a] = _For[m, a] + implicit def toFor[a](m: m[a]): For[a] = throw new Error("todo") // lookup fails +// implicit def toFor[a](m: m[a]): _For[m, a] = throw new Error("todo") // fine. + } + + trait _For[m[_], a] { + def map[b](p: a => b): m[b] + } + + def useMonad[m[_], a](m: m[a])(implicit i: Monad[m]) = { + import i._ + + // value map is not a member of type parameter m[a] + for { + x <- m + } yield x.toString + } +} diff --git a/tests/pos/t5643.scala b/tests/pos/t5643.scala new file mode 100644 index 000000000..1ce34ba36 --- /dev/null +++ b/tests/pos/t5643.scala @@ -0,0 +1,19 @@ +object TupledEvidenceTest { + + abstract class TupledEvidence[M[_], T0] { type T = T0 } + + implicit def witnessTuple2[M[_], T1, T2](implicit ev1: M[T1], ev2: M[T2]): + TupledEvidence[M, (T1, T2)] { type T = (T1, T2) } = sys.error("") + + class GetResult[T] + + implicit val getString: GetResult[String] = new GetResult[String] + + implicit def getTuple[T](implicit w: TupledEvidence[GetResult, T]): GetResult[w.T] = sys.error("") + + def f[T : GetResult] = "" + + f[(String,String)](getTuple[(String, String)]) + + f[(String,String)] +} diff --git a/tests/run/HLists-nonvariant.check b/tests/run/HLists-nonvariant.check new file mode 100644 index 000000000..53efe30d7 --- /dev/null +++ b/tests/run/HLists-nonvariant.check @@ -0,0 +1,8 @@ +1 +A +true +true +HCons(1,HCons(A,HCons(true,HNil))) +1 +A +true diff --git a/tests/run/HLists-nonvariant.scala b/tests/run/HLists-nonvariant.scala new file mode 100644 index 000000000..a3a639bcd --- /dev/null +++ b/tests/run/HLists-nonvariant.scala @@ -0,0 +1,78 @@ +sealed trait HList +case class HCons[HD, TL](hd: HD, tl: TL) extends HList +case object HNil extends HList + +sealed trait Num +case object Zero extends Num +case class Succ[N <: Num](pred: N) extends Num + +object Test { + + type HNil = HNil.type + type Zero = Zero.type + + trait Concat[Xs <: HList, Ys <: HList, Zs <: HList] { + def concat(xs: Xs, ys: Ys): Zs + } + + implicit def concatNil[Xs <: HList]: Concat[HNil, Xs, Xs] = + new Concat[HNil, Xs, Xs] { + def concat(fst: HNil, snd: Xs) = snd + } + + implicit def concatCons[X, Xs <: HList, Ys <: HList, Zs <: HList]( + implicit ev: Concat[Xs, Ys, Zs] + ): Concat[HCons[X, Xs], Ys, HCons[X, Zs]] = + new Concat[HCons[X, Xs], Ys, HCons[X, Zs]] { + def concat(xs: HCons[X, Xs], ys: Ys): HCons[X, Zs] = + HCons(xs.hd, ev.concat(xs.tl, ys)) + } + + def concat[Xs <: HList, Ys <: HList, Zs <: HList](xs: Xs, ys: Ys)(implicit ev: Concat[Xs, Ys, Zs]): Zs = + ev.concat(xs, ys) + + val xs = HCons(1, HCons("A", HNil)) + val ys = HCons(true, HNil) + val zs = concat(xs, ys) + val zs1: HCons[Int, HCons[String, HCons[Boolean, HNil]]] = zs + + trait At[Xs <: HList, N <: Num] { + type Out + def at(xs: Xs, n: N): Out + } + + implicit def atZero[XZ, Xs <: HList]: At[HCons[XZ, Xs], Zero] { type Out = XZ } = + new At[HCons[XZ, Xs], Zero] { + type Out = XZ + def at(xs: HCons[XZ, Xs], n: Zero) = xs.hd + } + + implicit def atSucc[XX, Xs <: HList, N <: Num]( + implicit ev: At[Xs, N] + ): At[HCons[XX, Xs], Succ[N]] { type Out = ev.Out } = new At[HCons[XX, Xs], Succ[N]] { + type Out = ev.Out + def at(xs: HCons[XX, Xs], n: Succ[N]): Out = ev.at(xs.tl, n.pred) + } + + def at[Xs <: HList, N <: Num](xs: Xs, n: N)( + implicit ev: At[Xs, N] + ): ev.Out = ev.at(xs, n) + + def main(args: Array[String]) = { + val ys1 = HCons(1, HNil) + println(at(ys1, Zero)) + + val ys2 = HCons(1, HCons("A", HNil)) + val y2 = at(ys2, Succ(Zero)) + println(at(ys2, Succ(Zero))) + val ys3 = HCons(1, HCons("A", HCons(true, HNil))) + println(at(ys3, Succ(Succ(Zero)))) + val ys4 = HCons(1, HCons("A", HCons(true, HCons(1.0, HNil)))) + println(at(ys4, Succ(Succ(Zero)))) + + println(zs1) + println(at(zs1, Zero)) + println(at(zs1, Succ(Zero))) + println(at(zs1, Succ(Succ(Zero)))) + } +} diff --git a/tests/run/HLists.check b/tests/run/HLists.check new file mode 100644 index 000000000..53efe30d7 --- /dev/null +++ b/tests/run/HLists.check @@ -0,0 +1,8 @@ +1 +A +true +true +HCons(1,HCons(A,HCons(true,HNil))) +1 +A +true diff --git a/tests/run/HLists.scala b/tests/run/HLists.scala new file mode 100644 index 000000000..eebbcb4cd --- /dev/null +++ b/tests/run/HLists.scala @@ -0,0 +1,78 @@ +sealed trait HList +case class HCons[+HD, +TL](hd: HD, tl: TL) extends HList +case object HNil extends HList + +sealed trait Num +case object Zero extends Num +case class Succ[N <: Num](pred: N) extends Num + +object Test { + + type HNil = HNil.type + type Zero = Zero.type + + trait Concat[Xs <: HList, Ys <: HList, Zs <: HList] { + def concat(xs: Xs, ys: Ys): Zs + } + + implicit def concatNil[Xs <: HList]: Concat[HNil, Xs, Xs] = + new Concat[HNil, Xs, Xs] { + def concat(fst: HNil, snd: Xs) = snd + } + + implicit def concatCons[X, Xs <: HList, Ys <: HList, Zs <: HList]( + implicit ev: Concat[Xs, Ys, Zs] + ): Concat[HCons[X, Xs], Ys, HCons[X, Zs]] = + new Concat[HCons[X, Xs], Ys, HCons[X, Zs]] { + def concat(xs: HCons[X, Xs], ys: Ys): HCons[X, Zs] = + HCons(xs.hd, ev.concat(xs.tl, ys)) + } + + def concat[Xs <: HList, Ys <: HList, Zs <: HList](xs: Xs, ys: Ys)(implicit ev: Concat[Xs, Ys, Zs]): Zs = + ev.concat(xs, ys) + + val xs = HCons(1, HCons("A", HNil)) + val ys = HCons(true, HNil) + val zs = concat(xs, ys) + val zs1: HCons[Int, HCons[String, HCons[Boolean, HNil]]] = zs + + trait At[Xs <: HList, N <: Num] { + type Out + def at(xs: Xs, n: N): Out + } + + implicit def atZero[XZ, Xs <: HList]: At[HCons[XZ, Xs], Zero] { type Out = XZ } = + new At[HCons[XZ, Xs], Zero] { + type Out = XZ + def at(xs: HCons[XZ, Xs], n: Zero) = xs.hd + } + + implicit def atSucc[XX, Xs <: HList, N <: Num]( + implicit ev: At[Xs, N] + ): At[HCons[XX, Xs], Succ[N]] { type Out = ev.Out } = new At[HCons[XX, Xs], Succ[N]] { + type Out = ev.Out + def at(xs: HCons[XX, Xs], n: Succ[N]): Out = ev.at(xs.tl, n.pred) + } + + def at[Xs <: HList, N <: Num](xs: Xs, n: N)( + implicit ev: At[Xs, N] + ): ev.Out = ev.at(xs, n) + + def main(args: Array[String]) = { + val ys1 = HCons(1, HNil) + println(at(ys1, Zero)) + + val ys2 = HCons(1, HCons("A", HNil)) + val y2 = at(ys2, Succ(Zero)) + println(at(ys2, Succ(Zero))) + val ys3 = HCons(1, HCons("A", HCons(true, HNil))) + println(at(ys3, Succ(Succ(Zero)))) + val ys4 = HCons(1, HCons("A", HCons(true, HCons(1.0, HNil)))) + println(at(ys4, Succ(Succ(Zero)))) + + println(zs1) + println(at(zs1, Zero)) + println(at(zs1, Succ(Zero))) + println(at(zs1, Succ(Succ(Zero)))) + } +} diff --git a/tests/run/hmap-covariant.scala b/tests/run/hmap-covariant.scala new file mode 100644 index 000000000..475cc6ee6 --- /dev/null +++ b/tests/run/hmap-covariant.scala @@ -0,0 +1,97 @@ +trait Tuple +case class TCons[+H, +T <: Tuple](h: H, t: T) extends Tuple +final case object TNil extends Tuple + +// Type level natural numbers ------------------------------------------------- + +sealed trait Nat +sealed trait Succ[P <: Nat] extends Nat +sealed trait Zero extends Nat + +// Accessor type class to compute the N'th element of an Tuple L -------------- + +trait At[L <: Tuple, N <: Nat, Out] { + def apply(l: L): Out +} + +object At { + implicit def caseZero[H, T <: Tuple]: At[H TCons T, Zero, H] = + new At[H TCons T, Zero, H] { + def apply(l: H TCons T): H = { + val (h TCons _) = l + h + } + } + + implicit def caseN[H, T <: Tuple, N <: Nat, O] + (implicit a: At[T, N, O]): At[H TCons T, Succ[N], O] = + new At[H TCons T, Succ[N], O] { + def apply(l: H TCons T): O = { + val (_ TCons t) = l + a(t) + } + } +} + +// An HMap is an Tuple with HEntry elements. We are reusing Tuple for it's nice syntax + +final case class HEntry[K, V](value: V) + +// Accessor type class to compute the element of type K in a HMap L ----------- + +trait PhantomGet[K, M <: Tuple, I <: Nat] // extends PhantomAny + +object PhantomGet { + implicit def getHead[K, V, T <: Tuple] + : PhantomGet[K, HEntry[K, V] TCons T, Zero] = null + + implicit def getTail[K, H, T <: Tuple, I <: Nat] + (implicit t: PhantomGet[K, T, I]) + : PhantomGet[K, H TCons T, Succ[I]] = null +} + +// Syntax --------------------------------------------------------------------- + +object syntax { + object hmap { + implicit class HmapGet[M <: Tuple](m: M) { + def get[K, V, I <: Nat](k: K) + (implicit + g: PhantomGet[k.type, M, I], + a: At[M, I, HEntry[k.type, V]] + ): V = a(m).value + } + + def --[K, V](key: K, value: V) = HEntry[key.type, V](value) + } +} + +object Test { + def main(args: Array[String]): Unit = { + import syntax.hmap._ + + val map1 = + TCons(HEntry[K = "name"]("foo"), + TCons(HEntry[K = "genre"](true), + TCons(HEntry[K = "moneyz"](123), + TCons(HEntry[K = "cat"]("bar"), + (TNil: TNil.type))))) + + assert(map1.get("name") == "foo") + assert(map1.get("genre") == true) + assert(map1.get("moneyz") == 123) + assert(map1.get("cat") == "bar") + + val map2 = + TCons(--("name" , "foo"), + TCons(--("genre" , true), + TCons(--("moneyz", 123), + TCons(--("cat" , "bar"), + TNil)))) + + assert(map2.get("name") == "foo") + assert(map2.get("genre") == true) + assert(map2.get("moneyz") == 123) + assert(map2.get("cat") == "bar") + } +} diff --git a/tests/run/i2004.check b/tests/run/i2004.check new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/tests/run/i2004.check @@ -0,0 +1 @@ +1 diff --git a/tests/run/i2004.scala b/tests/run/i2004.scala new file mode 100644 index 000000000..ff21a6cc9 --- /dev/null +++ b/tests/run/i2004.scala @@ -0,0 +1,11 @@ +object Test { + def main(args: Array[String]) = { + val f: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = + { + (x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, x16: Int, x17: Int, x18: Int, x19: Int, x20: Int, x21: Int, x22: Int, x23: Int, x24: Int, x25: Int, x26: Int) => + x1 + } + println(f.apply(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)) + + } +} diff --git a/tests/run/iterator-from.scala b/tests/run/iterator-from.scala index 4f403680c..c7c0f9809 100644 --- a/tests/run/iterator-from.scala +++ b/tests/run/iterator-from.scala @@ -11,7 +11,9 @@ object Test extends dotty.runtime.LegacyApp { val maxKey = 50 val maxValue = 50 - def testSet[A <% Ordered[A]](set: SortedSet[A], list: List[A]): Unit = { + implicit def convertIfView[A](x: A)(implicit view: A => Ordered[A]): Ordered[A] = view(x) + + def testSet[A: Ordering](set: SortedSet[A], list: List[A]): Unit = { val distinctSorted = list.distinct.sorted assertEquals("Set size wasn't the same as list sze", set.size, distinctSorted.size) @@ -24,7 +26,7 @@ object Test extends dotty.runtime.LegacyApp { } } - def testMap[A <% Ordered[A], B](map: SortedMap[A, B], list: List[(A, B)]): Unit = { + def testMap[A: Ordering, B](map: SortedMap[A, B], list: List[(A, B)]): Unit = { val distinctSorted = distinctByKey(list).sortBy(_._1) assertEquals("Map size wasn't the same as list sze", map.size, distinctSorted.size) diff --git a/tests/run/puzzler54.scala b/tests/run/puzzler54.scala new file mode 100644 index 000000000..9dd4cbb47 --- /dev/null +++ b/tests/run/puzzler54.scala @@ -0,0 +1,13 @@ +// Scala Puzzler 54 +object Test { + case class Card(number: Int, suit: String = "clubs") { + val value = (number % 13) + 1 // ace = 1, king = 13 + def isInDeck(implicit deck: List[Card]) = deck contains this + } + + def main(args: Array[String]) = { + implicit val deck = List(Card(1, "clubs")) + implicit def intToCard(n: Int): Card = Card(n) + assert(1.isInDeck) + } +} diff --git a/tests/run/t8280.check b/tests/run/t8280.check index 44c51f5aa..b5885df48 100644 --- a/tests/run/t8280.check +++ b/tests/run/t8280.check @@ -1,7 +1,6 @@ -Int -Int Long Int Int Int Int +Int diff --git a/tests/run/t8280.scala b/tests/run/t8280.scala index f433dcc32..5fcbad0a3 100644 --- a/tests/run/t8280.scala +++ b/tests/run/t8280.scala @@ -37,7 +37,8 @@ object Moop1 { implicit object f1 extends (Int => String) { def apply(x: Int): String = "Int" } implicit val f2: Long => String = _ => "Long" - println(5: String) + //println(5: String) + // This picked f1 before, but is now disallowed since subtypes of functions are no longer implicit conversions } } @@ -73,14 +74,14 @@ object Moop3 { // Dotty deviation. This fails for Dotty with ambiguity error for similar reasons as ob1. } object ob2 { - implicit val f1: Int => String = _ => "Int" + implicit val f1: ImplicitConverter[Int, String] = _ => "Int" implicit def f2(x: Long): String = "Long" println(5: String) } object ob3 { - implicit val f1: Int => String = _ => "Int" - implicit val f2: Long => String = _ => "Long" + implicit val f1: ImplicitConverter[Int, String] = _ => "Int" + implicit val f2: ImplicitConverter[Long, String] = _ => "Long" println(5: String) } |