diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 8d0691037795..796c9ad91bd2 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -106,6 +106,13 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ => Nil } + /** Is tree a path? */ + def isPath(tree: Tree): Boolean = unsplice(tree) match { + case Ident(_) | This(_) | Super(_, _) => true + case Select(qual, _) => isPath(qual) + case _ => false + } + /** Is tree a self constructor call this(...)? I.e. a call to a constructor of the * same object? */ diff --git a/compiler/src/dotty/tools/dotc/core/Constraint.scala b/compiler/src/dotty/tools/dotc/core/Constraint.scala index 946a19b24d30..91bedf35948b 100644 --- a/compiler/src/dotty/tools/dotc/core/Constraint.scala +++ b/compiler/src/dotty/tools/dotc/core/Constraint.scala @@ -17,8 +17,8 @@ abstract class Constraint extends Showable { type This <: Constraint - /** Does the constraint's domain contain the type parameters of `pt`? */ - def contains(pt: TypeLambda): Boolean + /** Does the constraint's domain contain the type parameters of `tl`? */ + def contains(tl: TypeLambda): Boolean /** Does the constraint's domain contain the type parameter `param`? */ def contains(param: TypeParamRef): Boolean @@ -106,14 +106,22 @@ abstract class Constraint extends Showable { */ def replace(param: TypeParamRef, tp: Type)(implicit ctx: Context): This - /** Is entry associated with `pt` removable? This is the case if + /** Is entry associated with `tl` removable? This is the case if * all type parameters of the entry are associated with type variables * which have their `inst` fields set. */ - def isRemovable(pt: TypeLambda): Boolean + def isRemovable(tl: TypeLambda): Boolean - /** A new constraint with all entries coming from `pt` removed. */ - def remove(pt: TypeLambda)(implicit ctx: Context): This + /** A new constraint with all entries coming from `tl` removed. */ + def remove(tl: TypeLambda)(implicit ctx: Context): This + + /** A new constraint with entry `tl` renamed to a fresh type lambda */ + def rename(tl: TypeLambda)(implicit ctx: Context): This + + /** The given `tl` in case it is not contained in this constraint, + * a fresh copy of `tl` otherwise. + */ + def ensureFresh(tl: TypeLambda)(implicit ctx: Context): TypeLambda /** The type lambdas constrained by this constraint */ def domainLambdas: List[TypeLambda] diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 91b1d611bee2..b3072d60ccfe 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -69,6 +69,8 @@ object NameOps { def isSetterName: Boolean = name endsWith str.SETTER_SUFFIX def isScala2LocalSuffix: Boolean = testSimple(_.endsWith(" ")) def isSelectorName: Boolean = testSimple(n => n.startsWith("_") && n.drop(1).forall(_.isDigit)) + def isAnonymousClassName: Boolean = name.startsWith(str.ANON_CLASS) + def isAnonymousFunctionName: Boolean = name.startsWith(str.ANON_FUN) /** Is name a variable name? */ def isVariableName: Boolean = testSimple { n => diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index d5219751e04a..2f568dfe7750 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -8,6 +8,7 @@ import collection.mutable import printing.Printer import printing.Texts._ import config.Config +import config.Printers.constr import reflect.ClassTag import annotation.tailrec import annotation.internal.sharable @@ -503,6 +504,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, } def & (other: Constraint, otherHasErrors: Boolean)(implicit ctx: Context): OrderingConstraint = { + def merge[T](m1: ArrayValuedMap[T], m2: ArrayValuedMap[T], join: (T, T) => T): ArrayValuedMap[T] = { var merged = m1 def mergeArrays(xs1: Array[T], xs2: Array[T]) = { @@ -527,7 +529,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case (e1: TypeBounds, e2: TypeBounds) => e1 & e2 case (e1: TypeBounds, _) if e1 contains e2 => e2 case (_, e2: TypeBounds) if e2 contains e1 => e1 - case (tv1: TypeVar, tv2: TypeVar) if tv1.instanceOpt eq tv2.instanceOpt => e1 + case (tv1: TypeVar, tv2: TypeVar) if tv1 eq tv2 => e1 case _ => if (otherHasErrors) e1 @@ -535,13 +537,63 @@ class OrderingConstraint(private val boundsMap: ParamBounds, throw new AssertionError(i"cannot merge $this with $other, mergeEntries($e1, $e2) failed") } - val that = other.asInstanceOf[OrderingConstraint] + /** Ensure that constraint `c` does not associate different TypeVars for the + * same type lambda than this constraint. Do this by renaming type lambdas + * in `c` where necessary. + */ + def ensureNotConflicting(c: OrderingConstraint): OrderingConstraint = { + def hasConflictingTypeVarsFor(tl: TypeLambda) = + this.typeVarOfParam(tl.paramRefs(0)) ne c.typeVarOfParam(tl.paramRefs(0)) + // Note: Since TypeVars are allocated in bulk for each type lambda, we only + // have to check the first one to find out if some of them are different. + val conflicting = c.domainLambdas.find(tl => + this.contains(tl) && hasConflictingTypeVarsFor(tl)) + conflicting match { + case Some(tl) => ensureNotConflicting(c.rename(tl)) + case None => c + } + } + + val that = ensureNotConflicting(other.asInstanceOf[OrderingConstraint]) + new OrderingConstraint( merge(this.boundsMap, that.boundsMap, mergeEntries), merge(this.lowerMap, that.lowerMap, mergeParams), merge(this.upperMap, that.upperMap, mergeParams)) + }.reporting(res => i"constraint merge $this with $other = $res", constr) + + def rename(tl: TypeLambda)(implicit ctx: Context): OrderingConstraint = { + assert(contains(tl)) + val tl1 = ensureFresh(tl) + def swapKey[T](m: ArrayValuedMap[T]) = m.remove(tl).updated(tl1, m(tl)) + var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap)) + def subst[T <: Type](x: T): T = x.subst(tl, tl1).asInstanceOf[T] + current.foreachParam {(p, i) => + current = boundsLens.map(this, current, p, i, subst) + current = lowerLens.map(this, current, p, i, _.map(subst)) + current = upperLens.map(this, current, p, i, _.map(subst)) + } + current.foreachTypeVar { tvar => + val TypeParamRef(binder, n) = tvar.origin + if (binder eq tl) tvar.setOrigin(tl1.paramRefs(n)) + } + constr.println(i"renamd $this to $current") + current } + def ensureFresh(tl: TypeLambda)(implicit ctx: Context): TypeLambda = + if (contains(tl)) { + var paramInfos = tl.paramInfos + if (tl.isInstanceOf[HKLambda]) { + // HKLambdas are hash-consed, need to create an artificial difference by adding + // a LazyRef to a bound. + val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos + paramInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 + } + ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType)) + } + else tl + override def checkClosed()(implicit ctx: Context): Unit = { def isFreeTypeParamRef(tp: Type) = tp match { case TypeParamRef(binder: TypeLambda, _) => !contains(binder) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ac6905ff05ae..68ca0fadd211 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -484,13 +484,13 @@ object SymDenotations { /** Is this symbol an anonymous class? */ final def isAnonymousClass(implicit ctx: Context): Boolean = - isClass && (initial.name startsWith str.ANON_CLASS) + isClass && initial.name.isAnonymousClassName final def isAnonymousFunction(implicit ctx: Context): Boolean = - this.symbol.is(Method) && (initial.name startsWith str.ANON_FUN) + this.symbol.is(Method) && initial.name.isAnonymousFunctionName final def isAnonymousModuleVal(implicit ctx: Context): Boolean = - this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) + this.symbol.is(ModuleVal) && initial.name.isAnonymousClassName /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index fdec03930357..5274a2beef9d 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -7,9 +7,11 @@ import Contexts._ import util.{SimpleIdentityMap, SimpleIdentitySet} import reporting._ import config.Config +import config.Printers.constr import collection.mutable import java.lang.ref.WeakReference import util.Stats +import Decorators._ import scala.annotation.internal.sharable @@ -17,7 +19,7 @@ object TyperState { @sharable private var nextId: Int = 0 } -class TyperState(previous: TyperState /* | Null */) { +class TyperState(private val previous: TyperState /* | Null */) { Stats.record("typerState") @@ -143,10 +145,11 @@ class TyperState(previous: TyperState /* | Null */) { def commit()(implicit ctx: Context): Unit = { Stats.record("typerState.commit") val targetState = ctx.typerState + if (constraint ne targetState.constraint) + constr.println(i"committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}") assert(isCommittable) - targetState.constraint = - if (targetState.constraint eq previousConstraint) constraint - else targetState.constraint & (constraint, otherHasErrors = reporter.errorsReported) + if (targetState.constraint eq previousConstraint) targetState.constraint = constraint + else targetState.mergeConstraintWith(this) constraint foreachTypeVar { tvar => if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState) } @@ -156,6 +159,9 @@ class TyperState(previous: TyperState /* | Null */) { isCommitted = true } + def mergeConstraintWith(that: TyperState)(implicit ctx: Context): Unit = + constraint = constraint & (that.constraint, otherHasErrors = that.reporter.errorsReported) + /** Make type variable instances permanent by assigning to `inst` field if * type variable instantiation cannot be retracted anymore. Then, remove * no-longer needed constraint entries. @@ -176,7 +182,12 @@ class TyperState(previous: TyperState /* | Null */) { constraint = constraint.remove(poly) } - override def toString: String = s"TS[$id]" + override def toString: String = { + def ids(state: TyperState): List[String] = + s"${state.id}${if (state.isCommittable) "" else "X"}" :: + (if (state.previous == null) Nil else ids(state.previous)) + s"TS[${ids(this).mkString(", ")}]" + } def stateChainStr: String = s"$this${if (previous == null) "" else previous.stateChainStr}" } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1754ee65724b..528983868395 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3667,7 +3667,14 @@ object Types { * `owningTree` and `owner` are used to determine whether a type-variable can be instantiated * at some given point. See `Inferencing#interpolateUndetVars`. */ - final class TypeVar(val origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType { + final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType { + + def origin: TypeParamRef = _origin + + /** Set origin to new parameter. Called if we merge two conflicting constraints. + * See OrderingConstraint#merge, OrderingConstraint#rename + */ + def setOrigin(p: TypeParamRef) = _origin = p /** The permanent instance type of the variable, or NoType is none is given yet */ private[this] var myInst: Type = NoType diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d43ab305932b..133c4637b1b6 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -15,7 +15,7 @@ import Denotations._ import SymDenotations._ import StdNames.{nme, tpnme} import ast.{Trees, untpd} -import typer.{Implicits, Namer} +import typer.{Implicits, Namer, Applications} import typer.ProtoTypes._ import Trees._ import TypeApplications._ @@ -578,6 +578,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") case Splice(tree) => keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") + case tree: Applications.IntegratedTypeArgs => + toText(tree.app) ~ Str("(with integrated type args)").provided(ctx.settings.YprintDebug.value) case Thicket(trees) => "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case _ => diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index f39d32d1abae..e0b3af4ad99b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -95,18 +95,19 @@ trait Reporting { this: Context => def strictWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = { val fullPos = addInlineds(pos) if (this.settings.strict.value) error(msg, fullPos) - else reportWarning(new ExtendMessage(() => msg)(_ + "\n(This would be an error under strict mode)").warning(fullPos)) + else reportWarning( + new ExtendMessage(() => msg)(_ + "\n(This would be an error under strict mode)") + .warning(fullPos)) } - def error(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = { - reporter.report(new Error(msg, addInlineds(pos))) - } - - def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = { + def error(msg: => Message, pos: SourcePosition = NoSourcePosition, sticky: Boolean = false): Unit = { val fullPos = addInlineds(pos) - if (ctx.scala2Mode) migrationWarning(msg, fullPos) else error(msg, fullPos) + reporter.report(if (sticky) new StickyError(msg, fullPos) else new Error(msg, fullPos)) } + def errorOrMigrationWarning(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = + if (ctx.scala2Mode) migrationWarning(msg, pos) else error(msg, pos) + def restrictionError(msg: => Message, pos: SourcePosition = NoSourcePosition): Unit = reporter.report { new ExtendMessage(() => msg)(m => s"Implementation restriction: $m").error(addInlineds(pos)) @@ -203,6 +204,9 @@ abstract class Reporter extends interfaces.ReporterResult { /** All errors reported by this reporter (ignoring outer reporters) */ def allErrors: List[Error] = errors + /** Were sticky errors reported? Overridden in StoreReporter. */ + def hasStickyErrors: Boolean = false + /** Have errors been reported by this reporter, or in the * case where this is a StoreReporter, by an outer reporter? */ diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index 7707dfd9c5e1..3806929f72f3 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -31,6 +31,9 @@ class StoreReporter(outer: Reporter) extends Reporter { override def hasUnreportedErrors: Boolean = outer != null && infos != null && infos.exists(_.isInstanceOf[Error]) + override def hasStickyErrors: Boolean = + infos != null && infos.exists(_.isInstanceOf[StickyError]) + override def removeBufferedMessages(implicit ctx: Context): List[MessageContainer] = if (infos != null) try infos.toList finally infos = null else Nil diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index dfe24cd1c91b..7d7d4255043a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -35,6 +35,15 @@ object messages { pos: SourcePosition ) extends MessageContainer(msgFn, pos, ERROR) + /** A sticky error is an error that should not be hidden by backtracking and + * trying some alternative path. Typcially, errors issued after catching + * a TypeError exception are sticky. + */ + class StickyError( + msgFn: => Message, + pos: SourcePosition + ) extends Error(msgFn, pos) + class Warning( msgFn: => Message, pos: SourcePosition diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index d66b61cd8c91..e97d8da17ab0 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -249,7 +249,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder case ex: TypeError => // See neg/i1750a for an example where a cyclic error can arise. // The root cause in this example is an illegal "override" of an inner trait - ctx.error(ex.toMessage, csym.sourcePos) + ctx.error(ex.toMessage, csym.sourcePos, sticky = true) defn.ObjectType :: Nil } if (ValueClasses.isDerivedValueClass(csym)) { diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index f0053b3abfe9..8ec26acfcf5e 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -64,7 +64,7 @@ abstract class MacroTransform extends Phase { } catch { case ex: TypeError => - ctx.error(ex.toMessage, tree.sourcePos) + ctx.error(ex.toMessage, tree.sourcePos, sticky = true) tree } diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index c42e6e1b7132..0753f260d6dc 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -205,7 +205,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { } catch { case ex: TypeError => - ctx.error(ex.toMessage, tree.sourcePos) + ctx.error(ex.toMessage, tree.sourcePos, sticky = true) tree } if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start) diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 052216bcfb5e..bf7d9481d6d9 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -134,7 +134,7 @@ object OverridingPairs { case ex: TypeError => // See neg/i1750a for an example where a cyclic error can arise. // The root cause in this example is an illegal "override" of an inner trait - ctx.error(ex.toMessage, base.sourcePos) + ctx.error(ex.toMessage, base.sourcePos, sticky = true) } } else { curEntry = curEntry.prev diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ecdce2384336..f7a6fa931185 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -186,9 +186,12 @@ object Applications { def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree - /** A wrapper indicating that its argument is an application of an extension method. + /** A wrapper indicating that its `app` argument has already integrated the type arguments + * of the expected type, provided that type is a (possibly ignored) PolyProto. + * I.e., if the expected type is a PolyProto, then `app` will be a `TypeApply(_, args)` where + * `args` are the type arguments of the expected type. */ - class ExtMethodApply(val app: Tree)(implicit @constructorOnly src: SourceFile) extends tpd.Tree { + class IntegratedTypeArgs(val app: Tree)(implicit @constructorOnly src: SourceFile) extends tpd.Tree { override def span = app.span def canEqual(that: Any): Boolean = app.canEqual(that) @@ -196,19 +199,24 @@ object Applications { def productElement(n: Int): Any = app.productElement(n) } - /** The unapply method of this extractor also recognizes ExtMethodApplys in closure blocks. + /** The unapply method of this extractor also recognizes IntegratedTypeArgs in closure blocks. * This is necessary to deal with closures as left arguments of extension method applications. * A test case is i5606.scala */ - object ExtMethodApply { - def apply(app: Tree)(implicit ctx: Context) = new ExtMethodApply(app) + object IntegratedTypeArgs { + def apply(app: Tree)(implicit ctx: Context) = new IntegratedTypeArgs(app) def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match { - case tree: ExtMethodApply => Some(tree.app) - case Block(stats, ExtMethodApply(app)) => Some(tpd.cpy.Block(tree)(stats, app)) + case tree: IntegratedTypeArgs => Some(tree.app) + case Block(stats, IntegratedTypeArgs(app)) => Some(tpd.cpy.Block(tree)(stats, app)) case _ => None } } + /** A wrapper indicating that its argument is an application of an extension method. + */ + class ExtMethodApply(app: Tree)(implicit @constructorOnly src: SourceFile) + extends IntegratedTypeArgs(app) + /** 1. If we are in an inline method but not in a nested quote, mark the inline method * as a macro. * @@ -797,6 +805,21 @@ trait Applications extends Compatibility { self: Typer with Dynamic => if (ctx.owner.isClassConstructor && untpd.isSelfConstrCall(app)) ctx.thisCallArgContext else ctx + /** Typecheck the function part of an application. + * Fallback if this fails: try to convert `E` to `new E`. + */ + def typedFunPart(fn: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = + tryEither { implicit ctx => + typedExpr(fn, pt) + } { (result, tstate) => + def fallBack = { + tstate.commit() + result + } + if (untpd.isPath(fn)) tryNew(untpd)(fn, pt, fallBack) + else fallBack + } + /** Typecheck application. Result could be an `Apply` node, * or, if application is an operator assignment, also an `Assign` or * Block node. @@ -806,7 +829,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def realApply(implicit ctx: Context): Tree = track("realApply") { val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isContextual)(argCtx(tree)) record("typedApply") - val fun1 = typedExpr(tree.fun, originalProto) + val fun1 = typedFunPart(tree.fun, originalProto) // Warning: The following lines are 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, @@ -941,8 +964,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val isNamed = hasNamedArg(tree.args) val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) record("typedTypeApply") - handleMeta(typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { - case ExtMethodApply(app) => + handleMeta(typedFunPart(tree.fun, PolyProto(typedArgs, pt)) match { + case IntegratedTypeArgs(app) => app case _: TypeApply if !ctx.isAfterTyper => errorTree(tree, "illegal repeated type application") @@ -1391,11 +1414,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => tp } - val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol - val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol - val ownerScore = compareOwner(owner1, owner2) - def compareWithTypes(tp1: Type, tp2: Type) = { + val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner) def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2) def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index fef3c043a3a0..8d197bf7fbf3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -748,7 +748,7 @@ trait Checking { typr.println(i"check no double declarations $cls") def checkDecl(decl: Symbol): Unit = { - for (other <- seen(decl.name)) { + for (other <- seen(decl.name) if (!decl.isAbsent && !other.isAbsent)) { typr.println(i"conflict? $decl $other") def javaFieldMethodPair = decl.is(JavaDefined) && other.is(JavaDefined) && diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index b913d7d5a023..855247a678c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -16,14 +16,14 @@ object ErrorReporting { import tpd._ - def errorTree(tree: untpd.Tree, msg: => Message, pos: SourcePosition)(implicit ctx: Context): tpd.Tree = - tree.withType(errorType(msg, pos)) + def errorTree(tree: untpd.Tree, msg: => Message, pos: SourcePosition, sticky: Boolean = false)(implicit ctx: Context): tpd.Tree = + tree.withType(errorType(msg, pos, sticky)) def errorTree(tree: untpd.Tree, msg: => Message)(implicit ctx: Context): tpd.Tree = errorTree(tree, msg, tree.sourcePos) - def errorType(msg: => Message, pos: SourcePosition)(implicit ctx: Context): ErrorType = { - ctx.error(msg, pos) + def errorType(msg: => Message, pos: SourcePosition, sticky: Boolean = false)(implicit ctx: Context): ErrorType = { + ctx.error(msg, pos, sticky) ErrorType(msg) } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 1b0c289eab23..99e6800ad66d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1101,7 +1101,7 @@ trait Implicits { self: Typer => } else { val returned = - if (cand.isExtension) Applications.ExtMethodApply(adapted).withType(adapted.tpe) + if (cand.isExtension) new Applications.ExtMethodApply(adapted).withType(adapted.tpe) else adapted SearchSuccess(returned, ref, cand.level)(ctx.typerState, ctx.gadt) } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index c074ca7a45b4..7e6e801c357f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -244,7 +244,7 @@ object ProtoTypes { * [](args): resultType */ case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer, - override val isContextual: Boolean, state: FunProtoState = new FunProtoState)(implicit ctx: Context) + override val isContextual: Boolean, state: FunProtoState = new FunProtoState)(implicit val ctx: Context) extends UncachedGroundType with ApplyingProto with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType @@ -298,13 +298,26 @@ object ProtoTypes { /** The typed arguments. This takes any arguments already typed using * `typedArg` into account. + * + * Arguments are typechecked in the typerState where the FunProto was created. + * However, any constraint changes are also propagated to the currently passed + * context. + * */ - def unforcedTypedArgs: List[Tree] = + def unforcedTypedArgs(implicit ctx: Context): List[Tree] = if (state.typedArgs.size == args.length) state.typedArgs else { - val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force = false)) - if (!args1.exists(arg => isUndefined(arg.tpe))) state.typedArgs = args1 - args1 + val prevConstraint = this.ctx.typerState.constraint + + try { + implicit val ctx = this.ctx + val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force = false)) + if (!args1.exists(arg => isUndefined(arg.tpe))) state.typedArgs = args1 + args1 + } + finally + if (this.ctx.typerState.constraint ne prevConstraint) + ctx.typerState.mergeConstraintWith(this.ctx.typerState) } /** Type single argument and remember the unadapted result in `myTypedArg`. @@ -372,7 +385,7 @@ object ProtoTypes { * [](args): resultType, where args are known to be typed */ class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isContextual: Boolean)(implicit ctx: Context) extends FunProto(args, resultType)(typer, isContextual)(ctx) { - override def unforcedTypedArgs: List[tpd.Tree] = args + override def unforcedTypedArgs(implicit ctx: Context): List[tpd.Tree] = args override def withContext(ctx: Context): FunProtoTyped = this } @@ -485,20 +498,7 @@ object ProtoTypes { tt.withType(tvar) } - /** Ensure that `tl` is not already in constraint, make a copy of necessary */ - def ensureFresh(tl: TypeLambda): TypeLambda = - if (state.constraint contains tl) { - var paramInfos = tl.paramInfos - if (tl.isInstanceOf[HKLambda]) { - // HKLambdas are hash-consed, need to create an artificial difference by adding - // a LazyRef to a bound. - val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos - paramInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 - } - ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType)) - } - else tl - val added = ensureFresh(tl) + val added = state.constraint.ensureFresh(tl) val tvars = if (addTypeVars) newTypeVars(added) else Nil ctx.typeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) (added, tvars) diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 94305da890ce..a725ca30b6be 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -8,7 +8,8 @@ import Symbols._ import StdNames._ import Decorators._ import typer.ProtoTypes._ -import ast.{tpd, untpd} +import ast.{tpd, untpd, Trees} +import Trees._ import scala.util.control.NonFatal import util.Spans.Span @@ -64,6 +65,9 @@ class ReTyper extends Typer with ReChecking { override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = promote(tree) + override def typedFunPart(fn: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = + typedExpr(fn, pt) + override def typedBind(tree: untpd.Bind, pt: Type)(implicit ctx: Context): Bind = { assertTyped(tree) val body1 = typed(tree.body, pt) @@ -93,6 +97,9 @@ class ReTyper extends Typer with ReChecking { override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType, locked: TypeVars)(fallBack: => Tree)(implicit ctx: Context): Tree = fallBack + override def tryNew[T >: Untyped <: Type] + (treesInst: Instance[T])(tree: Trees.Tree[T], pt: Type, fallBack: => Tree)(implicit ctx: Context): Tree = fallBack + override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = () override def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(implicit ctx: Context): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1ea27a898760..1f04a4c03b6f 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -987,7 +987,7 @@ class RefChecks extends MiniPhase { thisPhase => tree } catch { case ex: TypeError => - ctx.error(ex.toMessage, tree.sourcePos) + ctx.error(ex.toMessage, tree.sourcePos, sticky = true) tree } diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 5bd58b62a554..9886fdf04017 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -199,7 +199,7 @@ trait TypeAssigner { val d2 = pre.nonPrivateMember(name) if (reallyExists(d2) && firstTry) test(NamedType(pre, name, d2), false) - else if (pre.derivesFrom(defn.DynamicClass)) { + else if (pre.derivesFrom(defn.DynamicClass) && name.isTermName) { TryDynamicCallType } else { val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) @@ -238,7 +238,7 @@ trait TypeAssigner { val mbr = qualType.member(name) if (reallyExists(mbr)) qualType.select(name, mbr) - else if (qualType.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) + else if (qualType.derivesFrom(defn.DynamicClass) && name.isTermName && !Dynamic.isDynamicMethod(name)) TryDynamicCallType else if (qualType.isErroneous || name.toTermName == nme.ERROR) UnspecifiedErrorType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index df2e295598f7..e2667a9d1ccd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -27,7 +27,7 @@ import EtaExpansion.etaExpand import util.Spans._ import util.common._ import util.Property -import Applications.{ExtMethodApply, productSelectorTypes, wrapDefs} +import Applications.{ExtMethodApply, IntegratedTypeArgs, productSelectorTypes, wrapDefs} import collection.mutable import annotation.tailrec @@ -441,25 +441,25 @@ class Typer extends Namer tree } - private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select = - Applications.handleMeta(checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt)) + def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Tree = qual match { + case qual @ IntegratedTypeArgs(app) => + pt.revealIgnored match { + case _: PolyProto => qual // keep the IntegratedTypeArgs to strip at next typedTypeApply + case _ => app + } + case qual => + if (tree.name.isTypeName) checkStable(qual.tpe, qual.sourcePos) + val select = Applications.handleMeta( + checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt)) + if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) + else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select + else typedDynamicSelect(tree, Nil, pt) + } def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { def typeSelectOnTerm(implicit ctx: Context): Tree = - typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) match { - case qual1 @ ExtMethodApply(app) => - pt.revealIgnored match { - case _: PolyProto => qual1 // keep the ExtMethodApply to strip at next typedTypeApply - case _ => app - } - case qual1 => - if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.sourcePos) - val select = typedSelect(tree, pt, qual1) - if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) - else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select - else typedDynamicSelect(tree, Nil, pt) - } + typedSelect(tree, pt, typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))) def typeSelectOnType(qual: untpd.Tree)(implicit ctx: Context) = typedSelect(untpd.cpy.Select(tree)(qual, tree.name.toTypeName), pt) @@ -2113,7 +2113,7 @@ class Typer extends Namer try adapt(typedUnadapted(tree, pt, locked), pt, locked) catch { case ex: TypeError => - errorTree(tree, ex.toMessage, tree.sourcePos.focus) + errorTree(tree, ex.toMessage, tree.sourcePos.focus, sticky = true) // This uses tree.span.focus instead of the default tree.span, because: // - since tree can be a top-level definition, tree.span can point to the whole definition // - that would in turn hide all other type errors inside tree. @@ -2203,7 +2203,7 @@ class Typer extends Namer def tryEither[T](op: Context => T)(fallBack: (T, TyperState) => T)(implicit ctx: Context): T = { val nestedCtx = ctx.fresh.setNewTyperState() val result = op(nestedCtx) - if (nestedCtx.reporter.hasErrors) { + if (nestedCtx.reporter.hasErrors && !nestedCtx.reporter.hasStickyErrors) { record("tryEither.fallBack") fallBack(result, nestedCtx.typerState) } @@ -2232,6 +2232,55 @@ class Typer extends Namer case _ => false } + /** Try to rename `tpt` to a type `T` and typecheck `new T` with given expected type `pt`. + * The operation is called from either `adapt` or `typedApply`. `adapt` gets to call `tryNew` + * for calls `p.C(..)` if there is a value `p.C`. `typedApply` calls `tryNew` as a fallback + * in case typing `p.C` fails since there is no value with path `p.C`. The call from `adapt` + * is more efficient since it re-uses the prefix `p` in typed form. + */ + def tryNew[T >: Untyped <: Type] + (treesInst: Instance[T])(tree: Trees.Tree[T], pt: Type, fallBack: => Tree)(implicit ctx: Context): Tree = { + + def tryWithType(tpt: untpd.Tree): Tree = + tryEither { implicit ctx => + val tycon = typed(tpt) + if (ctx.reporter.hasErrors) + EmptyTree // signal that we should return the error in fallBack + else { + def recur(tpt: Tree, pt: Type): Tree = pt.revealIgnored match { + case PolyProto(targs, pt1) if !targs.exists(_.isInstanceOf[NamedArg]) => + // Applications with named arguments cannot be converted, since new expressions + // don't accept named arguments + IntegratedTypeArgs(recur(AppliedTypeTree(tpt, targs), pt1)) + case _ => + typed(untpd.Select(untpd.New(untpd.TypedSplice(tpt)), nme.CONSTRUCTOR), pt) + } + recur(tycon, pt) + .reporting(res => i"try new $tree -> $res", typr) + } + } { (nu, nuState) => + if (nu.isEmpty) fallBack + else { + // we found a type constructor, signal the error in its application instead of the original one + nuState.commit() + nu + } + } + + tree match { + case Ident(name) => + tryWithType(cpy.Ident(tree)(name.toTypeName)) + case Select(qual, name) => + val qual1 = treesInst match { + case `tpd` => untpd.TypedSplice(qual) + case `untpd` => qual + } + tryWithType(cpy.Select(tree)(qual1, name.toTypeName)) + case _ => + fallBack + } + } + /** Potentially add apply node or implicit conversions. Before trying either, * if the function is applied to an empty parameter list (), we try * @@ -2273,7 +2322,8 @@ class Typer extends Namer } def tryImplicit(fallBack: => Tree) = - tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked).getOrElse(fallBack) + tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked) + .getOrElse(tryNew(tpd)(tree, pt, fallBack)) if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver)) // Suppress insertion of apply or implicit conversion on extension method receiver @@ -2376,64 +2426,64 @@ class Typer extends Namer tree.withType(mt.resultType) } - def adaptOverloaded(ref: TermRef) = { - val altDenots = ref.denot.alternatives - typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%, %") - val alts = altDenots.map(TermRef(ref.prefix, ref.name, _)) - resolveOverloaded(alts, pt) match { - case alt :: Nil => - readaptSimplified(tree.withType(alt)) - case Nil => - def noMatches = - errorTree(tree, NoMatchingOverload(altDenots, pt)(err)) - def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil - pt match { - case pt: FunProto if !pt.isContextual => - // insert apply or convert qualifier only for a regular application - tryInsertApplyOrImplicit(tree, pt, locked)(noMatches) - case _ => - if (altDenots exists (_.info.paramInfoss == ListOfNil)) - typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked) - else - noMatches - } - case alts => - if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType) - else { - val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) - errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) - } - } - } - - def isUnary(tp: Type): Boolean = tp match { - case tp: MethodicType => - tp.firstParamTypes match { - case ptype :: Nil => !ptype.isRepeatedParam - case _ => false + def adaptOverloaded(ref: TermRef) = { + val altDenots = ref.denot.alternatives + typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%, %") + val alts = altDenots.map(TermRef(ref.prefix, ref.name, _)) + resolveOverloaded(alts, pt) match { + case alt :: Nil => + readaptSimplified(tree.withType(alt)) + case Nil => + def noMatches = + errorTree(tree, NoMatchingOverload(altDenots, pt)(err)) + def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil + pt match { + case pt: FunProto if !pt.isContextual => + // insert apply or convert qualifier only for a regular application + tryInsertApplyOrImplicit(tree, pt, locked)(noMatches) + case _ => + if (altDenots exists (_.info.paramInfoss == ListOfNil)) + typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt, locked) + else + noMatches + } + case alts => + if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType) + else { + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) } - case tp: TermRef => - tp.denot.alternatives.forall(alt => isUnary(alt.info)) - case _ => - false } + } - def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { - case wtp: MethodOrPoly => - def methodStr = methPart(tree).symbol.showLocated - if (matchingApply(wtp, pt)) - if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) - adapt(tree, pt.tupled, locked) - else - tree - else if (wtp.isContextual) - adaptNoArgs(wtp) // insert arguments implicitly - else - errorTree(tree, em"Missing arguments for $methodStr") - case _ => tryInsertApplyOrImplicit(tree, pt, locked) { - errorTree(tree, MethodDoesNotTakeParameters(tree)) + def isUnary(tp: Type): Boolean = tp match { + case tp: MethodicType => + tp.firstParamTypes match { + case ptype :: Nil => !ptype.isRepeatedParam + case _ => false } + case tp: TermRef => + tp.denot.alternatives.forall(alt => isUnary(alt.info)) + case _ => + false + } + + def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { + case wtp: MethodOrPoly => + def methodStr = methPart(tree).symbol.showLocated + if (matchingApply(wtp, pt)) + if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) + adapt(tree, pt.tupled, locked) + else + tree + else if (wtp.isContextual) + adaptNoArgs(wtp) // insert arguments implicitly + else + errorTree(tree, em"Missing arguments for $methodStr") + case _ => tryInsertApplyOrImplicit(tree, pt, locked) { + errorTree(tree, MethodDoesNotTakeParameters(tree)) } + } /** If `tp` is a TypeVar which is fully constrained (i.e. its upper bound `hi` conforms * to its lower bound `lo`), replace `tp` by `hi`. This is necessary to @@ -2828,13 +2878,13 @@ class Typer extends Namer } } catch { - case ex: TypeError => errorTree(tree, ex.toMessage, tree.sourcePos) + case ex: TypeError => errorTree(tree, ex.toMessage, tree.sourcePos, sticky = true) } val nestedCtx = ctx.fresh.setNewTyperState() val app = tryExtension(nestedCtx) if (!app.isEmpty && !nestedCtx.reporter.hasErrors) { nestedCtx.typerState.commit() - return ExtMethodApply(app).withType(WildcardType) + return new ExtMethodApply(app).withType(WildcardType) // Use wildcard type in order not to prompt any further adaptations such as eta expansion // before the method is fully applied. } @@ -2932,7 +2982,7 @@ class Typer extends Namer adaptToArgs(wtp, pt) case pt: PolyProto => tree match { - case _: ExtMethodApply => tree + case _: IntegratedTypeArgs => tree case _ => tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply } case _ => diff --git a/docs/docs/reference/features-classification.md b/docs/docs/reference/features-classification.md index 133b104c4399..0eb8e4fecf10 100644 --- a/docs/docs/reference/features-classification.md +++ b/docs/docs/reference/features-classification.md @@ -44,8 +44,9 @@ These features replace existing constructs with the aim of making the language s of value classes while guaranteeing absence of boxing, - [Toplevel definitions](https://dotty.epfl.ch/docs/reference/dropped-features/package-objects.html) replace package objects, dropping syntactic boilerplate, - [Vararg patterns](https://dotty.epfl.ch/docs/reference/changed-features/vararg-patterns.html) now use the form `: _*` instead of `@ _*`, mirroring vararg expressions, - - [Synthesized creation methods](https://contributors.scala-lang.org/t/expunging-new-from-scala-3/2868/81) allow to use simple function call syntax - instead of `new` expressions (under discussion, not implemented). + - [Creator applications](https://dotty.epfl.ch/docs/reference/other-new-features/creator-applications.html) allow to use simple function call syntax + instead of `new` expressions. `new` expressions stay around as a fallback for + the cases where creator applications cannot be used. With the exception of early initializers and old-style vararg patterns, all superseded features continue to be available in Scala 3.0. The plan is to deprecate and phase them out later. diff --git a/docs/docs/reference/other-new-features/creator-applications.md b/docs/docs/reference/other-new-features/creator-applications.md new file mode 100644 index 000000000000..b6c94b336aa4 --- /dev/null +++ b/docs/docs/reference/other-new-features/creator-applications.md @@ -0,0 +1,43 @@ +--- +layout: doc-page +title: "Creator Applications" +--- + +Creator applications allow to use simple function call syntax to create instances +of a class, even if there is no apply method implemented. Example: +```scala +class StringBuilder(s: String) { + def this() = this(s) +} + +StringBuilder("abc") // same as new StringBuilder("abc") +StringBuilder() // same as new StringBuilder() +``` +Creator applications generalize a functionality provided so far only for case classes, but the mechanism how this is achieved is different. Instead generating an apply method, the compiler adds a new possible interpretation to a function call `f(args)`. The previous rules are: + +Given a function call `f(args)`, + + - if `f` is a method applicable to `args`, typecheck `f(args)` unchanged, + - otherwise, if `f` has an `apply` method applicable to `args` as a member, continue with `f.apply(args)`, + - otherwise, if `f` is of the form `p.m` and there is an implicit conversion `c` applicable to `p` so that `c(p).m` is applicable to `args`, continue with `c(p).m(args)` + +There's now a fourth rule following these rules: + + - otherwise, if `f` is syntactically a stable identifier, and `new f` where `f` is interpreted as a type identifier is applicable to `args`, continue with `new f(args)`. + + Analogously, the possible interpretations of a function call with type arguments `f[targs]` are augmented with the following interpretation as a final fallback: + + - if `f` is syntactically a stable identifier, and `new f[targs]` where `f` is interpreted as a type identifier is well-typed, continue with `new f[targs]`. + +### Motivation + +Leaving out `new` hides an implementation detail and makes code more pleasant to read. Even though it requires a new rule, it will likely increase the perceived regularity of the language, since case classes already provide function call creation syntax (and are often defined for this reason alone). + +### Discussion + +An alternative design would auto-generate `apply` methods for normal classes, in the same way it is done now for case classes. This design was tried but abandoned since it +caused numerous problems, including + + - overloading ambiguities + - overriding errors + - shadowing of user-defined `apply` methods by more specific auto-generated ones. \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index ca7e91da3f7e..652ee70bad09 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -69,6 +69,8 @@ sidebar: subsection: - title: Trait Parameters url: docs/reference/other-new-features/trait-parameters.html + - title: Creator Applications + url: docs/reference/other-new-features/creator-applications.html - title: Inlining by Rewriting url: docs/reference/other-new-features/inline.html - title: Meta Programming diff --git a/tests/neg/creator-applys.scala b/tests/neg/creator-applys.scala new file mode 100644 index 000000000000..93245e519e6a --- /dev/null +++ b/tests/neg/creator-applys.scala @@ -0,0 +1,35 @@ +class Test { + class A { + def run = "A" + } + object A + class B[T] { + def run = "B" + } + class C[S, T](x: S, y: T) { + def run = s"C $x $y" + } + + val x1 = new Test().A() // error: object A does not take parameters + val x2 = new Test().B() // error: value B is not a member of Test + val x3 = new Test().B[Int]() // error: value B is not a member of Test +} + + +object Test2 { + class A(s: String = "A") { + def run = s + } + object A { + def apply() = A("X") // error: recursive method needs return type + } +} + +object Test3 { + class A(s: String = "A") { + def run = s + } + object A { + def apply(): A = A("X") // error too many arguments + } +} \ No newline at end of file diff --git a/tests/neg/i1648.scala b/tests/neg/i1648.scala index 52ba78a2dccd..812cb863a237 100644 --- a/tests/neg/i1648.scala +++ b/tests/neg/i1648.scala @@ -1 +1 @@ -class Foo { Object[A] } // error // error +class Foo { Object[A] } // error diff --git a/tests/pos/concats.scala b/tests/pos/concats.scala new file mode 100644 index 000000000000..94635b6b453e --- /dev/null +++ b/tests/pos/concats.scala @@ -0,0 +1,11 @@ +object Test { + type Name = String + val CommonOpNames: Set[Name] = Set("OR", "XOR") + val ConversionNames: Set[Name] = Set("toByte") + val BooleanOpNames: Set[Name] = Set("ZOR") ++ CommonOpNames + val NumberOpNames: Set[Name] = ( + Set("ADD") + ++ Set("UNARY_+", "UNARY_-") + ++ CommonOpNames + ) +} \ No newline at end of file diff --git a/tests/run/creator-applys.scala b/tests/run/creator-applys.scala new file mode 100644 index 000000000000..7904aff6ff0a --- /dev/null +++ b/tests/run/creator-applys.scala @@ -0,0 +1,164 @@ +object Test extends App { + class A { + def run = "A" + } + object A + class B[T] { + def run = "B" + } + object B + class C[S, T](x: S, y: T) { + def run = s"C $x $y" + } + object C + + val x1 = A() + assert(x1.run == "A") + + val x2 = B[String]() + assert(x2.run == "B") + + val x3: B[String] = Test.B() + assert(x3.run == "B") + + val x4: C[String, Int] = C("a", 1) + assert(x4.run == "C a 1") + + val x5 = C[String, Int]("a", 1) + assert(x5.run == "C a 1") + + val x5a = C[S = String, T = Int]("a", 1) + assert(x5a.run == "C a 1") + + val x5b = C[T = Int]("a", 1) + assert(x5b.run == "C a 1") + + val x6 = C("a", 1) + assert((x6: C[String, Int]).run == "C a 1") + Test1 +} + +object Test1 { + class A { + def run = "A" + } + class B[T] { + def run = "B" + } + class C[S, T](x: S, y: T) { + def run = s"C $x $y" + } + + val x1 = A() + assert(x1.run == "A") + + val x2 = B[String]() + assert(x2.run == "B") + + val x3: B[String] = B() + assert(x3.run == "B") + + val x4: C[String, Int] = C("a", 1) + assert(x4.run == "C a 1") + + val x5 = C[String, Int]("a", 1) + assert(x5.run == "C a 1") + + val x6 = C("a", 1) + assert((x6: C[String, Int]).run == "C a 1") + Test2 +} + +object Test2 { + val x1 = Test.A() + assert(x1.run == "A") + + val x2 = Test.B[String]() + assert(x2.run == "B") + + val x3: Test.B[String] = Test.B() + assert(x3.run == "B") + + val x4: Test.C[String, Int] = Test.C("a", 1) + assert(x4.run == "C a 1") + + val x5 = Test.C[String, Int]("a", 1) + assert(x5.run == "C a 1") + + val x6 = Test.C("a", 1) + assert((x6: Test.C[String, Int]).run == "C a 1") + Test3 +} + +object Test3 { + val x1 = Test1.A() + assert(x1.run == "A") + + val x2 = Test1.B[String]() + assert(x2.run == "B") + + val x3: Test1.B[String] = Test1.B() + assert(x3.run == "B") + + val x4: Test1.C[String, Int] = Test1.C("a", 1) + assert(x4.run == "C a 1") + + val x5 = Test1.C[String, Int]("a", 1) + assert(x5.run == "C a 1") + + val x6 = Test1.C("a", 1) + assert((x6: Test1.C[String, Int]).run == "C a 1") + Test4 +} + +object Test4 { + type A = Test.A + type AA[T] = A + type B[T] = Test.B[T] + type C[T] = Test.C[T, Int] + + val x1 = A() + assert(x1.run == "A") + + val x1a = AA[Int]() + assert(x1a.run == "A") + + val x2 = B[String]() + assert(x2.run == "B") + + val x3: B[String] = B() + assert(x3.run == "B") + + val x5 = C[String]("a", 1) + assert(x5.run == "C a 1") + Test5 +} + +object Test5 { + val x1 = Test4.A() + assert(x1.run == "A") + + val x1a = Test4.AA[Int]() + assert(x1a.run == "A") + + val x2 = Test4.B[String]() + assert(x2.run == "B") + + val x3: Test4.B[String] = Test4.B() + assert(x3.run == "B") + + val x5 = Test4.C[String]("a", 1) + assert(x5.run == "C a 1") + Test6 +} + +object Test6 { + class A(s: String = "A") { + def run = s + } + object A { + def apply(): A = new A("X") + } + val x1 = A() + assert(x1.run == "X") +} \ No newline at end of file