From eed52216c634a6d73f737358ed6d6c5855452603 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Sun, 26 Feb 2017 16:01:37 -0800 Subject: Allow user-defined `[un]apply` in case companion Don't emit a synthetic `apply` (or `unapply`) when it would clash with an existing one. This allows e.g., a `private apply`, along with a `case class` with a `private` constructor. We have to retract the synthetic method in a pretty roundabout way, as we need the other methods and the owner to be completed already. Unless we have to complete the synthetic `apply` while completing the user-defined one, this should not be a problem. If this does happen, this implies there's a cycle in computing the user-defined signature and the synthetic one, which is not allowed. --- .../scala/tools/nsc/typechecker/Namers.scala | 99 ++++++++++++++++------ test/files/neg/userdefined_apply.check | 13 +++ test/files/neg/userdefined_apply.scala | 31 +++++++ test/files/pos/userdefined_apply.scala | 36 ++++++++ 4 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 test/files/neg/userdefined_apply.check create mode 100644 test/files/neg/userdefined_apply.scala create mode 100644 test/files/pos/userdefined_apply.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 28169c9da1..8c5f4590b9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -613,7 +613,15 @@ trait Namers extends MethodSynthesis { noDuplicates(selectors map (_.rename), AppearsTwice) } - def enterCopyMethod(copyDef: DefDef): Symbol = { + class CompleterWrapper(completer: TypeCompleter) extends TypeCompleter { + val tree = completer.tree + + override def complete(sym: Symbol): Unit = { + completer.complete(sym) + } + } + + def copyMethodCompleter(copyDef: DefDef): TypeCompleter = { val sym = copyDef.symbol val lazyType = completerOf(copyDef) @@ -632,14 +640,63 @@ trait Namers extends MethodSynthesis { ) } - sym setInfo { - mkTypeCompleter(copyDef) { sym => - assignParamTypes() - lazyType complete sym - } + mkTypeCompleter(copyDef) { sym => + assignParamTypes() + lazyType complete sym } } + // for apply/unapply, which may need to disappear when they clash with a user-defined method of matching signature + def applyUnapplyMethodCompleter(un_applyDef: DefDef, companionContext: Context): TypeCompleter = + new CompleterWrapper(completerOf(un_applyDef)) { + override def complete(sym: Symbol): Unit = { + super.complete(sym) + + // don't propagate e.g. @volatile annot to apply's argument + def retainOnlyParamAnnots(param: Symbol) = + param setAnnotations (param.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = false)) + + sym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots)) + + // If there's a same-named locked symbol, we're currently completing its signature. + // This means it (may) refer to us, and is thus either overloaded or recursive without a signature. + // rule out locked symbols from the owner.info.member call + val scopePartiallyCompleted = + companionContext.scope.lookupAll(sym.name).exists(existing => existing != sym && existing.hasFlag(LOCKED)) + + val suppress = + scopePartiallyCompleted || { + val userDefined = companionContext.owner.info.member(sym.name).filter(_ != sym) + (userDefined != NoSymbol) && { + userDefined.info match { + // TODO: do we have something for this already? the synthetic symbol can't be overloaded, right? + case OverloadedType(pre, alternatives) => + // pre probably relevant because of inherited overloads? + alternatives.exists(_.isErroneous) || alternatives.exists(alt => pre.memberInfo(alt) matches pre.memberInfo(sym)) + case tp => + (tp eq ErrorType) || tp.matches(sym.info) + } + } + } + + if (suppress) { + sym setInfo ErrorType + sym setFlag IS_ERROR + + // Don't unlink in an error situation to generate less confusing error messages. + // Ideally, our error reporting would distinguish overloaded from recursive user-defined apply methods without signature, + // but this would require some form of partial-completion of method signatures, so that we can + // know what the argument types were, even though we can't complete the result type, because + // we hit a cycle while trying to compute it (when we get here with locked user-defined symbols, we + // are in the complete for that symbol, and thus the locked symbol has not yet received enough info; + // I hesitate to provide more info, because it would involve a WildCard or something for its result type, + // which could upset other code paths) + if (!scopePartiallyCompleted) + companionContext.scope.unlink(sym) + } + } + } + def completerOf(tree: MemberDef): TypeCompleter = { val mono = namerOf(tree.symbol) monoTypeCompleter tree val tparams = treeInfo.typeParameters(tree) @@ -687,13 +744,17 @@ trait Namers extends MethodSynthesis { val bridgeFlag = if (mods hasAnnotationNamed tpnme.bridgeAnnot) BRIDGE | ARTIFACT else 0 val sym = assignAndEnterSymbol(tree) setFlag bridgeFlag - if (name == nme.copy && sym.isSynthetic) - enterCopyMethod(tree) - else if (name == nme.apply && sym.hasAllFlags(SYNTHETIC | CASE)) - sym setInfo caseApplyMethodCompleter(tree, completerOf(tree).asInstanceOf[LockingTypeCompleter]) - else - sym setInfo completerOf(tree) - } + // copy/apply/unapply synthetics are added using the addIfMissing mechanism, + // which ensures the owner has its preliminary info (we may add another decl here) + val completer = + if (sym hasFlag SYNTHETIC) { + if (name == nme.copy) copyMethodCompleter(tree) + else if (sym hasFlag CASE) applyUnapplyMethodCompleter(tree, context) + else completerOf(tree) + } else completerOf(tree) + + sym setInfo completer + } def enterClassDef(tree: ClassDef) { val ClassDef(mods, _, _, impl) = tree @@ -818,16 +879,6 @@ trait Namers extends MethodSynthesis { classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } - def caseApplyMethodCompleter(tree: DefDef, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => - sigCompleter.completeImpl(methSym) - - // don't propagate e.g. @volatile annot to apply's argument - def retainOnlyParamAnnots(param: Symbol) = - param setAnnotations (param.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = false)) - - methSym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots)) - } - // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, // as specified by Field.noFieldFor) def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { fieldOrGetterSym => @@ -1464,7 +1515,7 @@ trait Namers extends MethodSynthesis { val defTpt = // don't mess with tpt's of case copy default getters, because assigning something other than TypeTree() - // will break the carefully orchestrated naming/typing logic that involves enterCopyMethod and caseClassCopyMeth + // will break the carefully orchestrated naming/typing logic that involves copyMethodCompleter and caseClassCopyMeth if (meth.isCaseCopy) TypeTree() else { // If the parameter type mentions any type parameter of the method, let the compiler infer the diff --git a/test/files/neg/userdefined_apply.check b/test/files/neg/userdefined_apply.check new file mode 100644 index 0000000000..ca0154885d --- /dev/null +++ b/test/files/neg/userdefined_apply.check @@ -0,0 +1,13 @@ +userdefined_apply.scala:3: error: overloaded method apply needs result type + private def apply(x: Int) = if (x > 0) new ClashOverloadNoSig(x) else apply("") + ^ +userdefined_apply.scala:12: error: overloaded method apply needs result type + private def apply(x: Int) = if (x > 0) ClashRecNoSig(1) else ??? + ^ +userdefined_apply.scala:19: error: overloaded method apply needs result type + private def apply(x: Boolean) = if (x) NoClashNoSig(1) else ??? + ^ +userdefined_apply.scala:26: error: overloaded method apply needs result type + private def apply(x: Boolean) = if (x) NoClashOverload(1) else apply("") + ^ +four errors found diff --git a/test/files/neg/userdefined_apply.scala b/test/files/neg/userdefined_apply.scala new file mode 100644 index 0000000000..1f2aff6e82 --- /dev/null +++ b/test/files/neg/userdefined_apply.scala @@ -0,0 +1,31 @@ +object ClashOverloadNoSig { + // error: overloaded method apply needs result type + private def apply(x: Int) = if (x > 0) new ClashOverloadNoSig(x) else apply("") + + def apply(x: String): ClashOverloadNoSig = ??? +} + +case class ClashOverloadNoSig private(x: Int) + +object ClashRecNoSig { + // error: recursive method apply needs result type + private def apply(x: Int) = if (x > 0) ClashRecNoSig(1) else ??? +} + +case class ClashRecNoSig private(x: Int) + +object NoClashNoSig { + // error: overloaded method apply needs result type + private def apply(x: Boolean) = if (x) NoClashNoSig(1) else ??? +} + +case class NoClashNoSig private(x: Int) + +object NoClashOverload { + // error: overloaded method apply needs result type + private def apply(x: Boolean) = if (x) NoClashOverload(1) else apply("") + + def apply(x: String): NoClashOverload = ??? +} + +case class NoClashOverload private(x: Int) diff --git a/test/files/pos/userdefined_apply.scala b/test/files/pos/userdefined_apply.scala new file mode 100644 index 0000000000..ca563f1dc5 --- /dev/null +++ b/test/files/pos/userdefined_apply.scala @@ -0,0 +1,36 @@ +// NOTE: the companion inherits a public apply method from Function1! +case class NeedsCompanion private (x: Int) + +object ClashNoSig { // ok + private def apply(x: Int) = if (x > 0) new ClashNoSig(x) else ??? +} +case class ClashNoSig private (x: Int) + + +object Clash { + private def apply(x: Int) = if (x > 0) new Clash(x) else ??? +} +case class Clash private (x: Int) + +object ClashSig { + private def apply(x: Int): ClashSig = if (x > 0) new ClashSig(x) else ??? +} +case class ClashSig private (x: Int) + +object ClashOverload { + private def apply(x: Int): ClashOverload = if (x > 0) new ClashOverload(x) else apply("") + def apply(x: String): ClashOverload = ??? +} +case class ClashOverload private (x: Int) + +object NoClashSig { + private def apply(x: Boolean): NoClashSig = if (x) NoClashSig(1) else ??? +} +case class NoClashSig private (x: Int) + +object NoClashOverload { + // needs full sig + private def apply(x: Boolean): NoClashOverload = if (x) NoClashOverload(1) else apply("") + def apply(x: String): NoClashOverload = ??? +} +case class NoClashOverload private (x: Int) -- cgit v1.2.3 From 276434b4af2c2d244d1b5e596867041b36e7b920 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 28 Feb 2017 10:24:45 -0800 Subject: Clarify spec of interaction of existing vs synthetic apply/unapply When matching user-defined apply/unapply members exist in a case class's companion object, don't add clashing synthetic ones. --- spec/05-classes-and-objects.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/spec/05-classes-and-objects.md b/spec/05-classes-and-objects.md index 5bd520589d..246e579c94 100644 --- a/spec/05-classes-and-objects.md +++ b/spec/05-classes-and-objects.md @@ -854,9 +854,8 @@ a `val` or `var` modifier. Hence, an accessor definition for the parameter is [generated](#class-definitions). A case class definition of `$c$[$\mathit{tps}\,$]($\mathit{ps}_1\,$)$\ldots$($\mathit{ps}_n$)` with type -parameters $\mathit{tps}$ and value parameters $\mathit{ps}$ implicitly -generates an [extractor object](08-pattern-matching.html#extractor-patterns) which is -defined as follows: +parameters $\mathit{tps}$ and value parameters $\mathit{ps}$ implies +the definition of a companion object, which serves as an [extractor object](08-pattern-matching.html#extractor-patterns). It has the following shape: ```scala object $c$ { @@ -873,11 +872,14 @@ each $\mathit{xs}\_i$ denotes the parameter names of the parameter section $\mathit{ps}\_i$, and $\mathit{xs}\_{11}, \ldots , \mathit{xs}\_{1k}$ denote the names of all parameters in the first parameter section $\mathit{xs}\_1$. -If a type parameter section is missing in the -class, it is also missing in the `apply` and -`unapply` methods. -The definition of `apply` is omitted if class $c$ is -`abstract`. +If a type parameter section is missing in the class, it is also missing in the `apply` and `unapply` methods. + +If the companion object $c$ is already defined, +the `apply` and `unapply` methods are added to the existing object. +The definition of `apply` is omitted if class $c$ is `abstract`. +If the object $c$ already defines a [matching](#definition-matching) member of the +same name as the synthetic member to be added, the synthetic member +is not added (overloading or mutual recursion is allowed, however). If the case class definition contains an empty value parameter list, the `unapply` method returns a `Boolean` instead of an `Option` type and @@ -890,9 +892,6 @@ def unapply[$\mathit{tps}\,$]($x$: $c$[$\mathit{tps}\,$]) = x ne null The name of the `unapply` method is changed to `unapplySeq` if the first parameter section $\mathit{ps}_1$ of $c$ ends in a [repeated parameter](04-basic-declarations-and-definitions.html#repeated-parameters). -If a companion object $c$ exists already, no new object is created, -but the `apply` and `unapply` methods are added to the existing -object instead. A method named `copy` is implicitly added to every case class unless the class already has a member (directly defined or inherited) with that name, or the -- cgit v1.2.3 From 31a56077af5c5b35049fec456204e12a19bb6701 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 28 Feb 2017 14:14:11 -0800 Subject: Improvements based on reviews by Lukas & Jason --- spec/05-classes-and-objects.md | 5 +-- .../scala/tools/nsc/typechecker/Namers.scala | 48 ++++++++++++++-------- .../scala/reflect/internal/tpe/FindMembers.scala | 14 +++++++ test/files/neg/userdefined_apply.check | 20 +++++++-- test/files/neg/userdefined_apply.scala | 26 ++++++++++++ test/files/pos/userdefined_apply.scala | 18 ++++++++ 6 files changed, 107 insertions(+), 24 deletions(-) diff --git a/spec/05-classes-and-objects.md b/spec/05-classes-and-objects.md index 246e579c94..ffb65979f7 100644 --- a/spec/05-classes-and-objects.md +++ b/spec/05-classes-and-objects.md @@ -876,10 +876,9 @@ If a type parameter section is missing in the class, it is also missing in the ` If the companion object $c$ is already defined, the `apply` and `unapply` methods are added to the existing object. +If the object $c$ already has a [matching](#definition-matching) +`apply` (or `unapply`) member, no new definition is added. The definition of `apply` is omitted if class $c$ is `abstract`. -If the object $c$ already defines a [matching](#definition-matching) member of the -same name as the synthetic member to be added, the synthetic member -is not added (overloading or mutual recursion is allowed, however). If the case class definition contains an empty value parameter list, the `unapply` method returns a `Boolean` instead of an `Option` type and diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 8c5f4590b9..51df750951 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -650,6 +650,8 @@ trait Namers extends MethodSynthesis { def applyUnapplyMethodCompleter(un_applyDef: DefDef, companionContext: Context): TypeCompleter = new CompleterWrapper(completerOf(un_applyDef)) { override def complete(sym: Symbol): Unit = { + assert(sym hasAllFlags CASE | SYNTHETIC, sym.defString) + super.complete(sym) // don't propagate e.g. @volatile annot to apply's argument @@ -658,23 +660,37 @@ trait Namers extends MethodSynthesis { sym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots)) + // owner won't be locked + val ownerInfo = companionContext.owner.info + // If there's a same-named locked symbol, we're currently completing its signature. - // This means it (may) refer to us, and is thus either overloaded or recursive without a signature. - // rule out locked symbols from the owner.info.member call - val scopePartiallyCompleted = - companionContext.scope.lookupAll(sym.name).exists(existing => existing != sym && existing.hasFlag(LOCKED)) - - val suppress = - scopePartiallyCompleted || { - val userDefined = companionContext.owner.info.member(sym.name).filter(_ != sym) + // If `scopePartiallyCompleted`, the program is known to have a type error, since + // this means a user-defined method is missing a result type while its rhs refers to `sym` or an overload. + // This is an error because overloaded/recursive methods must have a result type. + // The method would be overloaded if its signature, once completed, would not match the synthetic method's, + // or recursive if it turned out we should unlink our synthetic method (matching sig). + // In any case, error out. We don't unlink the symbol so that `symWasOverloaded` says yes, + // which would be wrong if the method is in fact recursive, but it seems less confusing. + val scopePartiallyCompleted = new HasMember(ownerInfo, sym.name, BridgeFlags | SYNTHETIC, LOCKED).apply() + + // Check `scopePartiallyCompleted` first to rule out locked symbols from the owner.info.member call, + // as FindMember will call info on a locked symbol (while checking type matching to assemble an overloaded type), + // and throw a TypeError, so that we are aborted. + // Do not consider deferred symbols, as suppressing our concrete implementation would be an error regardless + // of whether the signature matches (if it matches, we omitted a valid implementation, if it doesn't, + // we would get an error for the missing implementation it isn't implemented by some overload other than our synthetic one) + val suppress = scopePartiallyCompleted || { + // can't exclude deferred members using DEFERRED flag here (TODO: why?) + val userDefined = ownerInfo.memberBasedOnName(sym.name, BridgeFlags | SYNTHETIC) + (userDefined != NoSymbol) && { - userDefined.info match { - // TODO: do we have something for this already? the synthetic symbol can't be overloaded, right? - case OverloadedType(pre, alternatives) => - // pre probably relevant because of inherited overloads? - alternatives.exists(_.isErroneous) || alternatives.exists(alt => pre.memberInfo(alt) matches pre.memberInfo(sym)) - case tp => - (tp eq ErrorType) || tp.matches(sym.info) + assert(userDefined != sym) + val alts = userDefined.alternatives // could be just the one, if this member isn't overloaded + // don't compute any further `memberInfo`s if there's an error somewhere + alts.exists(_.isErroneous) || { + val self = companionContext.owner.thisType + val memberInfo = self.memberInfo(sym) + alts.exists(alt => !alt.isDeferred && (self.memberInfo(alt) matches memberInfo)) } } } @@ -744,8 +760,6 @@ trait Namers extends MethodSynthesis { val bridgeFlag = if (mods hasAnnotationNamed tpnme.bridgeAnnot) BRIDGE | ARTIFACT else 0 val sym = assignAndEnterSymbol(tree) setFlag bridgeFlag - // copy/apply/unapply synthetics are added using the addIfMissing mechanism, - // which ensures the owner has its preliminary info (we may add another decl here) val completer = if (sym hasFlag SYNTHETIC) { if (name == nme.copy) copyMethodCompleter(tree) diff --git a/src/reflect/scala/reflect/internal/tpe/FindMembers.scala b/src/reflect/scala/reflect/internal/tpe/FindMembers.scala index 6ba48cb44d..510d76793e 100644 --- a/src/reflect/scala/reflect/internal/tpe/FindMembers.scala +++ b/src/reflect/scala/reflect/internal/tpe/FindMembers.scala @@ -285,4 +285,18 @@ trait FindMembers { initBaseClasses.head.newOverloaded(tpe, members) } } + + private[scala] final class HasMember(tpe: Type, name: Name, excludedFlags: Long, requiredFlags: Long) extends FindMemberBase[Boolean](tpe, name, excludedFlags, requiredFlags) { + private[this] var _result = false + override protected def result: Boolean = _result + + protected def shortCircuit(sym: Symbol): Boolean = { + _result = true + true // prevents call to addMemberIfNew + } + + // Not used + protected def addMemberIfNew(sym: Symbol): Unit = {} + } + } diff --git a/test/files/neg/userdefined_apply.check b/test/files/neg/userdefined_apply.check index ca0154885d..c8c8976f5f 100644 --- a/test/files/neg/userdefined_apply.check +++ b/test/files/neg/userdefined_apply.check @@ -1,13 +1,25 @@ userdefined_apply.scala:3: error: overloaded method apply needs result type private def apply(x: Int) = if (x > 0) new ClashOverloadNoSig(x) else apply("") ^ -userdefined_apply.scala:12: error: overloaded method apply needs result type +userdefined_apply.scala:14: error: overloaded method apply needs result type private def apply(x: Int) = if (x > 0) ClashRecNoSig(1) else ??? ^ -userdefined_apply.scala:19: error: overloaded method apply needs result type +userdefined_apply.scala:21: error: overloaded method apply needs result type private def apply(x: Boolean) = if (x) NoClashNoSig(1) else ??? ^ -userdefined_apply.scala:26: error: overloaded method apply needs result type +userdefined_apply.scala:28: error: overloaded method apply needs result type private def apply(x: Boolean) = if (x) NoClashOverload(1) else apply("") ^ -four errors found +userdefined_apply.scala:45: error: recursive method apply needs result type +case class NoClashNoSigPoly private(x: Int) + ^ +userdefined_apply.scala:39: error: NoClashNoSigPoly.type does not take parameters + def apply(x: T) = if (???) NoClashNoSigPoly(1) else ??? + ^ +userdefined_apply.scala:57: error: recursive method apply needs result type +case class ClashNoSigPoly private(x: Int) + ^ +userdefined_apply.scala:51: error: ClashNoSigPoly.type does not take parameters + def apply(x: T) = if (???) ClashNoSigPoly(1) else ??? + ^ +8 errors found diff --git a/test/files/neg/userdefined_apply.scala b/test/files/neg/userdefined_apply.scala index 1f2aff6e82..0a0d960b39 100644 --- a/test/files/neg/userdefined_apply.scala +++ b/test/files/neg/userdefined_apply.scala @@ -8,6 +8,8 @@ object ClashOverloadNoSig { case class ClashOverloadNoSig private(x: Int) object ClashRecNoSig { + // TODO: status quo is that the error refers to an overloaded method, which is actually recursive + // (we should have unlinked the symbol in the `if(suppress)` part of `applyUnapplyMethodCompleter`) // error: recursive method apply needs result type private def apply(x: Int) = if (x > 0) ClashRecNoSig(1) else ??? } @@ -29,3 +31,27 @@ object NoClashOverload { } case class NoClashOverload private(x: Int) + + +class BaseNCNSP[T] { + // TODO: suppress the following error + // error: NoClashNoSigPoly.type does not take parameters + def apply(x: T) = if (???) NoClashNoSigPoly(1) else ??? +} + +object NoClashNoSigPoly extends BaseNCNSP[Boolean] +// TODO: position error at definition of apply in superclass instead of on case clss +// error: recursive method apply needs result type +case class NoClashNoSigPoly private(x: Int) + + +class BaseCNSP[T] { + // TODO: suppress the following error + // error: ClashNoSigPoly.type does not take parameters + def apply(x: T) = if (???) ClashNoSigPoly(1) else ??? +} + +object ClashNoSigPoly extends BaseCNSP[Int] +// TODO: position error at definition of apply in superclass instead of on case clss +// error: recursive method apply needs result type +case class ClashNoSigPoly private(x: Int) diff --git a/test/files/pos/userdefined_apply.scala b/test/files/pos/userdefined_apply.scala index ca563f1dc5..e29f9f5141 100644 --- a/test/files/pos/userdefined_apply.scala +++ b/test/files/pos/userdefined_apply.scala @@ -34,3 +34,21 @@ object NoClashOverload { def apply(x: String): NoClashOverload = ??? } case class NoClashOverload private (x: Int) + + + +class BaseNCP[T] { + // error: overloaded method apply needs result type + def apply(x: T): NoClashPoly = if (???) NoClashPoly(1) else ??? +} + +object NoClashPoly extends BaseNCP[Boolean] +case class NoClashPoly private(x: Int) + + +class BaseCP[T] { + // error: overloaded method apply needs result type + def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ??? +} +object ClashPoly extends BaseCP[Int] +case class ClashPoly private(x: Int) -- cgit v1.2.3 From 379e113e568c3d3193aace81fc37d7279eff4f8c Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Apr 2017 17:30:00 -0700 Subject: `CompleterWrapper` delegates `typeParams`. Fixes the problem reported with #5730 by xuwei-k in scala/scala-dev#352. The problem was already present before the introduction of `applyUnapplyMethodCompleter`, as 63f7b35 (in #5294) introduced a similar bug where the `PolyTypeCompleter`'s `typeParams` override was masked. --- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 3 +++ test/files/pos/userdefined_apply_poly_overload.scala | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 test/files/pos/userdefined_apply_poly_overload.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 51df750951..1e4a59615f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -614,6 +614,9 @@ trait Namers extends MethodSynthesis { } class CompleterWrapper(completer: TypeCompleter) extends TypeCompleter { + // override important when completer.isInstanceOf[PolyTypeCompleter]! + override val typeParams = completer.typeParams + val tree = completer.tree override def complete(sym: Symbol): Unit = { diff --git a/test/files/pos/userdefined_apply_poly_overload.scala b/test/files/pos/userdefined_apply_poly_overload.scala new file mode 100644 index 0000000000..6760c1424f --- /dev/null +++ b/test/files/pos/userdefined_apply_poly_overload.scala @@ -0,0 +1,13 @@ +object Foo { + // spurious error if: + // - this definition precedes that of apply (which is overloaded with the synthetic one derived from the case class) + // - AND `Foo.apply` is explicitly applied to `[A]` (no error if `[A]` is inferred) + // + def referToPolyOverloadedApply[A]: Foo[A] = Foo.apply[A]("bla") + // ^ + // found : String("bla") + // required: Int + + def apply[A](x: Int): Foo[A] = ??? +} +case class Foo[A](x: String) // must be polymorphic -- cgit v1.2.3 From d8613df1cd6545f85767bf649a483621676b5893 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 22 Feb 2017 11:02:56 -0800 Subject: Create scope only once --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2cbd9475fc..1f2b8ae16e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3099,8 +3099,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper || (looker.hasAccessorFlag && !accessed.hasAccessorFlag && accessed.isPrivate) ) - def checkNoDoubleDefs: Unit = { - val scope = if (inBlock) context.scope else context.owner.info.decls + def checkNoDoubleDefs(scope: Scope): Unit = { var e = scope.elems while ((e ne null) && e.owner == scope) { var e1 = scope.lookupNextEntry(e) @@ -3143,8 +3142,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - def addSynthetics(stats: List[Tree]): List[Tree] = { - val scope = if (inBlock) context.scope else context.owner.info.decls + def addSynthetics(stats: List[Tree], scope: Scope): List[Tree] = { var newStats = new ListBuffer[Tree] var moreToAdd = true while (moreToAdd) { @@ -3219,11 +3217,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val stats1 = stats mapConserve typedStat if (phase.erasedTypes) stats1 else { + val scope = if (inBlock) context.scope else context.owner.info.decls + // As packages are open, it doesn't make sense to check double definitions here. Furthermore, // it is expensive if the package is large. Instead, such double definitions are checked in `Namers.enterInScope` if (!context.owner.isPackageClass) - checkNoDoubleDefs - addSynthetics(stats1) + checkNoDoubleDefs(scope) + + addSynthetics(stats1, scope) } } -- cgit v1.2.3 From c04bcdc6dedf3e4cf3e6a608a66978841abc6171 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 22 Feb 2017 16:08:26 -0800 Subject: Refactor to reduce assignSymbol indirection - remove logging wrapper that also does important work - `assignAndEnterSymbol(tree)` --> `enterInScope(assignMemberSymbol(tree))` - reduce redundant type test (we know it's an import/package/member) --- .../tools/nsc/typechecker/MethodSynthesis.scala | 2 +- .../scala/tools/nsc/typechecker/Namers.scala | 88 +++++++++------------- .../scala/tools/nsc/typechecker/Unapplies.scala | 2 +- 3 files changed, 37 insertions(+), 55 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index 0f257d3717..fd9a45166e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -221,7 +221,7 @@ trait MethodSynthesis { def enterImplicitWrapper(classDef: ClassDef): Unit = { val methDef = factoryMeth(classDef.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef) - val methSym = assignAndEnterSymbol(methDef) + val methSym = enterInScope(assignMemberSymbol(methDef)) context.unit.synthetics(methSym) = methDef methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol, completerOf(methDef).asInstanceOf[LockingTypeCompleter]) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 1e4a59615f..e8eb19c52b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -103,14 +103,10 @@ trait Namers extends MethodSynthesis { else newNamer(cx) } - def enterValueParams(vparamss: List[List[ValDef]]): List[List[Symbol]] = { + def enterValueParams(vparamss: List[List[ValDef]]): List[List[Symbol]] = mmap(vparamss) { param => - val sym = assignSymbol(param, param.name, mask = ValueParameterFlags) - setPrivateWithin(param, sym) - enterInScope(sym) - sym setInfo monoTypeCompleter(param) + enterInScope(assignMemberSymbol(param, mask = ValueParameterFlags)) setInfo monoTypeCompleter(param) } - } protected def owner = context.owner def contextFile = context.unit.source.file @@ -286,9 +282,7 @@ trait Namers extends MethodSynthesis { case tree @ DefDef(_, _, _, _, _, _) => enterDefDef(tree) case tree @ TypeDef(_, _, _, _) => enterTypeDef(tree) case DocDef(_, defn) => enterSym(defn) - case tree @ Import(_, _) => - assignSymbol(tree) - returnContext = context.make(tree) + case tree @ Import(_, _) => enterImport(tree); returnContext = context.make(tree) case _ => } returnContext @@ -299,25 +293,15 @@ trait Namers extends MethodSynthesis { } } - /** Creates a new symbol and assigns it to the tree, returning the symbol - */ - def assignSymbol(tree: Tree): Symbol = - logAssignSymbol(tree, tree match { - case PackageDef(pid, _) => createPackageSymbol(tree.pos, pid) - case imp: Import => createImportSymbol(imp) - case mdef: MemberDef => createMemberSymbol(mdef, mdef.name, -1L) - case _ => abort("Unexpected tree: " + tree) - }) - def assignSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = - logAssignSymbol(tree, createMemberSymbol(tree, name, mask)) - - def assignAndEnterSymbol(tree: MemberDef): Symbol = { - val sym = assignSymbol(tree, tree.name, -1L) + def assignMemberSymbol(tree: MemberDef, mask: Long = -1L): Symbol = { + val sym = createMemberSymbol(tree, tree.name, mask) setPrivateWithin(tree, sym) - enterInScope(sym) + tree.symbol = sym + sym } + def assignAndEnterFinishedSymbol(tree: MemberDef): Symbol = { - val sym = assignAndEnterSymbol(tree) + val sym = enterInScope(assignMemberSymbol(tree)) sym setInfo completerOf(tree) // log("[+info] " + sym.fullLocationString) sym @@ -329,19 +313,6 @@ trait Namers extends MethodSynthesis { sym } - private def logAssignSymbol(tree: Tree, sym: Symbol): Symbol = { - if (isPastTyper) sym.name.toTermName match { - case nme.IMPORT | nme.OUTER | nme.ANON_CLASS_NAME | nme.ANON_FUN_NAME | nme.CONSTRUCTOR => () - case _ => - tree match { - case md: DefDef => log("[+symbol] " + sym.debugLocationString) - case _ => - } - } - tree.symbol = sym - sym - } - /** Create a new symbol at the context owner based on the given tree. * A different name can be given. If the modifier flags should not be * be transferred to the symbol as they are, supply a mask containing @@ -419,7 +390,7 @@ trait Namers extends MethodSynthesis { clearRenamedCaseAccessors(existing) existing } - else assignAndEnterSymbol(tree) setFlag inConstructorFlag + else enterInScope(assignMemberSymbol(tree)) setFlag inConstructorFlag } clazz match { case csym: ClassSymbol if csym.isTopLevel => enterClassSymbol(tree, csym) @@ -466,9 +437,10 @@ trait Namers extends MethodSynthesis { /** Enter a module symbol. */ def enterModuleSymbol(tree : ModuleDef): Symbol = { - var m: Symbol = context.scope lookupModule tree.name val moduleFlags = tree.mods.flags | MODULE - if (m.isModule && !m.hasPackageFlag && inCurrentScope(m) && (currentRun.canRedefine(m) || m.isSynthetic)) { + + val existingModule = context.scope lookupModule tree.name + if (existingModule.isModule && !existingModule.hasPackageFlag && inCurrentScope(existingModule) && (currentRun.canRedefine(existingModule) || existingModule.isSynthetic)) { // This code accounts for the way the package objects found in the classpath are opened up // early by the completer of the package itself. If the `packageobjects` phase then finds // the same package object in sources, we have to clean the slate and remove package object @@ -476,21 +448,24 @@ trait Namers extends MethodSynthesis { // // TODO SI-4695 Pursue the approach in https://github.com/scala/scala/pull/2789 that avoids // opening up the package object on the classpath at all if one exists in source. - if (m.isPackageObject) { - val packageScope = m.enclosingPackageClass.rawInfo.decls - packageScope.foreach(mem => if (mem.owner != m.enclosingPackageClass) packageScope unlink mem) + if (existingModule.isPackageObject) { + val packageScope = existingModule.enclosingPackageClass.rawInfo.decls + packageScope.foreach(mem => if (mem.owner != existingModule.enclosingPackageClass) packageScope unlink mem) } - updatePosFlags(m, tree.pos, moduleFlags) - setPrivateWithin(tree, m) - m.moduleClass andAlso (setPrivateWithin(tree, _)) - context.unit.synthetics -= m - tree.symbol = m + updatePosFlags(existingModule, tree.pos, moduleFlags) + setPrivateWithin(tree, existingModule) + existingModule.moduleClass andAlso (setPrivateWithin(tree, _)) + context.unit.synthetics -= existingModule + tree.symbol = existingModule } else { - m = assignAndEnterSymbol(tree) + enterInScope(assignMemberSymbol(tree)) + val m = tree.symbol m.moduleClass setFlag moduleClassFlags(moduleFlags) setPrivateWithin(tree, m.moduleClass) } + + val m = tree.symbol if (m.isTopLevel && !m.hasPackageFlag) { m.moduleClass.associatedFile = contextFile currentRun.symSource(m) = m.moduleClass.sourceFile @@ -751,17 +726,24 @@ trait Namers extends MethodSynthesis { } def enterPackage(tree: PackageDef) { - val sym = assignSymbol(tree) + val sym = createPackageSymbol(tree.pos, tree.pid) + tree.symbol = sym newNamer(context.make(tree, sym.moduleClass, sym.info.decls)) enterSyms tree.stats } + + private def enterImport(tree: Import) = { + val sym = createImportSymbol(tree) + tree.symbol = sym + } + def enterTypeDef(tree: TypeDef) = assignAndEnterFinishedSymbol(tree) def enterDefDef(tree: DefDef): Unit = tree match { case DefDef(_, nme.CONSTRUCTOR, _, _, _, _) => assignAndEnterFinishedSymbol(tree) - case DefDef(mods, name, tparams, _, _, _) => + case DefDef(mods, name, _, _, _, _) => val bridgeFlag = if (mods hasAnnotationNamed tpnme.bridgeAnnot) BRIDGE | ARTIFACT else 0 - val sym = assignAndEnterSymbol(tree) setFlag bridgeFlag + val sym = enterInScope(assignMemberSymbol(tree)) setFlag bridgeFlag val completer = if (sym hasFlag SYNTHETIC) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index f2e9b260b0..c13257f6ec 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -24,7 +24,7 @@ trait Unapplies extends ast.TreeDSL { private def unapplyParamName = nme.x_0 private def caseMods = Modifiers(SYNTHETIC | CASE) - // In the typeCompleter (templateSig) of a case class (resp it's module), + // In the typeCompleter (templateSig) of a case class (resp its module), // synthetic `copy` (reps `apply`, `unapply`) methods are added. To compute // their signatures, the corresponding ClassDef is needed. During naming (in // `enterClassDef`), the case class ClassDef is added as an attachment to the -- cgit v1.2.3 From 79a7015ae1753328203cef5105f44423505446f1 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 23 Feb 2017 12:05:21 -0800 Subject: Clean up copyMethodCompleter, capture less --- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index e8eb19c52b..355d7cba30 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -600,12 +600,10 @@ trait Namers extends MethodSynthesis { } def copyMethodCompleter(copyDef: DefDef): TypeCompleter = { - val sym = copyDef.symbol - val lazyType = completerOf(copyDef) - /* Assign the types of the class parameters to the parameters of the - * copy method. See comment in `Unapplies.caseClassCopyMeth` */ - def assignParamTypes() { + * copy method. See comment in `Unapplies.caseClassCopyMeth` + */ + def assignParamTypes(copyDef: DefDef, sym: Symbol) { val clazz = sym.owner val constructorType = clazz.primaryConstructor.tpe val subst = new SubstSymMap(clazz.typeParams, copyDef.tparams map (_.symbol)) @@ -618,9 +616,11 @@ trait Namers extends MethodSynthesis { ) } - mkTypeCompleter(copyDef) { sym => - assignParamTypes() - lazyType complete sym + new CompleterWrapper(completerOf(copyDef)) { + override def complete(sym: Symbol): Unit = { + assignParamTypes(tree.asInstanceOf[DefDef], sym) + super.complete(sym) + } } } -- cgit v1.2.3 From afad090d66ff565c856a231f6ae42dd70f75b2fc Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 6 Apr 2017 16:08:28 -0700 Subject: Use CompleterWrapper for implicitFactoryMethodCompleter mkTypeCompleter is not suitable for wrapping potentially polymorphic completers --- .../scala/tools/nsc/typechecker/MethodSynthesis.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index fd9a45166e..fea9debe7e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -223,7 +223,7 @@ trait MethodSynthesis { val methDef = factoryMeth(classDef.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef) val methSym = enterInScope(assignMemberSymbol(methDef)) context.unit.synthetics(methSym) = methDef - methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol, completerOf(methDef).asInstanceOf[LockingTypeCompleter]) + methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 355d7cba30..bce55a3e31 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -869,13 +869,14 @@ trait Namers extends MethodSynthesis { import AnnotationInfo.{mkFilter => annotationFilter} - def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => - sigCompleter.completeImpl(methSym) + def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol) = new CompleterWrapper(completerOf(tree)) { + override def complete(methSym: Symbol): Unit = { + super.complete(methSym) + val annotations = classSym.initialize.annotations - val annotations = classSym.initialize.annotations - - methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) - classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) + methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) + } } // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, -- cgit v1.2.3 From ade53a123c1edce12db442ee74b636d130e7e0f2 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 6 Apr 2017 16:30:34 -0700 Subject: Boy scout mkTypeCompleter Create named subclasses, preserve factory methods for external users. Make explicit that TypeCompleterBase is not meant for wrapping. --- .../tools/nsc/typechecker/MethodSynthesis.scala | 14 +- .../scala/tools/nsc/typechecker/Namers.scala | 247 ++++++++++++--------- 2 files changed, 143 insertions(+), 118 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index fea9debe7e..72d186b301 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -146,8 +146,8 @@ trait MethodSynthesis { // if there's no field symbol, the ValDef tree receives the getter symbol and thus is not a synthetic if (fieldSym != NoSymbol) { context.unit.synthetics(getterSym) = getter.derivedTree(getterSym) - getterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = false) - } else getterSym setInfo namer.valTypeCompleter(tree) + getterSym setInfo new namer.AccessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = false) + } else getterSym setInfo new namer.ValTypeCompleter(tree) enterInScope(getterSym) @@ -155,17 +155,17 @@ trait MethodSynthesis { val setter = Setter(tree) val setterSym = setter.createSym context.unit.synthetics(setterSym) = setter.derivedTree(setterSym) - setterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = true) + setterSym setInfo new namer.AccessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = true) enterInScope(setterSym) } // TODO: delay emitting the field to the fields phase (except for private[this] vals, which only get a field and no accessors) if (fieldSym != NoSymbol) { - fieldSym setInfo namer.valTypeCompleter(tree) + fieldSym setInfo new namer.ValTypeCompleter(tree) enterInScope(fieldSym) } } else { - getterSym setInfo namer.valTypeCompleter(tree) + getterSym setInfo new namer.ValTypeCompleter(tree) enterInScope(getterSym) } @@ -208,11 +208,11 @@ trait MethodSynthesis { sym } - val getterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = false) + val getterCompleter = new namer.AccessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = false) enterInScope(deriveBeanAccessor(if (hasBeanProperty) "get" else "is") setInfo getterCompleter) if (tree.mods.isMutable) { - val setterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = true) + val setterCompleter = new namer.AccessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = true) enterInScope(deriveBeanAccessor("set") setInfo setterCompleter) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index bce55a3e31..30ee0316fc 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -105,7 +105,7 @@ trait Namers extends MethodSynthesis { def enterValueParams(vparamss: List[List[ValDef]]): List[List[Symbol]] = mmap(vparamss) { param => - enterInScope(assignMemberSymbol(param, mask = ValueParameterFlags)) setInfo monoTypeCompleter(param) + enterInScope(assignMemberSymbol(param, mask = ValueParameterFlags)) setInfo new MonoTypeCompleter(param) } protected def owner = context.owner @@ -337,8 +337,10 @@ trait Namers extends MethodSynthesis { } } - def createImportSymbol(tree: Import) = - NoSymbol.newImport(tree.pos) setInfo (namerOf(tree.symbol) importTypeCompleter tree) + def createImportSymbol(tree: Import) = { + val importNamer = namerOf(tree.symbol) + NoSymbol.newImport(tree.pos) setInfo new importNamer.ImportTypeCompleter(tree) + } /** All PackageClassInfoTypes come from here. */ def createPackageSymbol(pos: Position, pid: RefTree): Symbol = { @@ -428,7 +430,8 @@ trait Namers extends MethodSynthesis { def enterModuleDef(tree: ModuleDef) = { val sym = enterModuleSymbol(tree) - sym.moduleClass setInfo namerOf(sym).moduleClassTypeCompleter(tree) + val mcsNamer = namerOf(sym) + sym.moduleClass setInfo new mcsNamer.ModuleClassTypeCompleter(tree) sym setInfo completerOf(tree) validateCompanionDefs(tree) sym @@ -588,17 +591,6 @@ trait Namers extends MethodSynthesis { noDuplicates(selectors map (_.rename), AppearsTwice) } - class CompleterWrapper(completer: TypeCompleter) extends TypeCompleter { - // override important when completer.isInstanceOf[PolyTypeCompleter]! - override val typeParams = completer.typeParams - - val tree = completer.tree - - override def complete(sym: Symbol): Unit = { - completer.complete(sym) - } - } - def copyMethodCompleter(copyDef: DefDef): TypeCompleter = { /* Assign the types of the class parameters to the parameters of the * copy method. See comment in `Unapplies.caseClassCopyMeth` @@ -692,7 +684,8 @@ trait Namers extends MethodSynthesis { } def completerOf(tree: MemberDef): TypeCompleter = { - val mono = namerOf(tree.symbol) monoTypeCompleter tree + val treeNamer = namerOf(tree.symbol) + val mono = new treeNamer.MonoTypeCompleter(tree) val tparams = treeInfo.typeParameters(tree) if (tparams.isEmpty) mono else { @@ -822,49 +815,57 @@ trait Namers extends MethodSynthesis { NoSymbol } - def monoTypeCompleter(tree: MemberDef) = mkTypeCompleter(tree) { sym => - // this early test is there to avoid infinite baseTypes when - // adding setters and getters --> bug798 - // It is a def in an attempt to provide some insulation against - // uninitialized symbols misleading us. It is not a certainty - // this accomplishes anything, but performance is a non-consideration - // on these flag checks so it can't hurt. - def needsCycleCheck = sym.isNonClassType && !sym.isParameter && !sym.isExistential - - val annotations = annotSig(tree.mods.annotations) + def monoTypeCompleter(tree: MemberDef) = new MonoTypeCompleter(tree) + class MonoTypeCompleter(tree: MemberDef) extends TypeCompleterBase(tree) { + override def completeImpl(sym: Symbol): Unit = { + // this early test is there to avoid infinite baseTypes when + // adding setters and getters --> bug798 + // It is a def in an attempt to provide some insulation against + // uninitialized symbols misleading us. It is not a certainty + // this accomplishes anything, but performance is a non-consideration + // on these flag checks so it can't hurt. + def needsCycleCheck = sym.isNonClassType && !sym.isParameter && !sym.isExistential + + val annotations = annotSig(tree.mods.annotations) + + val tp = typeSig(tree, annotations) + + findCyclicalLowerBound(tp) andAlso { sym => + if (needsCycleCheck) { + // neg/t1224: trait C[T] ; trait A { type T >: C[T] <: C[C[T]] } + // To avoid an infinite loop on the above, we cannot break all cycles + log(s"Reinitializing info of $sym to catch any genuine cycles") + sym reset sym.info + sym.initialize + } + } - val tp = typeSig(tree, annotations) + sym.setInfo(if (!sym.isJavaDefined) tp else RestrictJavaArraysMap(tp)) - findCyclicalLowerBound(tp) andAlso { sym => if (needsCycleCheck) { - // neg/t1224: trait C[T] ; trait A { type T >: C[T] <: C[C[T]] } - // To avoid an infinite loop on the above, we cannot break all cycles - log(s"Reinitializing info of $sym to catch any genuine cycles") - sym reset sym.info - sym.initialize + log(s"Needs cycle check: ${sym.debugLocationString}") + if (!typer.checkNonCyclic(tree.pos, tp)) + sym setInfo ErrorType } - } - sym.setInfo(if (!sym.isJavaDefined) tp else RestrictJavaArraysMap(tp)) - - if (needsCycleCheck) { - log(s"Needs cycle check: ${sym.debugLocationString}") - if (!typer.checkNonCyclic(tree.pos, tp)) - sym setInfo ErrorType + validate(sym) } - - validate(sym) } - def moduleClassTypeCompleter(tree: ModuleDef) = mkTypeCompleter(tree) { sym => - val moduleSymbol = tree.symbol - assert(moduleSymbol.moduleClass == sym, moduleSymbol.moduleClass) - moduleSymbol.info // sets moduleClass info as a side effect. + def moduleClassTypeCompleter(tree: ModuleDef) = new ModuleClassTypeCompleter(tree) + class ModuleClassTypeCompleter(tree: ModuleDef) extends TypeCompleterBase(tree) { + override def completeImpl(sym: Symbol): Unit = { + val moduleSymbol = tree.symbol + assert(moduleSymbol.moduleClass == sym, moduleSymbol.moduleClass) + moduleSymbol.info // sets moduleClass info as a side effect. + } } - - def importTypeCompleter(imp: Import) = mkTypeCompleter(imp) { sym => - sym setInfo importSig(imp) + def importTypeCompleter(tree: Import) = new ImportTypeCompleter(tree) + class ImportTypeCompleter(imp: Import) extends TypeCompleterBase(imp) { + override def completeImpl(sym: Symbol): Unit = { + sym setInfo importSig(imp) + } } import AnnotationInfo.{mkFilter => annotationFilter} @@ -881,57 +882,62 @@ trait Namers extends MethodSynthesis { // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, // as specified by Field.noFieldFor) - def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { fieldOrGetterSym => - val mods = tree.mods - val isGetter = fieldOrGetterSym.isMethod - val annots = - if (mods.annotations.isEmpty) Nil - else { - val annotSigs = annotSig(mods.annotations) + def valTypeCompleter(tree: ValDef) = new ValTypeCompleter(tree) + class ValTypeCompleter(tree: ValDef) extends TypeCompleterBase(tree) { + override def completeImpl(fieldOrGetterSym: Symbol): Unit = { + val mods = tree.mods + val isGetter = fieldOrGetterSym.isMethod + val annots = + if (mods.annotations.isEmpty) Nil + else { + val annotSigs = annotSig(mods.annotations) if (isGetter) filterAccessorAnnots(annotSigs, tree) // if this is really a getter, retain annots targeting either field/getter else annotSigs filter annotationFilter(FieldTargetClass, !mods.isParamAccessor) - } + } - // must use typeSig, not memberSig (TODO: when do we need to switch namers?) - val sig = typeSig(tree, annots) + // must use typeSig, not memberSig (TODO: when do we need to switch namers?) + val sig = typeSig(tree, annots) - fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig) + fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig) - validate(fieldOrGetterSym) + validate(fieldOrGetterSym) + } } // knowing `isBean`, we could derive `isSetter` from `valDef.name` - def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = mkTypeCompleter(valDef) { accessorSym => - context.unit.synthetics get accessorSym match { - case Some(ddef: DefDef) => - // `accessorSym` is the accessor for which we're completing the info (tree == ddef), - // while `valDef` is the field definition that spawned the accessor - // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol - - // reuse work done in valTypeCompleter if we already computed the type signature of the val - // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) - val valSig = - if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info - else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig) - - // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized - // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`) - // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back... - // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed) - if (missingTpt || accessorSym.isParamAccessor) { - if (!isSetter) ddef.tpt setType valSig - else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig - else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym") - } + def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = new AccessorTypeCompleter(valDef, missingTpt, isBean, isSetter) + class AccessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) extends TypeCompleterBase(valDef) { + override def completeImpl(accessorSym: Symbol): Unit = { + context.unit.synthetics get accessorSym match { + case Some(ddef: DefDef) => + // `accessorSym` is the accessor for which we're completing the info (tree == ddef), + // while `valDef` is the field definition that spawned the accessor + // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol + + // reuse work done in valTypeCompleter if we already computed the type signature of the val + // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) + val valSig = + if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info + else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig) + + // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized + // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`) + // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back... + // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed) + if (missingTpt || accessorSym.isParamAccessor) { + if (!isSetter) ddef.tpt setType valSig + else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig + else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym") + } - val mods = valDef.mods - val annots = - if (mods.annotations.isEmpty) Nil - else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean) + val mods = valDef.mods + val annots = + if (mods.annotations.isEmpty) Nil + else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean) - // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types) - // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig) - val sig = + // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types) + // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig) + val sig = if (isSetter || isBean) typeSig(ddef, annots) else { if (annots.nonEmpty) annotate(accessorSym, annots) @@ -939,16 +945,17 @@ trait Namers extends MethodSynthesis { NullaryMethodType(valSig) } - accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) + accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) - if (!isBean && accessorSym.isOverloaded) - if (isSetter) ddef.rhs.setType(ErrorType) - else GetterDefinedTwiceError(accessorSym) + if (!isBean && accessorSym.isOverloaded) + if (isSetter) ddef.rhs.setType(ErrorType) + else GetterDefinedTwiceError(accessorSym) - validate(accessorSym) + validate(accessorSym) - case _ => - throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym") + case _ => + throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym") + } } } @@ -993,11 +1000,14 @@ trait Namers extends MethodSynthesis { } - def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => - val selftpe = typer.typedType(tree).tpe - sym setInfo { - if (selftpe.typeSymbol isNonBottomSubClass sym.owner) selftpe - else intersectionType(List(sym.owner.tpe, selftpe)) + def selfTypeCompleter(tree: Tree) = new SelfTypeCompleter(tree) + class SelfTypeCompleter(tree: Tree) extends TypeCompleterBase(tree) { + override def completeImpl(sym: Symbol): Unit = { + val selftpe = typer.typedType(tree).tpe + sym setInfo { + if (selftpe.typeSymbol isNonBottomSubClass sym.owner) selftpe + else intersectionType(List(sym.owner.tpe, selftpe)) + } } } @@ -1071,7 +1081,7 @@ trait Namers extends MethodSynthesis { val sym = ( if (hasType || hasName) { - owner.typeOfThis = if (hasType) selfTypeCompleter(tpt) else owner.tpe_* + owner.typeOfThis = if (hasType) new SelfTypeCompleter(tpt) else owner.tpe_* val selfSym = owner.thisSym setPos self.pos if (hasName) selfSym setName name else selfSym } @@ -1165,7 +1175,7 @@ trait Namers extends MethodSynthesis { val res = GenPolyType(tparams0, resultType) val pluginsTp = pluginsTypeSig(res, typer, cdef, WildcardType) - // Already assign the type to the class symbol (monoTypeCompleter will do it again). + // Already assign the type to the class symbol (MonoTypeCompleter will do it again). // Allows isDerivedValueClass to look at the info. clazz setInfo pluginsTp if (clazz.isDerivedValueClass) { @@ -1179,7 +1189,7 @@ trait Namers extends MethodSynthesis { private def moduleSig(mdef: ModuleDef): Type = { val moduleSym = mdef.symbol - // The info of both the module and the moduleClass symbols need to be assigned. monoTypeCompleter assigns + // The info of both the module and the moduleClass symbols need to be assigned. MonoTypeCompleter assigns // the result of typeSig to the module symbol. The module class info is assigned here as a side-effect. val result = templateSig(mdef.impl) val pluginsTp = pluginsTypeSig(result, typer, mdef, WildcardType) @@ -1579,7 +1589,7 @@ trait Namers extends MethodSynthesis { // (a val's name ends in a " ", so can't compare to def) val overridingSym = if (isGetter) vdef.symbol else vdef.symbol.getterIn(valOwner) - // We're called from an accessorTypeCompleter, which is completing the info for the accessor's symbol, + // We're called from an AccessorTypeCompleter, which is completing the info for the accessor's symbol, // which may or may not be `vdef.symbol` (see isGetter above) val overridden = safeNextOverriddenSymbol(overridingSym) @@ -1722,7 +1732,7 @@ trait Namers extends MethodSynthesis { } /** - * TypeSig is invoked by monoTypeCompleters. It returns the type of a definition which + * TypeSig is invoked by MonoTypeCompleters. It returns the type of a definition which * is then assigned to the corresponding symbol (typeSig itself does not need to assign * the type to the symbol, but it can if necessary). */ @@ -1913,10 +1923,9 @@ trait Namers extends MethodSynthesis { } } - def mkTypeCompleter(t: Tree)(c: Symbol => Unit) = new LockingTypeCompleter with FlagAgnosticCompleter { - val tree = t - def completeImpl(sym: Symbol) = c(sym) - } + // NOTE: only meant for monomorphic definitions, + // do not use to wrap existing completers (see CompleterWrapper for that) + abstract class TypeCompleterBase[T <: Tree](val tree: T) extends LockingTypeCompleter with FlagAgnosticCompleter trait LockingTypeCompleter extends TypeCompleter { def completeImpl(sym: Symbol): Unit @@ -1960,6 +1969,22 @@ trait Namers extends MethodSynthesis { } } + /** + * Wrap an existing completer to do some post/pre-processing of the completed type. + * + * @param completer + */ + class CompleterWrapper(completer: TypeCompleter) extends TypeCompleter { + // override important when completer.isInstanceOf[PolyTypeCompleter]! + override val typeParams = completer.typeParams + + val tree = completer.tree + + override def complete(sym: Symbol): Unit = { + completer.complete(sym) + } + } + // Can we relax these restrictions? For motivation, see // test/files/pos/depmet_implicit_oopsla_session_2.scala // neg/depmet_try_implicit.scala -- cgit v1.2.3