From 7c74ea5081aceb0e946a7d21c1debcc24e4ddb5f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 5 Mar 2019 14:26:45 +0100 Subject: [PATCH 01/17] Make TypeErrors sticky The purpose of this change is that we should always report a thrown TypeError and not hide it by backtracking into some other alternative. Wihthout this change test i4564.scala fails after applying the creator applications commit, since some errors about recursive methods lacking a result type are hidden by using a constructor instead. Ehere is an example: ``` object NoClashNoSig { private def apply(x: Boolean) = if (x) NoClashNoSig(1) else ??? // error: overloaded method apply needs result type } case class NoClashNoSig private(x: Int) ``` With introduction of creator applications, `NoClashSig` in `apply` would call the constructor, effectively hiding the "overloaded method apply needs result type" error. This is not what we want. --- .../dotty/tools/dotc/reporting/Reporter.scala | 18 +++++++++++------- .../tools/dotc/reporting/StoreReporter.scala | 3 +++ .../dotc/reporting/diagnostic/messages.scala | 9 +++++++++ .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- .../tools/dotc/transform/MacroTransform.scala | 2 +- .../dotty/tools/dotc/transform/MegaPhase.scala | 2 +- .../tools/dotc/transform/OverridingPairs.scala | 2 +- .../tools/dotc/typer/ErrorReporting.scala | 8 ++++---- .../src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +++--- 10 files changed, 35 insertions(+), 19 deletions(-) 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/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/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/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index df2e295598f7..d488c83466e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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) } @@ -2828,7 +2828,7 @@ 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) From 7ca527bcc7f9bfa7d857ae001eab91f00b5bdbc5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 5 Mar 2019 14:34:56 +0100 Subject: [PATCH 02/17] Don't try dynamic calls for type selections. Without this change, tests/neg/applydynamic_sip.scala fails once creator applications are introduced. --- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 54441d60151f3d57ff54c23731b17521cee7adc5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 7 Mar 2019 11:18:09 +0100 Subject: [PATCH 03/17] Generalize ExtMethodApply to IntegratedTypeArgs Creator methods create new sitation where we need to integrate type arguments of the prototype in a term. Create a superclass IntegratedTypeArgs of ExtMethodApply to cover both that and extension methods. --- .../dotty/tools/dotc/typer/Applications.scala | 22 ++-- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 116 +++++++++--------- 3 files changed, 73 insertions(+), 67 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ecdce2384336..df965143e255 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -186,9 +186,10 @@ 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. */ - 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 +197,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. * @@ -942,7 +948,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => 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) => + case IntegratedTypeArgs(app) => app case _: TypeApply if !ctx.isAfterTyper => errorTree(tree, "illegal repeated type application") 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/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d488c83466e3..0b236bdb14b5 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 @@ -448,9 +448,9 @@ class Typer extends Namer def typeSelectOnTerm(implicit ctx: Context): Tree = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) match { - case qual1 @ ExtMethodApply(app) => + case qual1 @ IntegratedTypeArgs(app) => pt.revealIgnored match { - case _: PolyProto => qual1 // keep the ExtMethodApply to strip at next typedTypeApply + case _: PolyProto => qual1 // keep the IntegratedTypeArgs to strip at next typedTypeApply case _ => app } case qual1 => @@ -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 && !nestedCtx.reporter.hasStickyErrors) + if (nestedCtx.reporter.hasErrors && !nestedCtx.reporter.hasStickyErrors) { record("tryEither.fallBack") fallBack(result, nestedCtx.typerState) } @@ -2376,64 +2376,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 @@ -2834,7 +2834,7 @@ class Typer extends Namer 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. } From 7cf15bc9f6ad7201bf8b342570a436fd5c297779 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 5 Mar 2019 14:37:41 +0100 Subject: [PATCH 04/17] Fallback from A to new A --- .../src/dotty/tools/dotc/typer/Typer.scala | 31 ++++++++- tests/run/creator-applys.scala | 69 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 tests/run/creator-applys.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0b236bdb14b5..a66ce634bf45 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2272,8 +2272,37 @@ class Typer extends Namer else try adapt(simplify(sel, pt1, locked), pt1, locked) finally sel.removeAttachment(InsertedApply) } + def tryNew(fallBack: => Tree): Tree = { + + def tryNewWithType(tpt: untpd.Tree): Tree = + tryEither { implicit ctx => + val tycon = typed(tpt.withSpan(tree.span)) + if (ctx.reporter.hasErrors) + EmptyTree // signal that we should return the error in fallBack + else + typed(untpd.Select(untpd.New(untpd.TypedSplice(tycon)), nme.CONSTRUCTOR), pt) + } { (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) => + tryNewWithType(untpd.Ident(name.toTypeName)) + case Select(qual, name) => + tryNewWithType(untpd.Select(untpd.TypedSplice(qual), name.toTypeName)) + case _ => + fallBack + } + } + def tryImplicit(fallBack: => Tree) = - tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked).getOrElse(fallBack) + tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked) + .getOrElse(tryNew(fallBack)) if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver)) // Suppress insertion of apply or implicit conversion on extension method receiver diff --git a/tests/run/creator-applys.scala b/tests/run/creator-applys.scala new file mode 100644 index 000000000000..efdf8f90bb6b --- /dev/null +++ b/tests/run/creator-applys.scala @@ -0,0 +1,69 @@ +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 = Test.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") +/* + val x7 = C[S = String]("a", 1) + assert((x7: C[String, Int]).run == "C a 1") + + val x8 = C[T = Int]("a", 1) + assert((x8: C[String, Int]).run == "C a 1") +*/ +} +/* +object Test2 { + 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 = Test.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") +}*/ \ No newline at end of file From 5ce951f7f807610c95ecbd0eee99f5ba02db6f40 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 11 Mar 2019 13:38:17 +0100 Subject: [PATCH 05/17] Add NameOps methods testing for anonymous classes and functions --- compiler/src/dotty/tools/dotc/core/NameOps.scala | 2 ++ compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) 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/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 From 996424ef8b3cf6ae41ebb087e731a95ac445bb7f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 12 Mar 2019 10:10:53 +0100 Subject: [PATCH 06/17] Code simplification --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index df965143e255..95b3c83aa84f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1397,11 +1397,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) From 5d8725d5dd5b5f931637ed68e8495b2fcb042ada Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 12 Mar 2019 10:11:35 +0100 Subject: [PATCH 07/17] Don't issue double def errors involving absent members --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) && From a989de8d25643e8e24af9a8b0fea533a731a79dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Mar 2019 08:41:30 +0100 Subject: [PATCH 08/17] Improve treatment of typedArgs --- .../dotty/tools/dotc/core/TyperState.scala | 8 +++--- .../dotty/tools/dotc/typer/ProtoTypes.scala | 25 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index fdec03930357..c14f5f456533 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -144,9 +144,8 @@ class TyperState(previous: TyperState /* | Null */) { Stats.record("typerState.commit") val targetState = ctx.typerState 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 +155,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. diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index c074ca7a45b4..4e36002d9284 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 } From b864fdf8719bd64066e96d00639ddc6f3e8652b7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 12 Mar 2019 21:38:23 +0100 Subject: [PATCH 09/17] Improve toString for TypeState Now includes stack of ids of all nested typer states. --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index c14f5f456533..8ee858a47943 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -17,7 +17,7 @@ object TyperState { @sharable private var nextId: Int = 0 } -class TyperState(previous: TyperState /* | Null */) { +class TyperState(private val previous: TyperState /* | Null */) { Stats.record("typerState") @@ -178,7 +178,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}" } From 7dae38f6adf602f8e807fdc5e742db4ed1bc2bb7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Mar 2019 12:22:04 +0100 Subject: [PATCH 10/17] Fix constraint merges with conflicting type variables When merging two constraints we can end up in a situation where a type lambda is associated with different type variables in the two constraint. This happened when compiling concat.scala after introducing an additional tryEither for typechecking the function part of an application. That caused a constraint merge of two constraints over logically separate instances of `++`, which shared the same type lambda for `++` but associated it with different type variables. This situation can arise since for efficiency we do not always clone a type lambda before adding it to a constraint. The correct way to deal with the situation is to clone the type lambda with the conflicting TypeVars in one of the constraints before proceeding with the merge. --- .../dotty/tools/dotc/core/Constraint.scala | 20 +++++-- .../tools/dotc/core/OrderingConstraint.scala | 56 ++++++++++++++++++- .../src/dotty/tools/dotc/core/Types.scala | 9 ++- .../dotty/tools/dotc/typer/ProtoTypes.scala | 15 +---- tests/pos/concats.scala | 11 ++++ 5 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 tests/pos/concats.scala 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/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index d5219751e04a..8e33b31d9536 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 dufferent. + 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/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/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4e36002d9284..7e6e801c357f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -498,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/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 From 4eab6bf43ed8aef7f347965fdbc40a29d3272a94 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Mar 2019 13:23:40 +0100 Subject: [PATCH 11/17] Add fallback `E -> new E` if typing an applied function fails --- .../dotty/tools/dotc/core/TyperState.scala | 4 ++ .../dotty/tools/dotc/typer/Applications.scala | 29 ++++++++++++-- .../src/dotty/tools/dotc/typer/ReTyper.scala | 5 +++ .../src/dotty/tools/dotc/typer/Typer.scala | 39 ++++++++++--------- tests/neg/i1648.scala | 2 +- tests/neg/i1907.scala | 2 +- tests/run/creator-applys.scala | 4 +- 7 files changed, 59 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 8ee858a47943..7d03ca314cb2 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 @@ -143,6 +145,8 @@ class TyperState(private val 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) if (targetState.constraint eq previousConstraint) targetState.constraint = constraint else targetState.mergeConstraintWith(this) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 95b3c83aa84f..1028e35c345c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -214,7 +214,7 @@ object Applications { */ 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. * @@ -803,6 +803,29 @@ 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 + } + fn match { + case Ident(name) => + tryNewWithType(cpy.Ident(fn)(name.toTypeName), pt, fallBack) + case Select(qual, name) => + tryNewWithType(cpy.Select(fn)(qual, name.toTypeName), pt, fallBack) + // TODO: try to keep as much as possible from typed `qual` in order to avoid + // combinatorial explosion + case _ => + fallBack + } + } + /** Typecheck application. Result could be an `Apply` node, * or, if application is an operator assignment, also an `Assign` or * Block node. @@ -812,7 +835,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, @@ -947,7 +970,7 @@ 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 { + handleMeta(typedFunPart(tree.fun, PolyProto(typedArgs, pt)) match { case IntegratedTypeArgs(app) => app case _: TypeApply if !ctx.isAfterTyper => diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 94305da890ce..2798d35f24f8 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -64,6 +64,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 +96,8 @@ class ReTyper extends Typer with ReChecking { override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType, locked: TypeVars)(fallBack: => Tree)(implicit ctx: Context): Tree = fallBack + override def tryNewWithType(tpt: untpd.Tree, 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/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a66ce634bf45..3bea65f7a832 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2232,6 +2232,24 @@ class Typer extends Namer case _ => false } + /** Try to rename `tpt` to a type `T` and typecheck `new T` with given expected type `pt`. + */ + def tryNewWithType(tpt: untpd.Tree, pt: Type, fallBack: => Tree)(implicit ctx: Context): Tree = + tryEither { implicit ctx => + val tycon = typed(tpt) + if (ctx.reporter.hasErrors) + EmptyTree // signal that we should return the error in fallBack + else + typed(untpd.Select(untpd.New(untpd.TypedSplice(tycon)), nme.CONSTRUCTOR), pt) + } { (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 + } + } + /** Potentially add apply node or implicit conversions. Before trying either, * if the function is applied to an empty parameter list (), we try * @@ -2273,28 +2291,11 @@ class Typer extends Namer } def tryNew(fallBack: => Tree): Tree = { - - def tryNewWithType(tpt: untpd.Tree): Tree = - tryEither { implicit ctx => - val tycon = typed(tpt.withSpan(tree.span)) - if (ctx.reporter.hasErrors) - EmptyTree // signal that we should return the error in fallBack - else - typed(untpd.Select(untpd.New(untpd.TypedSplice(tycon)), nme.CONSTRUCTOR), pt) - } { (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) => - tryNewWithType(untpd.Ident(name.toTypeName)) + tryNewWithType(cpy.Ident(tree)(name.toTypeName), pt, fallBack) case Select(qual, name) => - tryNewWithType(untpd.Select(untpd.TypedSplice(qual), name.toTypeName)) + tryNewWithType(cpy.Select(tree)(untpd.TypedSplice(qual), name.toTypeName), pt, fallBack) case _ => fallBack } 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/neg/i1907.scala b/tests/neg/i1907.scala index 6bc3bb56f7c4..504e74233b3f 100644 --- a/tests/neg/i1907.scala +++ b/tests/neg/i1907.scala @@ -3,5 +3,5 @@ import java.io.File object Test { Some(new File(".")) .map(_.listFiles).getOrElse(Array.empty) // error: undetermined ClassTag - .map(_.listFiles) + .map(_.listFiles) // error: missing parameter type } diff --git a/tests/run/creator-applys.scala b/tests/run/creator-applys.scala index efdf8f90bb6b..8342b8167d2f 100644 --- a/tests/run/creator-applys.scala +++ b/tests/run/creator-applys.scala @@ -37,7 +37,7 @@ object Test extends App { assert((x8: C[String, Int]).run == "C a 1") */ } -/* + object Test2 { class A { def run = "A" @@ -66,4 +66,4 @@ object Test2 { val x6 = C("a", 1) assert((x6: C[String, Int]).run == "C a 1") -}*/ \ No newline at end of file +} \ No newline at end of file From c453c30da53e7d20fa7a3dc1545dabc03b1143d7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Mar 2019 19:30:00 +0100 Subject: [PATCH 12/17] Allow `new` conversion only for paths Also, some refactorings to shorten and simplify --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 7 ++ .../dotty/tools/dotc/typer/Applications.scala | 12 +-- .../src/dotty/tools/dotc/typer/ReTyper.scala | 6 +- .../src/dotty/tools/dotc/typer/Typer.scala | 87 ++++++++++--------- 4 files changed, 60 insertions(+), 52 deletions(-) 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/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1028e35c345c..298c96c99a03 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -814,16 +814,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => tstate.commit() result } - fn match { - case Ident(name) => - tryNewWithType(cpy.Ident(fn)(name.toTypeName), pt, fallBack) - case Select(qual, name) => - tryNewWithType(cpy.Select(fn)(qual, name.toTypeName), pt, fallBack) - // TODO: try to keep as much as possible from typed `qual` in order to avoid - // combinatorial explosion - case _ => - fallBack - } + if (untpd.isPath(fn)) tryNew(untpd)(fn, pt, fallBack) + else fallBack } /** Typecheck application. Result could be an `Apply` node, diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 2798d35f24f8..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 @@ -96,7 +97,8 @@ class ReTyper extends Typer with ReChecking { override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType, locked: TypeVars)(fallBack: => Tree)(implicit ctx: Context): Tree = fallBack - override def tryNewWithType(tpt: untpd.Tree, pt: Type, 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 = () diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3bea65f7a832..2b1e7226986d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @ IntegratedTypeArgs(app) => - pt.revealIgnored match { - case _: PolyProto => qual1 // keep the IntegratedTypeArgs 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) @@ -2234,21 +2234,39 @@ class Typer extends Namer /** Try to rename `tpt` to a type `T` and typecheck `new T` with given expected type `pt`. */ - def tryNewWithType(tpt: untpd.Tree, pt: Type, fallBack: => Tree)(implicit ctx: Context): Tree = - tryEither { implicit ctx => - val tycon = typed(tpt) - if (ctx.reporter.hasErrors) - EmptyTree // signal that we should return the error in fallBack - else - typed(untpd.Select(untpd.New(untpd.TypedSplice(tycon)), nme.CONSTRUCTOR), pt) - } { (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 + + 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 + typed(untpd.Select(untpd.New(untpd.TypedSplice(tycon)), nme.CONSTRUCTOR), pt) + } { (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 @@ -2290,20 +2308,9 @@ class Typer extends Namer else try adapt(simplify(sel, pt1, locked), pt1, locked) finally sel.removeAttachment(InsertedApply) } - def tryNew(fallBack: => Tree): Tree = { - tree match { - case Ident(name) => - tryNewWithType(cpy.Ident(tree)(name.toTypeName), pt, fallBack) - case Select(qual, name) => - tryNewWithType(cpy.Select(tree)(untpd.TypedSplice(qual), name.toTypeName), pt, fallBack) - case _ => - fallBack - } - } - def tryImplicit(fallBack: => Tree) = tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked) - .getOrElse(tryNew(fallBack)) + .getOrElse(tryNew(tpd)(tree, pt, fallBack)) if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver)) // Suppress insertion of apply or implicit conversion on extension method receiver From 8ec0d1d6e0bcce12caa8515c0b50a5674e7a1d1c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 16 Mar 2019 19:46:07 +0100 Subject: [PATCH 13/17] Fix new insertion for Selects --- tests/run/creator-applys.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/run/creator-applys.scala b/tests/run/creator-applys.scala index 8342b8167d2f..54a957bb36fb 100644 --- a/tests/run/creator-applys.scala +++ b/tests/run/creator-applys.scala @@ -12,7 +12,7 @@ object Test extends App { } object C - val x1 = Test.A() + val x1 = A() assert(x1.run == "A") val x2 = B[String]() @@ -36,6 +36,7 @@ object Test extends App { val x8 = C[T = Int]("a", 1) assert((x8: C[String, Int]).run == "C a 1") */ + Test2 } object Test2 { @@ -52,18 +53,18 @@ object Test2 { val x1 = Test.A() assert(x1.run == "A") - val x2 = B[String]() + val x2 = Test.B[String]() assert(x2.run == "B") - val x3: B[String] = B() + val x3: B[String] = Test.B() assert(x3.run == "B") - val x4: C[String, Int] = C("a", 1) + val x4: C[String, Int] = Test.C("a", 1) assert(x4.run == "C a 1") - val x5 = C[String, Int]("a", 1) + val x5 = Test.C[String, Int]("a", 1) assert(x5.run == "C a 1") - val x6 = C("a", 1) + val x6 = Test.C("a", 1) assert((x6: C[String, Int]).run == "C a 1") } \ No newline at end of file From 69997bd68d11a93021a1369d0ce48671230290e0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Mar 2019 11:19:23 +0100 Subject: [PATCH 14/17] Fix handling of creator applications with type arguments --- .../tools/dotc/printing/RefinedPrinter.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 14 +- tests/neg/creator-applys.scala | 35 +++++ tests/run/creator-applys.scala | 120 ++++++++++++++++-- 4 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 tests/neg/creator-applys.scala diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d43ab305932b..6b9b4c18f06c 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) ~ "(with integrated type args)" case Thicket(trees) => "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2b1e7226986d..ea3a43ad7def 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2243,8 +2243,16 @@ class Typer extends Namer val tycon = typed(tpt) if (ctx.reporter.hasErrors) EmptyTree // signal that we should return the error in fallBack - else - typed(untpd.Select(untpd.New(untpd.TypedSplice(tycon)), nme.CONSTRUCTOR), pt) + else { + def recur(tpt: Tree, pt: Type): Tree = pt.revealIgnored match { + case PolyProto(targs, pt1) if !targs.exists(_.isInstanceOf[NamedArg]) => + 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 { @@ -2969,7 +2977,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/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/run/creator-applys.scala b/tests/run/creator-applys.scala index 54a957bb36fb..7904aff6ff0a 100644 --- a/tests/run/creator-applys.scala +++ b/tests/run/creator-applys.scala @@ -18,7 +18,7 @@ object Test extends App { val x2 = B[String]() assert(x2.run == "B") - val x3: B[String] = B() + val x3: B[String] = Test.B() assert(x3.run == "B") val x4: C[String, Int] = C("a", 1) @@ -27,19 +27,18 @@ object Test extends App { 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") -/* - val x7 = C[S = String]("a", 1) - assert((x7: C[String, Int]).run == "C a 1") - - val x8 = C[T = Int]("a", 1) - assert((x8: C[String, Int]).run == "C a 1") -*/ - Test2 + Test1 } -object Test2 { +object Test1 { class A { def run = "A" } @@ -50,21 +49,116 @@ object Test2 { 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: B[String] = Test.B() + val x3: Test.B[String] = Test.B() assert(x3.run == "B") - val x4: C[String, Int] = Test.C("a", 1) + 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: C[String, Int]).run == "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 From 928b0144d17590fc9388f13fe75a3e881c1ac99b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 Mar 2019 22:59:12 +0100 Subject: [PATCH 15/17] Add docs --- .../docs/reference/features-classification.md | 5 ++- .../creator-applications.md | 43 +++++++++++++++++++ docs/sidebar.yml | 2 + 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 docs/docs/reference/other-new-features/creator-applications.md 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 From 7f8f382a044fe4638c6264bbf30113e0810be311 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Mar 2019 19:18:13 +0100 Subject: [PATCH 16/17] Address review coments --- .../src/dotty/tools/dotc/core/OrderingConstraint.scala | 2 +- compiler/src/dotty/tools/dotc/core/TyperState.scala | 2 +- .../src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 ++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 7 ++++++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 8e33b31d9536..2f568dfe7750 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -545,7 +545,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds, 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 dufferent. + // 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 { diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 7d03ca314cb2..5274a2beef9d 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -145,7 +145,7 @@ class TyperState(private val previous: TyperState /* | Null */) { def commit()(implicit ctx: Context): Unit = { Stats.record("typerState.commit") val targetState = ctx.typerState - if (constraint ne targetState.constraint) + if (constraint ne targetState.constraint) constr.println(i"committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}") assert(isCommittable) if (targetState.constraint eq previousConstraint) targetState.constraint = constraint diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6b9b4c18f06c..133c4637b1b6 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -579,7 +579,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case Splice(tree) => keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") case tree: Applications.IntegratedTypeArgs => - toText(tree.app) ~ "(with integrated type args)" + 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/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 298c96c99a03..f7a6fa931185 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -188,6 +188,8 @@ object Applications { /** 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 IntegratedTypeArgs(val app: Tree)(implicit @constructorOnly src: SourceFile) extends tpd.Tree { override def span = app.span diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ea3a43ad7def..e2667a9d1ccd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2233,8 +2233,11 @@ class Typer extends Namer } /** 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 = { @@ -2246,6 +2249,8 @@ class Typer extends Namer 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) From 52f2e63dd8ac10dbe36289bedb74d1530e725019 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Mar 2019 21:13:21 +0100 Subject: [PATCH 17/17] Fix test --- tests/neg/i1907.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i1907.scala b/tests/neg/i1907.scala index 504e74233b3f..6bc3bb56f7c4 100644 --- a/tests/neg/i1907.scala +++ b/tests/neg/i1907.scala @@ -3,5 +3,5 @@ import java.io.File object Test { Some(new File(".")) .map(_.listFiles).getOrElse(Array.empty) // error: undetermined ClassTag - .map(_.listFiles) // error: missing parameter type + .map(_.listFiles) }