From a8a7f4a0e6bc418073f8b9ed43abbc96ee0141d9 Mon Sep 17 00:00:00 2001 From: Denys Shabalin Date: Thu, 30 Jan 2014 11:39:55 +0100 Subject: SI-8202 bug compatibility with SI-8211 for quasiquotes --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 2 +- src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala | 2 ++ src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index e3d2bf14a0..a549fee4d9 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2495,7 +2495,7 @@ self => def mkDefs(p: Tree, tp: Tree, rhs: Tree): List[Tree] = { val trees = { val pat = if (tp.isEmpty) p else Typed(p, tp) setPos (p.pos union tp.pos) - gen.mkPatDef(newmods, pat, rhs) + makePatDef(newmods, pat, rhs) } if (newmods.isDeferred) { trees match { diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 525dcffb0c..6e5a3f6ef7 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -168,4 +168,6 @@ abstract class TreeBuilder { vparamss ::: List(evidenceParams) } } + + def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree) = gen.mkPatDef(mods, pat, rhs) } diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index fcb8734644..4bb37d8487 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -77,6 +77,12 @@ trait Parsers { self: Quasiquotes => // tq"$a => $b" override def makeFunctionTypeTree(argtpes: List[Tree], restpe: Tree): Tree = AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), argtpes :+ restpe) + + // make q"val (x: T) = rhs" be equivalent to q"val x: T = rhs" for sake of bug compatibility (SI-8211) + override def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree) = pat match { + case Apply(Ident(nme.QUASIQUOTE_TUPLE), inParensPat :: Nil) => super.makePatDef(mods, inParensPat, rhs) + case _ => super.makePatDef(mods, pat, rhs) + } } import treeBuilder.{global => _, unit => _, _} -- cgit v1.2.3 From 4280fc1b854d7ab574c7d1063343acd73cb977c1 Mon Sep 17 00:00:00 2001 From: Denys Shabalin Date: Sun, 9 Feb 2014 15:00:55 +0100 Subject: Move placeholder construction logic into Placeholders Previously construction logic used to be in Parsers and deconstruction in Placeholders making it easy to forget one if you change the other. --- .../scala/tools/reflect/quasiquotes/Parsers.scala | 30 +++++++++------------- .../tools/reflect/quasiquotes/Placeholders.scala | 28 +++++++++++++++++--- src/reflect/scala/reflect/internal/StdNames.scala | 16 ++++++------ 3 files changed, 44 insertions(+), 30 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index 4bb37d8487..2410af49e5 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -61,12 +61,10 @@ trait Parsers { self: Quasiquotes => override implicit def fresh: FreshNameCreator = parser.fresh // q"(..$xs)" - override def makeTupleTerm(trees: List[Tree]): Tree = - Apply(Ident(nme.QUASIQUOTE_TUPLE), trees) + override def makeTupleTerm(trees: List[Tree]): Tree = TuplePlaceholder(trees) // tq"(..$xs)" - override def makeTupleType(trees: List[Tree]): Tree = - AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), trees) + override def makeTupleType(trees: List[Tree]): Tree = TupleTypePlaceholder(trees) // q"{ $x }" override def makeBlock(stats: List[Tree]): Tree = stats match { @@ -75,36 +73,32 @@ trait Parsers { self: Quasiquotes => } // tq"$a => $b" - override def makeFunctionTypeTree(argtpes: List[Tree], restpe: Tree): Tree = - AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), argtpes :+ restpe) + override def makeFunctionTypeTree(argtpes: List[Tree], restpe: Tree): Tree = FunctionTypePlaceholder(argtpes, restpe) // make q"val (x: T) = rhs" be equivalent to q"val x: T = rhs" for sake of bug compatibility (SI-8211) override def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree) = pat match { - case Apply(Ident(nme.QUASIQUOTE_TUPLE), inParensPat :: Nil) => super.makePatDef(mods, inParensPat, rhs) + case TuplePlaceholder(inParensPat :: Nil) => super.makePatDef(mods, inParensPat, rhs) case _ => super.makePatDef(mods, pat, rhs) } } import treeBuilder.{global => _, unit => _, _} - def quasiquoteParam(name: Name, flags: FlagSet = NoFlags) = - ValDef(Modifiers(flags), name.toTermName, Ident(tpnme.QUASIQUOTE_PARAM), EmptyTree) - // q"def foo($x)" override def param(owner: Name, implicitmod: Int, caseParam: Boolean): ValDef = if (isHole && lookingAhead { in.token == COMMA || in.token == RPAREN }) { - quasiquoteParam(ident(), implicitmod) + ParamPlaceholder(implicitmod, ident()) } else super.param(owner, implicitmod, caseParam) // q"($x) => ..." && q"class X { selfie => } override def convertToParam(tree: Tree): ValDef = tree match { - case Ident(name) if isHole(name) => quasiquoteParam(name) + case Ident(name) if isHole(name) => ParamPlaceholder(NoFlags, name) case _ => super.convertToParam(tree) } // q"foo match { case $x }" override def caseClause(): CaseDef = if (isHole && lookingAhead { in.token == CASE || in.token == RBRACE || in.token == SEMI }) { - val c = makeCaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Ident(ident()))), EmptyTree, EmptyTree) + val c = CasePlaceholder(ident()) while (in.token == SEMI) in.nextToken() c } else @@ -138,7 +132,7 @@ trait Parsers { self: Quasiquotes => in.nextToken() annot :: readAnnots(annot) case _ if isHole && lookingAhead { isAnnotation || isModifier || isDefIntro || isIdent || isStatSep || in.token == LPAREN } => - val ann = Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(in.name.toString)))) + val ann = ModsPlaceholder(in.name) in.nextToken() ann :: readAnnots(annot) case _ => @@ -147,13 +141,13 @@ trait Parsers { self: Quasiquotes => override def refineStat(): List[Tree] = if (isHole && !isDclIntro) { - val result = ValDef(NoMods, in.name, Ident(tpnme.QUASIQUOTE_REFINE_STAT), EmptyTree) :: Nil + val result = RefineStatPlaceholder(in.name) :: Nil in.nextToken() result } else super.refineStat() override def ensureEarlyDef(tree: Tree) = tree match { - case Ident(name: TermName) if isHole(name) => ValDef(NoMods | Flag.PRESUPER, name, Ident(tpnme.QUASIQUOTE_EARLY_DEF), EmptyTree) + case Ident(name: TermName) if isHole(name) => EarlyDefPlaceholder(name) case _ => super.ensureEarlyDef(tree) } @@ -164,14 +158,14 @@ trait Parsers { self: Quasiquotes => override def topStat = super.topStat.orElse { case _ if isHole => - val stats = ValDef(NoMods, in.name, Ident(tpnme.QUASIQUOTE_PACKAGE_STAT), EmptyTree) :: Nil + val stats = PackageStatPlaceholder(in.name) :: Nil in.nextToken() stats } override def enumerator(isFirst: Boolean, allowNestedIf: Boolean = true) = if (isHole && lookingAhead { in.token == EOF || in.token == RPAREN || isStatSep }) { - val res = build.SyntacticValFrom(Bind(in.name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) :: Nil + val res = ForEnumPlaceholder(in.name) :: Nil in.nextToken() res } else super.enumerator(isFirst, allowNestedIf) diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala index 825d0c04f3..130a01332b 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Placeholders.scala @@ -100,6 +100,8 @@ trait Placeholders { self: Quasiquotes => } object ModsPlaceholder extends HolePlaceholder { + def apply(name: Name) = + Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(name.toString)))) def matching = { case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s) } @@ -112,12 +114,16 @@ trait Placeholders { self: Quasiquotes => } object ParamPlaceholder extends HolePlaceholder { + def apply(flags: FlagSet, name: Name) = + ValDef(Modifiers(flags), nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) def matching = { - case ValDef(_, name, Ident(tpnme.QUASIQUOTE_PARAM), EmptyTree) => name + case ValDef(_, nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) => name } } object TuplePlaceholder { + def apply(args: List[Tree]) = + Apply(Ident(nme.QUASIQUOTE_TUPLE), args) def unapply(tree: Tree): Option[List[Tree]] = tree match { case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args) case _ => None @@ -125,6 +131,8 @@ trait Placeholders { self: Quasiquotes => } object TupleTypePlaceholder { + def apply(args: List[Tree]) = + AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) def unapply(tree: Tree): Option[List[Tree]] = tree match { case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args) case _ => None @@ -132,6 +140,8 @@ trait Placeholders { self: Quasiquotes => } object FunctionTypePlaceholder { + def apply(args: List[Tree], res: Tree) = + AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) def unapply(tree: Tree): Option[(List[Tree], Tree)] = tree match { case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) => Some((args, res)) case _ => None @@ -146,6 +156,8 @@ trait Placeholders { self: Quasiquotes => } object CasePlaceholder { + def apply(name: Name) = + CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), Ident(name) :: Nil), EmptyTree, EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(hole))), EmptyTree, EmptyTree) => Some(hole) case _ => None @@ -153,27 +165,35 @@ trait Placeholders { self: Quasiquotes => } object RefineStatPlaceholder { + def apply(name: Name) = + ValDef(NoMods, nme.QUASIQUOTE_REFINE_STAT, Ident(name), EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { - case ValDef(_, Placeholder(hole), Ident(tpnme.QUASIQUOTE_REFINE_STAT), _) => Some(hole) + case ValDef(_, nme.QUASIQUOTE_REFINE_STAT, Ident(Placeholder(hole)), _) => Some(hole) case _ => None } } object EarlyDefPlaceholder { + def apply(name: Name) = + ValDef(Modifiers(Flag.PRESUPER), nme.QUASIQUOTE_EARLY_DEF, Ident(name), EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { - case ValDef(_, Placeholder(hole), Ident(tpnme.QUASIQUOTE_EARLY_DEF), _) => Some(hole) + case ValDef(_, nme.QUASIQUOTE_EARLY_DEF, Ident(Placeholder(hole)), _) => Some(hole) case _ => None } } object PackageStatPlaceholder { + def apply(name: Name) = + ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(name), EmptyTree) def unapply(tree: Tree): Option[Hole] = tree match { - case ValDef(NoMods, Placeholder(hole), Ident(tpnme.QUASIQUOTE_PACKAGE_STAT), EmptyTree) => Some(hole) + case ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(Placeholder(hole)), EmptyTree) => Some(hole) case _ => None } } object ForEnumPlaceholder { + def apply(name: Name) = + build.SyntacticValFrom(Bind(name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) def unapply(tree: Tree): Option[Hole] = tree match { case build.SyntacticValFrom(Bind(Placeholder(hole), Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) => Some(hole) diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 679186f938..0ac067a399 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -253,12 +253,8 @@ trait StdNames { final val Quasiquote: NameType = "Quasiquote" // quasiquote-specific names - final val QUASIQUOTE_EARLY_DEF: NameType = "$quasiquote$early$def$" final val QUASIQUOTE_FUNCTION: NameType = "$quasiquote$function$" final val QUASIQUOTE_MODS: NameType = "$quasiquote$mods$" - final val QUASIQUOTE_PACKAGE_STAT: NameType = "$quasiquote$package$stat$" - final val QUASIQUOTE_PARAM: NameType = "$quasiquote$param$" - final val QUASIQUOTE_REFINE_STAT: NameType = "$quasiquote$refine$stat$" final val QUASIQUOTE_TUPLE: NameType = "$quasiquote$tuple$" // Annotation simple names, used in Namer @@ -331,12 +327,16 @@ trait StdNames { val REIFY_FREE_THIS_SUFFIX: NameType = "$this" val REIFY_FREE_VALUE_SUFFIX: NameType = "$value" val REIFY_SYMDEF_PREFIX: NameType = "symdef$" - val QUASIQUOTE_PREFIX: String = "qq$" - val QUASIQUOTE_NAME_PREFIX: String = "nn$" - val QUASIQUOTE_FILE: String = "" - val QUASIQUOTE_TUPLE: NameType = "$quasiquote$tuple$" val QUASIQUOTE_CASE: NameType = "$quasiquote$case$" + val QUASIQUOTE_EARLY_DEF: NameType = "$quasiquote$early$def$" + val QUASIQUOTE_FILE: String = "" val QUASIQUOTE_FOR_ENUM: NameType = "$quasiquote$for$enum$" + val QUASIQUOTE_NAME_PREFIX: String = "nn$" + val QUASIQUOTE_PACKAGE_STAT: NameType = "$quasiquote$package$stat$" + val QUASIQUOTE_PARAM: NameType = "$quasiquote$param$" + val QUASIQUOTE_PREFIX: String = "qq$" + val QUASIQUOTE_REFINE_STAT: NameType = "$quasiquote$refine$stat$" + val QUASIQUOTE_TUPLE: NameType = "$quasiquote$tuple$" val QUASIQUOTE_UNLIFT_HELPER: String = "$quasiquote$unlift$helper$" val MIXIN_CONSTRUCTOR: NameType = "$init$" val MODULE_INSTANCE_FIELD: NameType = NameTransformer.MODULE_INSTANCE_NAME // "MODULE$" -- cgit v1.2.3 From 8a27336c0e896d3fee6213068c77f23e62a0cd18 Mon Sep 17 00:00:00 2001 From: Denys Shabalin Date: Sun, 9 Feb 2014 15:02:44 +0100 Subject: Improve support for patterns in vals This commits adds construction-only support for splicing patterns into vals (a.k.a. PatDef). Due to non-locality of the desugaring it would have been quite expensive to support deconstruction as the only way to do it with current trees is to perform implodePatDefs transformation on every single tree. --- .../scala/tools/reflect/quasiquotes/Parsers.scala | 7 +- .../scala/tools/reflect/quasiquotes/Reifiers.scala | 25 ++++--- src/reflect/scala/reflect/api/BuildUtils.scala | 6 ++ .../scala/reflect/internal/BuildUtils.scala | 84 ++++++++++++++++------ src/reflect/scala/reflect/internal/StdNames.scala | 2 + src/reflect/scala/reflect/internal/TreeGen.scala | 4 +- .../quasiquotes/DefinitionConstructionProps.scala | 68 ++++++++++++++---- .../quasiquotes/QuasiquoteProperties.scala | 2 +- 8 files changed, 145 insertions(+), 53 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala index 2410af49e5..36452c2a6d 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Parsers.scala @@ -14,6 +14,7 @@ import scala.util.Try */ trait Parsers { self: Quasiquotes => import global.{Try => _, _} + import build.implodePatDefs abstract class Parser extends { val global: self.global.type = self.global @@ -182,7 +183,7 @@ trait Parsers { self: Quasiquotes => } object TermParser extends Parser { - def entryPoint = parser => Q(gen.mkTreeOrBlock(parser.templateOrTopStatSeq())) + def entryPoint = parser => Q(implodePatDefs(gen.mkTreeOrBlock(parser.templateOrTopStatSeq()))) } object TypeParser extends Parser { @@ -195,7 +196,7 @@ trait Parsers { self: Quasiquotes => } object CaseParser extends Parser { - def entryPoint = _.caseClause() + def entryPoint = parser => implodePatDefs(parser.caseClause()) } object PatternParser extends Parser { @@ -209,7 +210,7 @@ trait Parsers { self: Quasiquotes => def entryPoint = { parser => val enums = parser.enumerator(isFirst = false, allowNestedIf = false) assert(enums.length == 1) - enums.head + implodePatDefs(enums.head) } } diff --git a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala index 017e966f63..70580adbce 100644 --- a/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala +++ b/src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala @@ -194,8 +194,8 @@ trait Reifiers { self: Quasiquotes => reifyBuildCall(nme.SyntacticEmptyTypeTree) case SyntacticImport(expr, selectors) => reifyBuildCall(nme.SyntacticImport, expr, selectors) - case Q(Placeholder(Hole(tree, DotDot))) => - mirrorBuildCall(nme.SyntacticBlock, tree) + case Q(tree) if fillListHole.isDefinedAt(tree) => + mirrorBuildCall(nme.SyntacticBlock, fillListHole(tree)) case Q(other) => reifyTree(other) // Syntactic block always matches so we have to be careful @@ -311,11 +311,7 @@ trait Reifiers { self: Quasiquotes => */ def reifyMultiCardinalityList(xs: List[Any])(fill: PartialFunction[Any, Tree])(fallback: Any => Tree): Tree - /** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put - * in the correct position. Fallbacks to regular reification for non-high cardinality - * elements. - */ - override def reifyList(xs: List[Any]): Tree = reifyMultiCardinalityList(xs) { + val fillListHole: PartialFunction[Any, Tree] = { case Placeholder(Hole(tree, DotDot)) => tree case CasePlaceholder(Hole(tree, DotDot)) => tree case RefineStatPlaceholder(h @ Hole(_, DotDot)) => reifyRefineStat(h) @@ -323,12 +319,23 @@ trait Reifiers { self: Quasiquotes => case PackageStatPlaceholder(h @ Hole(_, DotDot)) => reifyPackageStat(h) case ForEnumPlaceholder(Hole(tree, DotDot)) => tree case ParamPlaceholder(Hole(tree, DotDot)) => tree + case SyntacticPatDef(mods, pat, tpt, rhs) => + reifyBuildCall(nme.SyntacticPatDef, mods, pat, tpt, rhs) + case SyntacticValDef(mods, p @ Placeholder(h: ApplyHole), tpt, rhs) if h.tpe <:< treeType => + mirrorBuildCall(nme.SyntacticPatDef, reify(mods), h.tree, reify(tpt), reify(rhs)) + } + + val fillListOfListsHole: PartialFunction[Any, Tree] = { case List(ParamPlaceholder(Hole(tree, DotDotDot))) => tree case List(Placeholder(Hole(tree, DotDotDot))) => tree - } { - reify(_) } + /** Reifies arbitrary list filling ..$x and ...$y holeMap when they are put + * in the correct position. Fallbacks to regular reification for non-high cardinality + * elements. + */ + override def reifyList(xs: List[Any]): Tree = reifyMultiCardinalityList(xs)(fillListHole.orElse(fillListOfListsHole))(reify) + def reifyAnnotList(annots: List[Tree]): Tree = reifyMultiCardinalityList(annots) { case AnnotPlaceholder(h @ Hole(_, DotDot)) => reifyAnnotation(h) } { diff --git a/src/reflect/scala/reflect/api/BuildUtils.scala b/src/reflect/scala/reflect/api/BuildUtils.scala index ec20a89a10..57dc7da6cc 100644 --- a/src/reflect/scala/reflect/api/BuildUtils.scala +++ b/src/reflect/scala/reflect/api/BuildUtils.scala @@ -220,6 +220,12 @@ private[reflect] trait BuildUtils { self: Universe => def unapply(tree: Tree): Option[(Modifiers, TermName, Tree, Tree)] } + val SyntacticPatDef: SyntacticPatDefExtractor + + trait SyntacticPatDefExtractor { + def apply(mods: Modifiers, pat: Tree, tpt: Tree, rhs: Tree): List[ValDef] + } + val SyntacticAssign: SyntacticAssignExtractor trait SyntacticAssignExtractor { diff --git a/src/reflect/scala/reflect/internal/BuildUtils.scala b/src/reflect/scala/reflect/internal/BuildUtils.scala index c5581601de..6f1e4d2e32 100644 --- a/src/reflect/scala/reflect/internal/BuildUtils.scala +++ b/src/reflect/scala/reflect/internal/BuildUtils.scala @@ -193,9 +193,9 @@ trait BuildUtils { self: SymbolTable => // recover constructor contents generated by gen.mkTemplate protected object UnCtor { def unapply(tree: Tree): Option[(Modifiers, List[List[ValDef]], List[Tree])] = tree match { - case DefDef(mods, nme.MIXIN_CONSTRUCTOR, _, _, _, Block(lvdefs, _)) => + case DefDef(mods, nme.MIXIN_CONSTRUCTOR, _, _, _, SyntacticBlock(lvdefs :+ _)) => Some((mods | Flag.TRAIT, Nil, lvdefs)) - case DefDef(mods, nme.CONSTRUCTOR, Nil, vparamss, _, Block(lvdefs :+ _, _)) => + case DefDef(mods, nme.CONSTRUCTOR, Nil, vparamss, _, SyntacticBlock(lvdefs :+ _ :+ _)) => Some((mods, vparamss, lvdefs)) case _ => None } @@ -474,10 +474,9 @@ trait BuildUtils { self: SymbolTable => } protected class SyntacticValDefBase(isMutable: Boolean) extends SyntacticValDefExtractor { - def apply(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree) = { - val mods1 = if (isMutable) mods | MUTABLE else mods - ValDef(mods1, name, tpt, rhs) - } + def modifiers(mods: Modifiers): Modifiers = if (isMutable) mods | MUTABLE else mods + + def apply(mods: Modifiers, name: TermName, tpt: Tree, rhs: Tree): ValDef = ValDef(modifiers(mods), name, tpt, rhs) def unapply(tree: Tree): Option[(Modifiers, TermName, Tree, Tree)] = tree match { case ValDef(mods, name, tpt, rhs) if mods.hasFlag(MUTABLE) == isMutable => @@ -547,25 +546,57 @@ trait BuildUtils { self: SymbolTable => // match a sequence of desugared `val $pat = $value` protected object UnPatSeq { - def unapply(trees: List[Tree]): Option[List[(Tree, Tree)]] = trees match { - case Nil => Some(Nil) - // case q"$mods val ${_}: ${_} = ${MaybeUnchecked(value)} match { case $pat => (..$ids) }" :: tail - case ValDef(mods, _, _, Match(MaybeUnchecked(value), CaseDef(pat, EmptyTree, SyntacticTuple(ids)) :: Nil)) :: tail + def unapply(trees: List[Tree]): Option[List[(Tree, Tree)]] = { + val imploded = implodePatDefs(trees) + val patvalues = imploded.flatMap { + case SyntacticPatDef(_, pat, EmptyTree, rhs) => Some((pat, rhs)) + case ValDef(_, name, SyntacticEmptyTypeTree(), rhs) => Some((Bind(name, self.Ident(nme.WILDCARD)), rhs)) + case ValDef(_, name, tpt, rhs) => Some((Bind(name, Typed(self.Ident(nme.WILDCARD), tpt)), rhs)) + case _ => None + } + if (patvalues.length == imploded.length) Some(patvalues) else None + } + } + + // implode multiple-statement desugaring of pattern definitions + // into single-statement valdefs with nme.QUASIQUOTE_PAT_DEF name + object implodePatDefs extends Transformer { + override def transform(tree: Tree) = tree match { + case templ: Template => deriveTemplate(templ)(transformStats) + case block: Block => + val Block(init, last) = block + Block(transformStats(init), transform(last)).copyAttrs(block) + case ValDef(mods, name1, SyntacticEmptyTypeTree(), Match(MaybeTyped(MaybeUnchecked(value), tpt), CaseDef(pat, EmptyTree, Ident(name2)) :: Nil)) + if name1 == name2 => + ValDef(mods, nme.QUASIQUOTE_PAT_DEF, Typed(pat, tpt), transform(value)) + case _ => + super.transform(tree) + } + def transformStats(trees: List[Tree]): List[Tree] = trees match { + case Nil => Nil + case ValDef(mods, _, SyntacticEmptyTypeTree(), Match(MaybeTyped(MaybeUnchecked(value), tpt), CaseDef(pat, EmptyTree, SyntacticTuple(ids)) :: Nil)) :: tail if mods.hasFlag(SYNTHETIC) && mods.hasFlag(ARTIFACT) => - tail.drop(ids.length) match { - case UnPatSeq(rest) => Some((pat, value) :: rest) - case _ => None + ids match { + case Nil => + ValDef(NoMods, nme.QUASIQUOTE_PAT_DEF, Typed(pat, tpt), transform(value)) :: transformStats(tail) + case _ => + val mods = tail.take(1).head.asInstanceOf[ValDef].mods + ValDef(mods, nme.QUASIQUOTE_PAT_DEF, Typed(pat, tpt), transform(value)) :: transformStats(tail.drop(ids.length)) } - // case q"${_} val $name1: ${_} = ${MaybeUnchecked(value)} match { case $pat => ${Ident(name2)} }" :: UnPatSeq(rest) - case ValDef(_, name1, _, Match(MaybeUnchecked(value), CaseDef(pat, EmptyTree, Ident(name2)) :: Nil)) :: UnPatSeq(rest) - if name1 == name2 => - Some((pat, value) :: rest) - // case q"${_} val $name: ${SyntacticEmptyTypeTree()} = $value" :: UnPatSeq(rest) => - case ValDef(_, name, SyntacticEmptyTypeTree(), value) :: UnPatSeq(rest) => - Some((Bind(name, self.Ident(nme.WILDCARD)), value) :: rest) - // case q"${_} val $name: $tpt = $value" :: UnPatSeq(rest) => - case ValDef(_, name, tpt, value) :: UnPatSeq(rest) => - Some((Bind(name, Typed(self.Ident(nme.WILDCARD), tpt)), value) :: rest) + case other :: tail => + transform(other) :: transformStats(tail) + } + def apply(tree: Tree) = transform(tree) + def apply(trees: List[Tree]) = transformStats(trees) + } + + object SyntacticPatDef extends SyntacticPatDefExtractor { + def apply(mods: Modifiers, pat: Tree, tpt: Tree, rhs: Tree): List[ValDef] = tpt match { + case SyntacticEmptyTypeTree() => gen.mkPatDef(mods, pat, rhs) + case _ => gen.mkPatDef(mods, Typed(pat, tpt), rhs) + } + def unapply(tree: Tree): Option[(Modifiers, Tree, Tree, Tree)] = tree match { + case ValDef(mods, nme.QUASIQUOTE_PAT_DEF, Typed(pat, tpt), rhs) => Some((mods, pat, tpt, rhs)) case _ => None } } @@ -746,6 +777,13 @@ trait BuildUtils { self: SymbolTable => } } + protected object MaybeTyped { + def unapply(tree: Tree): Some[(Tree, Tree)] = tree match { + case Typed(v, tpt) => Some((v, tpt)) + case v => Some((v, SyntacticEmptyTypeTree())) + } + } + protected def mkCases(cases: List[Tree]): List[CaseDef] = cases.map { case c: CaseDef => c case tree => throw new IllegalArgumentException("$tree is not valid representation of pattern match case") diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 0ac067a399..de582ee5f9 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -334,6 +334,7 @@ trait StdNames { val QUASIQUOTE_NAME_PREFIX: String = "nn$" val QUASIQUOTE_PACKAGE_STAT: NameType = "$quasiquote$package$stat$" val QUASIQUOTE_PARAM: NameType = "$quasiquote$param$" + val QUASIQUOTE_PAT_DEF: NameType = "$quasiquote$pat$def$" val QUASIQUOTE_PREFIX: String = "qq$" val QUASIQUOTE_REFINE_STAT: NameType = "$quasiquote$refine$stat$" val QUASIQUOTE_TUPLE: NameType = "$quasiquote$tuple$" @@ -615,6 +616,7 @@ trait StdNames { val SyntacticNew: NameType = "SyntacticNew" val SyntacticObjectDef: NameType = "SyntacticObjectDef" val SyntacticPackageObjectDef: NameType = "SyntacticPackageObjectDef" + val SyntacticPatDef: NameType = "SyntacticPatDef" val SyntacticTraitDef: NameType = "SyntacticTraitDef" val SyntacticTry: NameType = "SyntacticTry" val SyntacticTuple: NameType = "SyntacticTuple" diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 29fdba2781..e7675eb4bf 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -691,11 +691,11 @@ abstract class TreeGen extends macros.TreeBuilder { } /** Create tree for pattern definition */ - def mkPatDef(pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator): List[Tree] = + def mkPatDef(pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator): List[ValDef] = mkPatDef(Modifiers(0), pat, rhs) /** Create tree for pattern definition */ - def mkPatDef(mods: Modifiers, pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator): List[Tree] = matchVarPattern(pat) match { + def mkPatDef(mods: Modifiers, pat: Tree, rhs: Tree)(implicit fresh: FreshNameCreator): List[ValDef] = matchVarPattern(pat) match { case Some((name, tpt)) => List(atPos(pat.pos union rhs.pos) { ValDef(mods, name.toTermName, tpt, rhs) diff --git a/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala b/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala index 39e4c48073..5f22925335 100644 --- a/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala +++ b/test/files/scalacheck/quasiquotes/DefinitionConstructionProps.scala @@ -7,21 +7,22 @@ object DefinitionConstructionProps with TraitConstruction with TypeDefConstruction with ValDefConstruction + with PatDefConstruction with DefConstruction - with PackageConstruction + with PackageConstruction with ImportConstruction { - property("SI-6842") = test { - val x: Tree = q"val x: Int" - assertEqAst(q"def f($x) = 0", "def f(x: Int) = 0") - assertEqAst(q"class C($x)", "class C(val x: Int)") - assertEqAst(q"class C { $x => }", "class C { x: Int => }") - assertEqAst(q"trait B { $x => }", "trait B { x: Int => }") - assertEqAst(q"object A { $x => }", "object A { x: Int => }") - val t: Tree = q"type T" - assertEqAst(q"def f[$t] = 0", "def f[T] = 0") - assertEqAst(q"class C[$t]", "class C[T]") - assertEqAst(q"trait B[$t]", "trait B[T]") - } + + val x: Tree = q"val x: Int" + property("SI-6842 a1") = test { assertEqAst(q"def f($x) = 0", "def f(x: Int) = 0") } + property("SI-6842 a2") = test { assertEqAst(q"class C($x)", "class C(val x: Int)") } + property("SI-6842 a3") = test { assertEqAst(q"class C { $x => }", "class C { x: Int => }") } + property("SI-6842 a4") = test { assertEqAst(q"trait B { $x => }", "trait B { x: Int => }") } + property("SI-6842 a5") = test { assertEqAst(q"object A { $x => }", "object A { x: Int => }") } + + val t: Tree = q"type T" + property("SI-6842 b1") = test { assertEqAst(q"def f[$t] = 0", "def f[T] = 0") } + property("SI-6842 b2") = test { assertEqAst(q"class C[$t]", "class C[T]") } + property("SI-6842 b3") = test { assertEqAst(q"trait B[$t]", "trait B[T]") } } trait ClassConstruction { self: QuasiquoteProperties => @@ -200,19 +201,56 @@ trait TypeDefConstruction { self: QuasiquoteProperties => } trait ValDefConstruction { self: QuasiquoteProperties => - property("splice term name into val") = forAll { (name: TermName, tpt: Tree, rhs: Tree) => + property("splice into val") = forAll { (name: TermName, tpt: Tree, rhs: Tree) => q"val $name: $tpt = $rhs" ≈ ValDef(Modifiers(), name, tpt, rhs) } - property("splice term name into var") = forAll { (name: TermName, tpt: Tree, rhs: Tree) => + property("splice into var") = forAll { (name: TermName, tpt: Tree, rhs: Tree) => q"var $name: $tpt = $rhs" ≈ ValDef(Modifiers(MUTABLE), name, tpt, rhs) } + // left tree is not a pattern due to Si-8211 property("SI-8202") = test { assertEqAst(q"val (x: Int) = 1", "val x: Int = 1") } } +trait PatDefConstruction { self: QuasiquoteProperties => + property("splice pattern into pat def") = test { + val pat = pq"(a, b)" + assertEqAst(q"val $pat = (1, 2)", "val (a, b) = (1, 2)") + val tpt = tq"(Int, Int)" + assertEqAst(q"val $pat: $tpt = (1, 2)", "val (a, b): (Int, Int) = (1, 2)") + } + + property("splice pattern into pat def within other pattern (1)") = test { + val pat = pq"(a, b)" + assertEqAst(q"val Foo($pat) = Foo((1, 2))", "val Foo((a, b)) = Foo((1, 2))") + val tpt = tq"Foo" + assertEqAst(q"val Foo($pat): $tpt = Foo((1, 2))", "val Foo((a, b)): Foo = Foo((1, 2))") + } + + property("splice patterns into pat def within other pattern (2)") = test { + val pat1 = pq"(a, b)"; val pat2 = pq"(c, d)" + assertEqAst(q"val ($pat1, $pat2) = ((1, 2), (3, 4))", "val ((a, b), (c, d)) = ((1, 2), (3, 4))") + val tpt = tq"((Int, Int), (Int, Int))" + assertEqAst(q"val ($pat1, $pat2): $tpt = ((1, 2), (3, 4))", "val ((a, b), (c, d)): ((Int, Int), (Int, Int)) = ((1, 2), (3, 4))") + } + + property("splice pattern without free vars into pat def") = test { + val pat = pq"((1, 2), 3)" + assertEqAst(q"val $pat = ((1, 2), 3)", "{ val ((1, 2), 3) = ((1, 2), 3) }") + val tpt = tq"((Int, Int), Int)" + assertEqAst(q"val $pat: $tpt = ((1, 2), 3)","{ val ((1, 2), 3): ((Int, Int), Int) = ((1, 2), 3) }") + } + + // won't result into pattern match due to SI-8211 + property("splice typed pat into pat def") = test { + val pat = pq"x: Int" + assertEqAst(q"val $pat = 2", "{ val x: Int = 2 }") + } +} + trait MethodConstruction { self: QuasiquoteProperties => property("splice paramss into defdef") = test { val paramss = List(q"val x: Int") :: List(q"val y: Int = 1") :: Nil diff --git a/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala b/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala index 589b8d4d72..5d84984514 100644 --- a/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala +++ b/test/files/scalacheck/quasiquotes/QuasiquoteProperties.scala @@ -28,7 +28,7 @@ trait Helpers { override def transform(tree: Tree): Tree = tree match { case Ident(SimplifiedName(name)) => Ident(name) - case ValDef(mods, SimplifiedName(name), tpt, rhs) => ValDef(mods, name, tpt, rhs) + case ValDef(mods, SimplifiedName(name), tpt, rhs) => ValDef(mods, name, transform(tpt), transform(rhs)) case Bind(SimplifiedName(name), rhs) => Bind(name, rhs) case _ => super.transform(tree) -- cgit v1.2.3 From f8d80ea26a03a39fd1d0160a2fad69752712f574 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 31 May 2013 00:22:50 -0700 Subject: SI-3452 Correct Java generic signatures for mixins, static forwarders [Parts of this patch and some of the commentary are from @paulp] This took me so long to figure out I can't even tell you. Partly because there were two different bugs, one which only arose for trait forwarders and one for mirror class forwarders, and every time I'd make one set of tests work another set would start failing. The runtime failures associated with these bugs were fairly well hidden because you usually have to go through java to encounter them: scala doesn't pay that much attention to generic signatures, so they can be wrong and scala might still generate correct code. But java is not so lucky. Bug #1) During mixin composition, classes which extend traits receive forwarders to the implementations. An attempt was made to give these the correct info (in method "cloneBeforeErasure") but it was prone to giving the wrong answer, because: the key attribute which the forwarder must capture is what the underlying method will erase to *where the implementation is*, not how it appears to the class which contains it. That means the signature of the forwarder must be no more precise than the signature of the inherited implementation unless additional measures will be taken. This subtle difference will put on an unsubtle show for you in test run/t3452.scala. trait C[T] trait Search[M] { def search(input: M): C[Int] = null } object StringSearch extends Search[String] { } StringSearch.search("test"); // java // java.lang.NoSuchMethodError: StringSearch.search(Ljava/lang/String;)LC; The principled thing to do here would be to create a pair of methods in the host class: a mixin forwarder with the erased signature `(String)C[Int]`, and a bridge method with the same erased signature as the trait interface facet. But, this turns out to be pretty hard to retrofit onto the current setup of Mixin and Erasure, mostly due to the fact that mixin happens after erasure which has already taken care of bridging. For a future, release, we should try to move all bridging after mixin, and pursue this approach. But for now, what can we do about `LinkageError`s for Java clients? This commit simply checks if the pre-erasure method signature that we generate for the trait forward erases identically to that of the interface method. If so, we can be precise. If not, we emit the erased signature as the generic signature. Bug #2) The same principle is at work, at a different location. During genjvm, objects without declared companion classes are given static forwarders in the corresponding class, e.g. object Foo { def bar = 5 } which creates these classes (taking minor liberties): class Foo$ { static val MODULE$ = new Foo$ ; def bar = 5 } class Foo { static def bar = Foo$.MODULE$.bar } In generating these, genjvm circumvented the usual process whereby one creates a symbol and gives it an info, preferring to target the bytecode directly. However generic signatures are calculated from symbol info (in this case reusing the info from the module class.) Lacking even the attempt which was being made in mixin to "clone before erasure", we would have runtime failures of this kind: abstract class Foo { type T def f(x: T): List[T] = List() } object Bar extends Foo { type T = String } Bar.f(""); // java // java.lang.NoSuchMethodError: Bar.f(Ljava/lang/String;)Lscala/collection/immutable/List; Before/after this commit: < signature f (Ljava/lang/String;)Lscala/collection/immutable/List; --- > signature f (Ljava/lang/Object;)Lscala/collection/immutable/List; This takes the warning count for compiling collections under `-Ycheck:jvm` from 1521 to 26. --- .../scala/tools/nsc/backend/jvm/GenASM.scala | 13 ++- src/compiler/scala/tools/nsc/transform/Mixin.scala | 27 +++-- test/files/neg/t4749.check | 6 +- test/files/pos/t3452f.scala | 10 ++ test/files/run/mixin-signatures.check | 59 +++++++++++ test/files/run/mixin-signatures.scala | 105 +++++++++++++++++++ test/files/run/t3452.check | 1 + test/files/run/t3452.scala | 21 ++++ test/files/run/t3452a.check | 1 + test/files/run/t3452a/J_2.java | 5 + test/files/run/t3452a/S_1.scala | 24 +++++ test/files/run/t3452a/S_3.scala | 5 + test/files/run/t3452b.check | 1 + test/files/run/t3452b/J_2.java | 5 + test/files/run/t3452b/S_1.scala | 10 ++ test/files/run/t3452b/S_3.scala | 5 + test/files/run/t3452c.check | 8 ++ test/files/run/t3452c.scala | 113 +++++++++++++++++++++ test/files/run/t3452d/A.scala | 7 ++ test/files/run/t3452d/Test.java | 12 +++ test/files/run/t3452e/A.scala | 4 + test/files/run/t3452e/B.java | 2 + test/files/run/t3452e/Test.scala | 3 + test/files/run/t3452f.scala | 19 ++++ test/files/run/t3452g/A.scala | 9 ++ test/files/run/t3452g/Test.java | 14 +++ test/files/run/t3452h.scala | 8 ++ test/files/run/t6554.check | 1 + test/files/run/t6554.scala | 11 ++ test/support/java-tests.txt | 97 ++++++++++++++++++ tools/compare-java-sigs | 56 ++++++++++ 31 files changed, 649 insertions(+), 13 deletions(-) create mode 100644 test/files/pos/t3452f.scala create mode 100644 test/files/run/mixin-signatures.check create mode 100644 test/files/run/mixin-signatures.scala create mode 100644 test/files/run/t3452.check create mode 100644 test/files/run/t3452.scala create mode 100644 test/files/run/t3452a.check create mode 100644 test/files/run/t3452a/J_2.java create mode 100644 test/files/run/t3452a/S_1.scala create mode 100644 test/files/run/t3452a/S_3.scala create mode 100644 test/files/run/t3452b.check create mode 100644 test/files/run/t3452b/J_2.java create mode 100644 test/files/run/t3452b/S_1.scala create mode 100644 test/files/run/t3452b/S_3.scala create mode 100644 test/files/run/t3452c.check create mode 100644 test/files/run/t3452c.scala create mode 100644 test/files/run/t3452d/A.scala create mode 100644 test/files/run/t3452d/Test.java create mode 100644 test/files/run/t3452e/A.scala create mode 100644 test/files/run/t3452e/B.java create mode 100644 test/files/run/t3452e/Test.scala create mode 100644 test/files/run/t3452f.scala create mode 100644 test/files/run/t3452g/A.scala create mode 100644 test/files/run/t3452g/Test.java create mode 100644 test/files/run/t3452h.scala create mode 100644 test/files/run/t6554.check create mode 100644 test/files/run/t6554.scala create mode 100644 test/support/java-tests.txt create mode 100644 tools/compare-java-sigs (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index eb40e1dbde..b276572f9b 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -1064,7 +1064,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + val jgensig = ( + // only add generic signature if method concrete; bug #1745 + if (m.isDeferred) null else { + val clazz = module.linkedClassOfClass + val m1 = ( + if ((clazz.info member m.name) eq NoSymbol) + enteringErasure(m.cloneSymbol(clazz, Flags.METHOD | Flags.STATIC)) + else m + ) + getGenericSignature(m1, clazz) + } + ) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 89f9cb4b06..673bc04bd9 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -172,18 +172,23 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // info) as they are seen from the class. We can't use the member that we get from the // implementation class, as it's a clone that was made after erasure, and thus it does not // know its info at the beginning of erasure anymore. - // Optimize: no need if mixinClass has no typeparams. - mixinMember cloneSymbol clazz modifyInfo (info => - if (mixinClass.typeParams.isEmpty) info - else (clazz.thisType baseType mixinClass) memberInfo mixinMember - ) + val sym = mixinMember cloneSymbol clazz + + val erasureMap = erasure.erasure(mixinMember) + val erasedInterfaceInfo: Type = erasureMap(mixinMember.info) + val specificForwardInfo = (clazz.thisType baseType mixinClass) memberInfo mixinMember + val forwarderInfo = + if (erasureMap(specificForwardInfo) =:= erasedInterfaceInfo) + specificForwardInfo + else { + erasedInterfaceInfo + } + // Optimize: no need if mixinClass has no typeparams. + // !!! JZ Really? What about the effect of abstract types, prefix? + if (mixinClass.typeParams.isEmpty) sym + else sym modifyInfo (_ => forwarderInfo) } - // clone before erasure got rid of type info we'll need to generate a javaSig - // now we'll have the type info at (the beginning of) erasure in our history, - // and now newSym has the info that's been transformed to fit this period - // (no need for asSeenFrom as phase.erasedTypes) - // TODO: verify we need the updateInfo and document why - newSym updateInfo (mixinMember.info cloneInfo newSym) + newSym } /** Add getters and setters for all non-module fields of an implementation diff --git a/test/files/neg/t4749.check b/test/files/neg/t4749.check index 63d5c21532..3539140954 100644 --- a/test/files/neg/t4749.check +++ b/test/files/neg/t4749.check @@ -25,6 +25,10 @@ t4749.scala:26: warning: Fail6 has a main method with parameter type Array[Strin object Fail6 { ^ +t4749.scala:42: warning: Win3 has a main method with parameter type Array[String], but bippy.Win3 will not be a runnable program. + Reason: main method must have exact signature (Array[String])Unit + object Win3 extends WinBippy[Unit] { } + ^ error: No warnings can be incurred under -Xfatal-warnings. -6 warnings found +7 warnings found one error found diff --git a/test/files/pos/t3452f.scala b/test/files/pos/t3452f.scala new file mode 100644 index 0000000000..efe25a62fc --- /dev/null +++ b/test/files/pos/t3452f.scala @@ -0,0 +1,10 @@ +class Base[Coll] { + trait Transformed[S] { + lazy val underlying: Coll = ??? + } +} + +class Derived extends Base[String] { + class C extends Transformed[Any] +} + diff --git a/test/files/run/mixin-signatures.check b/test/files/run/mixin-signatures.check new file mode 100644 index 0000000000..3031fe75af --- /dev/null +++ b/test/files/run/mixin-signatures.check @@ -0,0 +1,59 @@ +class Test$bar1$ { + public java.lang.String Test$bar1$.f(java.lang.Object) + public java.lang.Object Test$bar1$.f(java.lang.Object) + public java.lang.String Test$bar1$.g(java.lang.String) + public java.lang.Object Test$bar1$.g(java.lang.Object) + public java.lang.String Test$bar1$.g(java.lang.Object) + public java.lang.Object Test$bar1$.h(java.lang.Object) +} + +class Test$bar2$ { + public java.lang.Object Test$bar2$.f(java.lang.String) + public java.lang.Object Test$bar2$.f(java.lang.Object) + public java.lang.String Test$bar2$.g(java.lang.String) + public java.lang.Object Test$bar2$.g(java.lang.Object) + public java.lang.Object Test$bar2$.g(java.lang.String) + public java.lang.Object Test$bar2$.h(java.lang.Object) +} + +class Test$bar3$ { + public java.lang.String Foo3.f(java.lang.Object) + generic: public java.lang.String Foo3.f(T) + public java.lang.Object Foo3.f(java.lang.Object) + public java.lang.String Test$bar3$.g(java.lang.String) + public java.lang.Object Test$bar3$.g(java.lang.Object) + public java.lang.String Test$bar3$.g(java.lang.Object) + public java.lang.Object Foo3.h(java.lang.Object) +} + +class Test$bar4$ { + public java.lang.Object Foo4.f(java.lang.String) + generic: public R Foo4.f(java.lang.String) + public java.lang.Object Foo4.f(java.lang.Object) + public java.lang.String Test$bar4$.g(java.lang.String) + public java.lang.Object Test$bar4$.g(java.lang.Object) + public java.lang.Object Test$bar4$.g(java.lang.String) + public java.lang.Object Foo4.h(java.lang.Object) +} + +class Test$bar5$ { + public java.lang.String Test$bar5$.f(java.lang.String) + public java.lang.Object Test$bar5$.f(java.lang.Object) + public java.lang.Object Test$bar5$.f(java.lang.String) + public java.lang.String Test$bar5$.f(java.lang.Object) + public java.lang.String Test$bar5$.g(java.lang.String) + public java.lang.Object Test$bar5$.g(java.lang.Object) + public java.lang.Object Test$bar5$.g(java.lang.String) + public java.lang.String Test$bar5$.g(java.lang.Object) + public java.lang.Object Test$bar5$.h(java.lang.Object) +} + +class Foo1$class { + public static java.lang.String Foo1$class.f(Foo1,java.lang.Object) +} + +class Foo2$class { + public static java.lang.Object Foo2$class.f(Foo2,java.lang.String) +} + +000000000000000000000000000000000000 diff --git a/test/files/run/mixin-signatures.scala b/test/files/run/mixin-signatures.scala new file mode 100644 index 0000000000..afd3fad877 --- /dev/null +++ b/test/files/run/mixin-signatures.scala @@ -0,0 +1,105 @@ +trait Base[T, R] { + def f(x: T): R + def g(x: T): R + def h(x: T): R = null.asInstanceOf[R] +} + +trait Foo1[T] extends Base[T, String] { + def f(x: T): String = null + def g(x: T): String +} +trait Foo2[R] extends Base[String, R] { + def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] } + def g(x: String): R +} +abstract class Foo3[T] extends Base[T, String] { + def f(x: T): String = "" + def g(x: T): String +} +abstract class Foo4[R] extends Base[String, R] { + def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] } + def g(x: String): R +} + +object Test { + object bar1 extends Foo1[String] { def g(x: String): String = { print(x.length) ; "" } } + object bar2 extends Foo2[String] { def g(x: String): String = { print(x.length) ; "" } } + object bar3 extends Foo3[String] { def g(x: String): String = { print(x.length) ; "" } } + object bar4 extends Foo4[String] { def g(x: String): String = { print(x.length) ; "" } } + + // Notice that in bar5, f and g require THREE bridges, because the final + // implementation is (String)String, but: + // + // inherited abstract signatures: T(R), (T)String, and (String)R + // which erase to: (Object)Object, (Object)String, and (String)Object + // + // each of which must be bridged to the actual (String)String implementation. + // + // public java.lang.String Test$bar5$.g(java.lang.String) + // public java.lang.Object Test$bar5$.g(java.lang.String) + // public java.lang.Object Test$bar5$.g(java.lang.Object) + // public java.lang.String Test$bar5$.g(java.lang.Object) + object bar5 extends Foo1[String] with Foo2[String] { + override def f(x: String): String = { print(x.length) ; x } + def g(x: String): String = { print(x.length) ; x } + } + + final def m1[T, R](x: Base[T, R], y: T) = { x.f(y) ; x.g(y) ; x.h(y) } + final def m2[T](x: Base[T, String], y: T) = { x.f(y) ; x.g(y) ; x.h(y) } + final def m3[R](x: Base[String, R]) = { x.f("") ; x.g("") ; x.h("") } + final def m4(x: Base[String, String]) = { x.f("") ; x.g("") ; x.h("") } + + final def m11[T](x: Foo1[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) } + final def m12(x: Foo1[String]) = { x.f("") ; x.g("") ; x.h("") } + final def m21[T](x: Foo2[T], y: T) = { x.f("") ; x.g("") ; x.h("") } + final def m22(x: Foo2[String]) = { x.f("") ; x.g("") ; x.h("") } + final def m31[T](x: Foo3[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) } + final def m32(x: Foo3[String]) = { x.f("") ; x.g("") ; x.h("") } + final def m41[T](x: Foo4[T], y: T) = { x.f("") ; x.g("") ; x.h("") } + final def m42(x: Foo4[String]) = { x.f("") ; x.g("") ; x.h("") } + + def go = { + m1(bar1, "") ; m2(bar1, "") ; m3(bar1) ; m4(bar1) + m1(bar2, "") ; m2(bar2, "") ; m3(bar2) ; m4(bar2) + m1(bar3, "") ; m2(bar3, "") ; m3(bar3) ; m4(bar3) + m1(bar4, "") ; m2(bar4, "") ; m3(bar4) ; m4(bar4) + + m11(bar1, "") ; m12(bar1) + m21(bar2, "") ; m22(bar2) + m31(bar3, "") ; m32(bar3) + m41(bar4, "") ; m42(bar4) + "" + } + + def flagsString(m: java.lang.reflect.Method) = { + val str = List( + if (m.isBridge) "" else "", + if (m.isSynthetic) "" else "" + ) filterNot (_ == "") mkString " " + + if (str == "") "" else " " + str + // + // val flags = scala.reflect.internal.ClassfileConstants.toScalaMethodFlags(m.getModifiers()) + // scala.tools.nsc.symtab.Flags.flagsToString(flags) + } + + def show(clazz: Class[_]) { + print(clazz + " {") + clazz.getMethods.sortBy(x => (x.getName, x.isBridge, x.toString)) filter (_.getName.length == 1) foreach { m => + print("\n " + m + flagsString(m)) + if ("" + m != "" + m.toGenericString) { + print("\n generic: " + m.toGenericString) + } + } + println("\n}") + println("") + } + def show(x: AnyRef) { show(x.getClass) } + def show(x: String) { show(Class.forName(x)) } + + def main(args: Array[String]): Unit = { + List(bar1, bar2, bar3, bar4, bar5) foreach show + List("Foo1$class", "Foo2$class") foreach show + println(go) + } +} \ No newline at end of file diff --git a/test/files/run/t3452.check b/test/files/run/t3452.check new file mode 100644 index 0000000000..b8626c4cff --- /dev/null +++ b/test/files/run/t3452.check @@ -0,0 +1 @@ +4 diff --git a/test/files/run/t3452.scala b/test/files/run/t3452.scala new file mode 100644 index 0000000000..253fc93cfa --- /dev/null +++ b/test/files/run/t3452.scala @@ -0,0 +1,21 @@ +trait IStringPair[T] { + def a : String + def b : String + def build(a : String, b : String) : T + def cat(that : IStringPair[T]) = build(this.a + that.a, this.b + that.b) + override def toString = a + b +} + +class StringPair(val a : String, val b : String) extends IStringPair[StringPair] { + def build(a : String, b : String) = new StringPair(a, b) + def len = a.length + b.length +} + +object Test { + def main(args: Array[String]): Unit = { + val a = new StringPair("A", "B") + val b = new StringPair("1", "2") + val c = a cat b + println(c.len) + } +} diff --git a/test/files/run/t3452a.check b/test/files/run/t3452a.check new file mode 100644 index 0000000000..9ff787eb86 --- /dev/null +++ b/test/files/run/t3452a.check @@ -0,0 +1 @@ +BulkSearch.searchFor called. diff --git a/test/files/run/t3452a/J_2.java b/test/files/run/t3452a/J_2.java new file mode 100644 index 0000000000..62057ffe61 --- /dev/null +++ b/test/files/run/t3452a/J_2.java @@ -0,0 +1,5 @@ +public class J_2 { + public static void main(String[] args) { + BulkSearchInstance.searchFor(new UpRelation()); + } +} diff --git a/test/files/run/t3452a/S_1.scala b/test/files/run/t3452a/S_1.scala new file mode 100644 index 0000000000..791faf42fa --- /dev/null +++ b/test/files/run/t3452a/S_1.scala @@ -0,0 +1,24 @@ +abstract class BulkSearch { + type R <: Row + type Rel <: Relation [R] + type Corr <: Correspondence[R] + + def searchFor(input: Rel): Mapping[Corr] = { println("BulkSearch.searchFor called.") ; null } +} + +object BulkSearchInstance extends BulkSearch { + type R = UpRow + type Rel = UpRelation + type Corr = UpCorrespondence +} + +class Row +class UpRow extends Row + +class Relation [R <: Row] +class UpRelation extends Relation [UpRow] + +class Correspondence [R <: Row] +class UpCorrespondence extends Correspondence [UpRow] + +class Mapping[MC <: Correspondence[_]] diff --git a/test/files/run/t3452a/S_3.scala b/test/files/run/t3452a/S_3.scala new file mode 100644 index 0000000000..aaa898dcde --- /dev/null +++ b/test/files/run/t3452a/S_3.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + J_2.main(args) + } +} diff --git a/test/files/run/t3452b.check b/test/files/run/t3452b.check new file mode 100644 index 0000000000..4f9ed634b4 --- /dev/null +++ b/test/files/run/t3452b.check @@ -0,0 +1 @@ +Search received: test diff --git a/test/files/run/t3452b/J_2.java b/test/files/run/t3452b/J_2.java new file mode 100644 index 0000000000..24c27b88bd --- /dev/null +++ b/test/files/run/t3452b/J_2.java @@ -0,0 +1,5 @@ +public class J_2 { + public static void j() { + StringSearch.search("test"); + } +} diff --git a/test/files/run/t3452b/S_1.scala b/test/files/run/t3452b/S_1.scala new file mode 100644 index 0000000000..3ab19ed031 --- /dev/null +++ b/test/files/run/t3452b/S_1.scala @@ -0,0 +1,10 @@ +trait Search[M] { + def search(input: M): C[Int] = { + println("Search received: " + input) + null + } +} + +object StringSearch extends Search[String] + +trait C[T] diff --git a/test/files/run/t3452b/S_3.scala b/test/files/run/t3452b/S_3.scala new file mode 100644 index 0000000000..102b433f47 --- /dev/null +++ b/test/files/run/t3452b/S_3.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + J_2.j() + } +} diff --git a/test/files/run/t3452c.check b/test/files/run/t3452c.check new file mode 100644 index 0000000000..ab47181198 --- /dev/null +++ b/test/files/run/t3452c.check @@ -0,0 +1,8 @@ +3 +3 +3 +3 +3 +3 +3 +3 diff --git a/test/files/run/t3452c.scala b/test/files/run/t3452c.scala new file mode 100644 index 0000000000..2c55767abc --- /dev/null +++ b/test/files/run/t3452c.scala @@ -0,0 +1,113 @@ +trait Base[A, B, C] { + def f(x: A, y: B, z: C): Unit + def g(x: A, y: B, z: C) = f(x, y, z) + def h(x: A, y: B, z: C) = g(x, y, z) +} + +trait D1[B, C] extends Base[String, B, C] +trait D2[A, B] extends Base[A, B, String] +trait D3[A, C] extends Base[A, String, C] +trait D4[A] extends Base[A, String, String] +trait D5[B] extends Base[String, B, String] +trait D6[C] extends Base[String, String, C] +trait D7 extends Base[String, String, String] + +trait E1[B, C] extends Base[String, B, C] { def f(x: String, y: B, z: C): Unit ; override def h(x: String, y: B, z: C) = g(x, y, z) } +trait E2[A, B] extends Base[A, B, String] { def f(x: A, y: B, z: String): Unit ; override def h(x: A, y: B, z: String) = g(x, y, z) } +trait E3[A, C] extends Base[A, String, C] { def f(x: A, y: String, z: C): Unit ; override def h(x: A, y: String, z: C) = g(x, y, z) } +trait E4[A] extends Base[A, String, String] { def f(x: A, y: String, z: String): Unit ; override def h(x: A, y: String, z: String) = g(x, y, z) } +trait E5[B] extends Base[String, B, String] { def f(x: String, y: B, z: String): Unit ; override def h(x: String, y: B, z: String) = g(x, y, z) } +trait E6[C] extends Base[String, String, C] { def f(x: String, y: String, z: C): Unit ; override def h(x: String, y: String, z: C) = g(x, y, z) } +trait E7 extends Base[String, String, String] { def f(x: String, y: String, z: String): Unit ; override def h(x: String, y: String, z: String) = g(x, y, z) } + +trait F1[B, C] extends Base[String, B, C] { def f(x: String, y: B, z: C): Unit = println(x.length) } +trait F2[A, B] extends Base[A, B, String] { def f(x: A, y: B, z: String): Unit = println(z.length) } +trait F3[A, C] extends Base[A, String, C] { def f(x: A, y: String, z: C): Unit = println(y.length) } +trait F4[A] extends Base[A, String, String] { def f(x: A, y: String, z: String): Unit = println(y.length) } +trait F5[B] extends Base[String, B, String] { def f(x: String, y: B, z: String): Unit = println(x.length) } +trait F6[C] extends Base[String, String, C] { def f(x: String, y: String, z: C): Unit = println(x.length) } +trait F7 extends Base[String, String, String] { def f(x: String, y: String, z: String): Unit = println(x.length) } + +abstract class DBag extends D1[String, String] with D2[String, String] with D3[String, String] with D4[String] with D5[String] with D6[String] with D7 { + def f(x: String, y: String, z: String) = println(x.length + y.length + z.length) +} +abstract class EBag extends E1[String, String] with E2[String, String] with E3[String, String] with E4[String] with E5[String] with E6[String] with E7 { + def f(x: String, y: String, z: String) = println(x.length + y.length + z.length) +} +abstract class FBag extends F1[String, String] with F2[String, String] with F3[String, String] with F4[String] with F5[String] with F6[String] with F7 { + override def f(x: String, y: String, z: String) = println(x.length + y.length + z.length) +} + +abstract class GBag1[A, B] extends Base[A, B, String] with D2[A, B] { + def f(x: A, y: B, z: String) = println(z.length) +} +abstract class GBag2[A] extends GBag1[A, String] with D4[A] { + override def f(x: A, y: String, z: String) = println(z.length) +} +abstract class GBag3 extends GBag2[String] with D7 { + override def f(x: String, y: String, z: String) = println(z.length) +} +class GBag extends GBag3 with D2[String, String] with D3[String, String] with D4[String] with D5[String] with D6[String] with D7 { +} + +object Test { + def f0(x: Base[String, String, String]) = x.f("a", "b", "c") + def f1(x: D1[String, String]) = x.f("a", "b", "c") + def f2(x: D2[String, String]) = x.f("a", "b", "c") + def f3(x: D3[String, String]) = x.f("a", "b", "c") + def f4(x: D4[String]) = x.f("a", "b", "c") + def f5(x: D5[String]) = x.f("a", "b", "c") + def f6(x: D6[String]) = x.f("a", "b", "c") + def f7(x: D7) = x.f("a", "b", "c") + + def main(args: Array[String]): Unit = { + val x = new DBag { } + f0(x) + f1(x) + f2(x) + f3(x) + f4(x) + f5(x) + f6(x) + f7(x) + } +} + +object TestE { + def f0(x: Base[String, String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f1(x: E1[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f2(x: E2[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f3(x: E3[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f4(x: E4[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f5(x: E5[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f6(x: E6[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f7(x: E7) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + + def main(args: Array[String]): Unit = { + val x = new EBag { } + f0(x) + f1(x) + f2(x) + f3(x) + f4(x) + f5(x) + f6(x) + f7(x) + } +} + + +object TestG { + def f0(x: Base[String, String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f1(x: GBag1[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f2(x: GBag2[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f3(x: GBag3) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + + def main(args: Array[String]): Unit = { + val x = new GBag { } + f0(x) + f1(x) + f2(x) + f3(x) + } +} diff --git a/test/files/run/t3452d/A.scala b/test/files/run/t3452d/A.scala new file mode 100644 index 0000000000..67a2080d27 --- /dev/null +++ b/test/files/run/t3452d/A.scala @@ -0,0 +1,7 @@ +trait TraversableLike[A, Repr] { + def tail: Repr = null.asInstanceOf[Repr] +} + +abstract class AbstractTrav[A] extends TraversableLike[A, Traversable[A]] + +class C[A] extends AbstractTrav[A] diff --git a/test/files/run/t3452d/Test.java b/test/files/run/t3452d/Test.java new file mode 100644 index 0000000000..875be6176c --- /dev/null +++ b/test/files/run/t3452d/Test.java @@ -0,0 +1,12 @@ +import scala.collection.immutable.Nil; +import scala.collection.immutable.List; +import scala.collection.Traversable; + +public class Test { + public static void main(String[] args) { + C c = new C(); + // TODO add a bridge during mixin so we can expose + // sharper generic signature for `tail`. + /*Traversable*/ Object ls = c.tail(); + } +} diff --git a/test/files/run/t3452e/A.scala b/test/files/run/t3452e/A.scala new file mode 100644 index 0000000000..939172f401 --- /dev/null +++ b/test/files/run/t3452e/A.scala @@ -0,0 +1,4 @@ +trait F1[T, R] { + def andThen[A](g: R => A): Int = 0 +} +class C1[TT, RR] extends F1[TT, RR] diff --git a/test/files/run/t3452e/B.java b/test/files/run/t3452e/B.java new file mode 100644 index 0000000000..0268af9987 --- /dev/null +++ b/test/files/run/t3452e/B.java @@ -0,0 +1,2 @@ +class B extends C1 { +} diff --git a/test/files/run/t3452e/Test.scala b/test/files/run/t3452e/Test.scala new file mode 100644 index 0000000000..fc175bf94a --- /dev/null +++ b/test/files/run/t3452e/Test.scala @@ -0,0 +1,3 @@ +object Test extends App { + new B +} diff --git a/test/files/run/t3452f.scala b/test/files/run/t3452f.scala new file mode 100644 index 0000000000..af64f5c042 --- /dev/null +++ b/test/files/run/t3452f.scala @@ -0,0 +1,19 @@ +import language.higherKinds + +trait GenSet[A] + +trait GenSetTemplate[A, +CC[X] <: GenSet[X]] { + def empty: CC[A] = ??? +} + +trait SetLike[A, +This <: SetLike[A, This] with Set[A]] { + def empty: This +} + +abstract class Set[A] extends GenSet[A] with SetLike[A,Set[A]] with GenSetTemplate[A,Set] + +object Test { + def main(args: Array[String]): Unit = { + locally(classOf[Set[_]]) // trigger classloading to verify class + } +} diff --git a/test/files/run/t3452g/A.scala b/test/files/run/t3452g/A.scala new file mode 100644 index 0000000000..a3f74c1e1e --- /dev/null +++ b/test/files/run/t3452g/A.scala @@ -0,0 +1,9 @@ +trait TraversableLike[A, Repr] { + def tail: Repr = null.asInstanceOf[Repr] +} + +abstract class AbstractTrav[A] extends TraversableLike[A, AbstractTrav[A]] + +object O extends AbstractTrav[String] + +class C[A] extends AbstractTrav[A] diff --git a/test/files/run/t3452g/Test.java b/test/files/run/t3452g/Test.java new file mode 100644 index 0000000000..c3b4222d16 --- /dev/null +++ b/test/files/run/t3452g/Test.java @@ -0,0 +1,14 @@ + +public class Test { + public static void main(String[] args) { + // To get better types here, we would need to + // add bridge during mixin so we can expose + // a generic return type of Traversable, because the erasure + // of this (Traversable) differs from the erasure of the mixed + // method (erasure(Repr) = Object) + + Object lsSharp = O.tail(); + + Object lsSharp2 = new C().tail(); + } +} diff --git a/test/files/run/t3452h.scala b/test/files/run/t3452h.scala new file mode 100644 index 0000000000..6237d3ea64 --- /dev/null +++ b/test/files/run/t3452h.scala @@ -0,0 +1,8 @@ +class Mix___eFoo_I_wBar__f extends Foo_I_ with Bar__f { f; } +trait T +abstract class Foo_I_ { class I extends T ; def f: I ; f; } +trait Bar__f { type I>:Null<:T; def f: I = {null}; f; def gobble: I = {null}} + +object Test extends App { + new Mix___eFoo_I_wBar__f +} diff --git a/test/files/run/t6554.check b/test/files/run/t6554.check new file mode 100644 index 0000000000..6e0af7b474 --- /dev/null +++ b/test/files/run/t6554.check @@ -0,0 +1 @@ +public java.lang.Object Bar.minBy(java.lang.Object) / public java.lang.Object Bar.minBy(java.lang.Object) diff --git a/test/files/run/t6554.scala b/test/files/run/t6554.scala new file mode 100644 index 0000000000..5d29d16666 --- /dev/null +++ b/test/files/run/t6554.scala @@ -0,0 +1,11 @@ +trait Foo[A] { + def minBy[B](b: B): A = ??? +} + +class Bar extends Foo[Int] + +object Test extends App { + val sigs = classOf[Bar].getDeclaredMethods.map(m => s"${m.toString} / ${m.toGenericString}").sorted + println(sigs.mkString("\n")) +} +// Was public java.lang.Object Bar.minBy(java.lang.Object) / public int Bar.minBy(B) diff --git a/test/support/java-tests.txt b/test/support/java-tests.txt new file mode 100644 index 0000000000..e0a3fddab3 --- /dev/null +++ b/test/support/java-tests.txt @@ -0,0 +1,97 @@ +test/files/buildmanager/t2280 +test/files/buildmanager/t3045 +test/files/buildmanager/t3133 +test/files/jvm/deprecation +test/files/jvm/t1143-2 +test/files/jvm/t1342 +test/files/jvm/t1464 +test/files/jvm/t2470 +test/files/jvm/t2570 +test/files/jvm/t2585 +test/files/jvm/t3003 +test/files/jvm/t3415 +test/files/jvm/ticket2163 +test/files/jvm/ticket4283 +test/files/jvm/varargs +test/files/neg/abstract-class-error +test/files/neg/java-access-neg +test/files/neg/primitive-sigs-1 +test/files/neg/protected-static-fail +test/files/neg/t0673 +test/files/neg/t1548 +test/files/neg/t3663 +test/files/neg/t3757 +test/files/neg/t4851 +test/files/pos/chang +test/files/pos/ilya +test/files/pos/ilya2 +test/files/pos/java-access-pos +test/files/pos/javaReadsSigs +test/files/pos/protected-static +test/files/pos/raw-map +test/files/pos/signatures +test/files/pos/super +test/files/pos/t0288 +test/files/pos/t0695 +test/files/pos/t1101 +test/files/pos/t1102 +test/files/pos/t1150 +test/files/pos/t1152 +test/files/pos/t1176 +test/files/pos/t1186 +test/files/pos/t1196 +test/files/pos/t1197 +test/files/pos/t1203 +test/files/pos/t1230 +test/files/pos/t1231 +test/files/pos/t1232 +test/files/pos/t1235 +test/files/pos/t1254 +test/files/pos/t1263 +test/files/pos/t1409 +test/files/pos/t1459 +test/files/pos/t1642 +test/files/pos/t1711 +test/files/pos/t1745 +test/files/pos/t1751 +test/files/pos/t1782 +test/files/pos/t1836 +test/files/pos/t1840 +test/files/pos/t1937 +test/files/pos/t2377 +test/files/pos/t2409 +test/files/pos/t2413 +test/files/pos/t2433 +test/files/pos/t2464 +test/files/pos/t2569 +test/files/pos/t2868 +test/files/pos/t294 +test/files/pos/t2940 +test/files/pos/t2956 +test/files/pos/t3249 +test/files/pos/t3349 +test/files/pos/t3404 +test/files/pos/t3429 +test/files/pos/t3486 +test/files/pos/t3521 +test/files/pos/t3567 +test/files/pos/t3622 +test/files/pos/t3642 +test/files/pos/t3938 +test/files/pos/t3946 +test/files/pos/t4402 +test/files/pos/t4603 +test/files/pos/t4737 +test/files/pos/t5644 +test/files/pos/t5703 +test/files/run/inner-parse +test/files/run/t1430 +test/files/run/t2296a +test/files/run/t2296b +test/files/run/t3452a +test/files/run/t3452b +test/files/run/t3897 +test/files/run/t4119 +test/files/run/t4238 +test/files/run/t4317 +test/files/run/t4891 diff --git a/tools/compare-java-sigs b/tools/compare-java-sigs new file mode 100644 index 0000000000..99ab775437 --- /dev/null +++ b/tools/compare-java-sigs @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Compare javac -Xprint (i.e. see signatures from java point of view) +# for the given classes. +# +# Sample: +# +# % SCALA_HOME=/scala/inst/29 SCALA_BUILD=/scala/inst/3 tools/compare-java-sigs 'scala.Predef$' +# +# Comparing javac -Xprint for scala.Predef$ based on '/scala/inst/29' and '/scala/inst/3' +# 3c3 +# < public final class Predef$ extends scala.LowPriorityImplicits implements scala.ScalaObject { +# --- +# > public final class Predef$ extends scala.LowPriorityImplicits { +# 7d6 +# < private final scala.SpecializableCompanion AnyRef; +# 21,22d19 +# < public scala.SpecializableCompanion AnyRef(); +# < +# 68a66,67 +# > public scala.runtime.Nothing$ $qmark$qmark$qmark(); +# > +# 225c224,226 +# < public scala.collection.immutable.StringOps augmentString(java.lang.String x); +# --- +# > public scala.runtime.StringFormat any2stringfmt(java.lang.Object x); +# > +# > public java.lang.String augmentString(java.lang.String x); +# 227c228 +# < public java.lang.String unaugmentString(scala.collection.immutable.StringOps x); +# --- +# > public java.lang.String unaugmentString(java.lang.String x); +# + +set -e + +[[ $# -gt 0 ]] || { + echo "Usage: $(basename $0) ..." + echo "" + echo "# Example usage" + echo "SCALA_HOME=/scala/inst/29 SCALA_BUILD=/scala/inst/3 \\" + echo " $(basename $0) scala.Function1 scala.runtime.AbstractFunction1" + exit 0 +} + +home1=$(cd ${SCALA_HOME:-/scala/inst/3} && pwd) +home2=$(cd ${SCALA_BUILD:-$(dirname $BASH_SOURCE)/../build/pack} && pwd) + +echo "Comparing javac -Xprint for $@ based on '$home1' and '$home2'" +tmpdir=$(mktemp -dt $(basename $BASH_SOURCE)) + +cd $tmpdir +javac -Xprint -cp $home1:$home1/lib/'*' "$@" > before.txt +javac -Xprint -cp $home2:$home2/lib/'*' "$@" > after.txt + +diff before.txt after.txt && echo "No differences in javac -Xprint output." -- cgit v1.2.3 From 3900e4bc7f20e453b8f09fa25ade3e39e60f00e2 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 21 Jan 2014 16:03:25 +0300 Subject: standardizes prefixes used in named/default desugaring --- src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala | 4 ++-- src/reflect/scala/reflect/internal/StdNames.scala | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 6a4df415ae..dceb0a47d8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -162,7 +162,7 @@ trait NamesDefaults { self: Analyzer => // never used for constructor calls, they always have a stable qualifier def blockWithQualifier(qual: Tree, selected: Name) = { - val sym = blockTyper.context.owner.newValue(unit.freshTermName("qual$"), newFlags = ARTIFACT) setInfo uncheckedBounds(qual.tpe) setPos (qual.pos.makeTransparent) + val sym = blockTyper.context.owner.newValue(unit.freshTermName(nme.QUAL_PREFIX), newFlags = ARTIFACT) setInfo uncheckedBounds(qual.tpe) setPos (qual.pos.makeTransparent) blockTyper.context.scope enter sym val vd = atPos(sym.pos)(ValDef(sym, qual) setType NoType) // it stays in Vegas: SI-5720, SI-5727 @@ -292,7 +292,7 @@ trait NamesDefaults { self: Analyzer => arg.tpe } ).widen // have to widen or types inferred from literal defaults will be singletons - val s = context.owner.newValue(unit.freshTermName(), arg.pos, newFlags = ARTIFACT) setInfo { + val s = context.owner.newValue(unit.freshTermName(nme.NAMEDARG_PREFIX), arg.pos, newFlags = ARTIFACT) setInfo { val tp = if (byName) functionType(Nil, argTpe) else argTpe uncheckedBounds(tp) } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 679186f938..970494f3d2 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -312,6 +312,8 @@ trait StdNames { val WHILE_PREFIX = "while$" val FRESH_PREFIX = "fresh" val FRESH_SUFFIX = "macro$" // uses a keyword to avoid collisions with mangled names + val QUAL_PREFIX = "qual$" + val NAMEDARG_PREFIX = "x$" // Compiler internal names val ANYname: NameType = "" -- cgit v1.2.3 From 9eead7fdcec4c23df0276e004bd6b91297f8b18c Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 21 Jan 2014 16:28:25 +0300 Subject: standardizes checks for default getters --- src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala | 3 +-- src/reflect/scala/reflect/internal/Symbols.scala | 2 ++ src/reflect/scala/reflect/internal/TreeInfo.scala | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index b801b644fb..71e6454931 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -525,7 +525,6 @@ trait TypeDiagnostics { val unused = p.unusedTerms unused foreach { defn: DefTree => val sym = defn.symbol - val isDefaultGetter = sym.name containsName nme.DEFAULT_GETTER_STRING val pos = ( if (defn.pos.isDefined) defn.pos else if (sym.pos.isDefined) sym.pos @@ -536,7 +535,7 @@ trait TypeDiagnostics { ) val why = if (sym.isPrivate) "private" else "local" val what = ( - if (isDefaultGetter) "default argument" + if (sym.isDefaultGetter) "default argument" else if (sym.isConstructor) "constructor" else if (sym.isVar || sym.isGetter && sym.accessed.isVar) "var" else if (sym.isVal || sym.isGetter && sym.accessed.isVal) "val" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 2969bd92de..7638380c41 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -556,6 +556,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => def isConstructor = false def isEarlyInitialized = false def isGetter = false + def isDefaultGetter = false def isLocalDummy = false def isMixinConstructor = false def isOverloaded = false @@ -2644,6 +2645,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def isSetterParameter = isValueParameter && owner.isSetter override def isAccessor = this hasFlag ACCESSOR override def isGetter = isAccessor && !isSetter + override def isDefaultGetter = name containsName nme.DEFAULT_GETTER_STRING override def isSetter = isAccessor && nme.isSetterName(name) // todo: make independent of name, as this can be forged. override def isLocalDummy = nme.isLocalDummyName(name) override def isClassConstructor = name == nme.CONSTRUCTOR diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 7bab15b0f4..1eb0743bc8 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -281,6 +281,10 @@ abstract class TreeInfo { } } + def isDefaultGetter(tree: Tree) = { + tree.symbol != null && tree.symbol.isDefaultGetter + } + /** Is tree a self constructor call this(...)? I.e. a call to a constructor of the * same object? */ -- cgit v1.2.3 From d2a1dd59c264947137669f4dbda20d89b26743af Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 22 Jan 2014 14:01:01 +0300 Subject: introduces -Yshow-symowners This facility, along with -Yshow-syms, has proven to be very useful when debugging problems caused by corrupt owner chains when hacking on named/default argument transformation. --- src/compiler/scala/tools/nsc/ast/Printers.scala | 6 +-- .../scala/tools/nsc/settings/ScalaSettings.scala | 1 + .../scala/tools/reflect/ToolBoxFactory.scala | 17 ++++---- src/reflect/scala/reflect/api/Printers.scala | 16 ++++--- src/reflect/scala/reflect/internal/Printers.scala | 49 ++++++++++++---------- src/reflect/scala/reflect/internal/Symbols.scala | 7 +++- .../internal/settings/MutableSettings.scala | 1 + src/reflect/scala/reflect/runtime/Settings.scala | 1 + 8 files changed, 58 insertions(+), 40 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/ast/Printers.scala b/src/compiler/scala/tools/nsc/ast/Printers.scala index c64b18207a..f3def3c80c 100644 --- a/src/compiler/scala/tools/nsc/ast/Printers.scala +++ b/src/compiler/scala/tools/nsc/ast/Printers.scala @@ -178,9 +178,9 @@ trait Printers extends scala.reflect.internal.Printers { this: Global => } } - def asString(t: Tree): String = render(t, newStandardTreePrinter, settings.printtypes, settings.uniqid, settings.Yshowsymkinds) - def asCompactString(t: Tree): String = render(t, newCompactTreePrinter, settings.printtypes, settings.uniqid, settings.Yshowsymkinds) - def asCompactDebugString(t: Tree): String = render(t, newCompactTreePrinter, true, true, true) + def asString(t: Tree): String = render(t, newStandardTreePrinter, settings.printtypes, settings.uniqid, settings.Yshowsymowners, settings.Yshowsymkinds) + def asCompactString(t: Tree): String = render(t, newCompactTreePrinter, settings.printtypes, settings.uniqid, settings.Yshowsymowners, settings.Yshowsymkinds) + def asCompactDebugString(t: Tree): String = render(t, newCompactTreePrinter, true, true, true, true) def newStandardTreePrinter(writer: PrintWriter): TreePrinter = new TreePrinter(writer) def newCompactTreePrinter(writer: PrintWriter): CompactTreePrinter = new CompactTreePrinter(writer) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a3114a3d7b..a385a31165 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -168,6 +168,7 @@ trait ScalaSettings extends AbsScalaSettings = BooleanSetting ("-Yshow-trees-stringified", "(Requires -Xprint:) Print stringifications along with detailed ASTs.") val Yshowsyms = BooleanSetting ("-Yshow-syms", "Print the AST symbol hierarchy after each phase.") val Yshowsymkinds = BooleanSetting ("-Yshow-symkinds", "Print abbreviated symbol kinds next to symbol names.") + val Yshowsymowners = BooleanSetting ("-Yshow-symowners", "Print owner identifiers next to symbol names.") val skip = PhasesSetting ("-Yskip", "Skip") val Ygenjavap = StringSetting ("-Ygen-javap", "dir", "Generate a parallel output directory of .javap files.", "") val Ygenasmp = StringSetting ("-Ygen-asmp", "dir", "Generate a parallel output directory of .asmp files (ie ASM Textifier output).", "") diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index 541a915adb..e7927e2d37 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -174,7 +174,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => def typecheck(expr: Tree, pt: Type, silent: Boolean, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean): Tree = transformDuringTyper(expr, withImplicitViewsDisabled = withImplicitViewsDisabled, withMacrosDisabled = withMacrosDisabled)( (currentTyper, expr) => { - trace("typing (implicit views = %s, macros = %s): ".format(!withImplicitViewsDisabled, !withMacrosDisabled))(showAttributed(expr, true, true, settings.Yshowsymkinds.value)) + trace("typing (implicit views = %s, macros = %s): ".format(!withImplicitViewsDisabled, !withMacrosDisabled))(showAttributed(expr, true, true, settings.Yshowsymowners.value, settings.Yshowsymkinds.value)) currentTyper.silent(_.typed(expr, pt), reportAmbiguousErrors = false) match { case analyzer.SilentResultValue(result) => trace("success: ")(showAttributed(result, true, true, settings.Yshowsymkinds.value)) @@ -189,7 +189,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => def inferImplicit(tree: Tree, pt: Type, isView: Boolean, silent: Boolean, withMacrosDisabled: Boolean, pos: Position): Tree = transformDuringTyper(tree, withImplicitViewsDisabled = false, withMacrosDisabled = withMacrosDisabled)( (currentTyper, tree) => { - trace("inferring implicit %s (macros = %s): ".format(if (isView) "view" else "value", !withMacrosDisabled))(showAttributed(pt, true, true, settings.Yshowsymkinds.value)) + trace("inferring implicit %s (macros = %s): ".format(if (isView) "view" else "value", !withMacrosDisabled))(showAttributed(pt, true, true, settings.Yshowsymowners.value, settings.Yshowsymkinds.value)) analyzer.inferImplicit(tree, pt, isView, currentTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw ToolBoxError(msg)) }) @@ -234,10 +234,10 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => List(), List(methdef), NoPosition)) - trace("wrapped: ")(showAttributed(moduledef, true, true, settings.Yshowsymkinds.value)) + trace("wrapped: ")(showAttributed(moduledef, true, true, settings.Yshowsymowners.value, settings.Yshowsymkinds.value)) val cleanedUp = resetAttrs(moduledef) - trace("cleaned up: ")(showAttributed(cleanedUp, true, true, settings.Yshowsymkinds.value)) + trace("cleaned up: ")(showAttributed(cleanedUp, true, true, settings.Yshowsymowners.value, settings.Yshowsymkinds.value)) cleanedUp.asInstanceOf[ModuleDef] } @@ -285,19 +285,22 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => tree } - def showAttributed(artifact: Any, printTypes: Boolean = true, printIds: Boolean = true, printKinds: Boolean = false): String = { + def showAttributed(artifact: Any, printTypes: Boolean = true, printIds: Boolean = true, printOwners: Boolean = false, printKinds: Boolean = false): String = { val saved1 = settings.printtypes.value val saved2 = settings.uniqid.value - val saved3 = settings.Yshowsymkinds.value + val saved3 = settings.Yshowsymowners.value + val saved4 = settings.Yshowsymkinds.value try { settings.printtypes.value = printTypes settings.uniqid.value = printIds + settings.Yshowsymowners.value = printOwners settings.Yshowsymkinds.value = printKinds artifact.toString } finally { settings.printtypes.value = saved1 settings.uniqid.value = saved2 - settings.Yshowsymkinds.value = saved3 + settings.Yshowsymowners.value = saved3 + settings.Yshowsymkinds.value = saved4 } } diff --git a/src/reflect/scala/reflect/api/Printers.scala b/src/reflect/scala/reflect/api/Printers.scala index 5bc92d3893..637fcd782e 100644 --- a/src/reflect/scala/reflect/api/Printers.scala +++ b/src/reflect/scala/reflect/api/Printers.scala @@ -142,6 +142,7 @@ trait Printers { self: Universe => def print(args: Any*) protected var printTypes = false protected var printIds = false + protected var printOwners = false protected var printKinds = false protected var printMirrors = false protected var printPositions = false @@ -149,6 +150,8 @@ trait Printers { self: Universe => def withoutTypes: this.type = { printTypes = false; this } def withIds: this.type = { printIds = true; this } def withoutIds: this.type = { printIds = false; this } + def withOwners: this.type = { printOwners = true; this } + def withoutOwners: this.type = { printOwners = false; this } def withKinds: this.type = { printKinds = true; this } def withoutKinds: this.type = { printKinds = false; this } def withMirrors: this.type = { printMirrors = true; this } @@ -169,12 +172,13 @@ trait Printers { self: Universe => } /** @group Printers */ - protected def render(what: Any, mkPrinter: PrintWriter => TreePrinter, printTypes: BooleanFlag = None, printIds: BooleanFlag = None, printKinds: BooleanFlag = None, printMirrors: BooleanFlag = None, printPositions: BooleanFlag = None): String = { + protected def render(what: Any, mkPrinter: PrintWriter => TreePrinter, printTypes: BooleanFlag = None, printIds: BooleanFlag = None, printOwners: BooleanFlag = None, printKinds: BooleanFlag = None, printMirrors: BooleanFlag = None, printPositions: BooleanFlag = None): String = { val buffer = new StringWriter() val writer = new PrintWriter(buffer) val printer = mkPrinter(writer) printTypes.value.map(printTypes => if (printTypes) printer.withTypes else printer.withoutTypes) printIds.value.map(printIds => if (printIds) printer.withIds else printer.withoutIds) + printOwners.value.map(printOwners => if (printOwners) printer.withOwners else printer.withoutOwners) printKinds.value.map(printKinds => if (printKinds) printer.withKinds else printer.withoutKinds) printMirrors.value.map(printMirrors => if (printMirrors) printer.withMirrors else printer.withoutMirrors) printPositions.value.map(printPositions => if (printPositions) printer.withPositions else printer.withoutPositions) @@ -193,8 +197,8 @@ trait Printers { self: Universe => * * @group Printers */ - def show(any: Any, printTypes: BooleanFlag = None, printIds: BooleanFlag = None, printKinds: BooleanFlag = None, printMirrors: BooleanFlag = None, printPositions: BooleanFlag = None): String = - render(any, newTreePrinter(_), printTypes, printIds, printKinds, printMirrors, printPositions) + def show(any: Any, printTypes: BooleanFlag = None, printIds: BooleanFlag = None, printOwners: BooleanFlag = None, printKinds: BooleanFlag = None, printMirrors: BooleanFlag = None, printPositions: BooleanFlag = None): String = + render(any, newTreePrinter(_), printTypes, printIds, printOwners, printKinds, printMirrors, printPositions) /** Hook to define what `show(...)` means. * @group Printers @@ -219,14 +223,14 @@ trait Printers { self: Universe => * @group Printers */ protected def newCodePrinter(out: PrintWriter): TreePrinter - + /** Renders internal structure of a reflection artifact as the * visualization of a Scala syntax tree. * * @group Printers */ - def showRaw(any: Any, printTypes: BooleanFlag = None, printIds: BooleanFlag = None, printKinds: BooleanFlag = None, printMirrors: BooleanFlag = None, printPositions: BooleanFlag = None): String = - render(any, newRawTreePrinter(_), printTypes, printIds, printKinds, printMirrors, printPositions) + def showRaw(any: Any, printTypes: BooleanFlag = None, printIds: BooleanFlag = None, printOwners: BooleanFlag = None, printKinds: BooleanFlag = None, printMirrors: BooleanFlag = None, printPositions: BooleanFlag = None): String = + render(any, newRawTreePrinter(_), printTypes, printIds, printOwners, printKinds, printMirrors, printPositions) /** Hook to define what `showRaw(...)` means. * @group Printers diff --git a/src/reflect/scala/reflect/internal/Printers.scala b/src/reflect/scala/reflect/internal/Printers.scala index 519d1047a6..21cf9578af 100644 --- a/src/reflect/scala/reflect/internal/Printers.scala +++ b/src/reflect/scala/reflect/internal/Printers.scala @@ -65,6 +65,7 @@ trait Printers extends api.Printers { self: SymbolTable => printTypes = settings.printtypes.value printIds = settings.uniqid.value + printOwners = settings.Yshowsymowners.value printKinds = settings.Yshowsymkinds.value printMirrors = false // typically there's no point to print mirrors inside the compiler, as there is only one mirror there printPositions = settings.Xprintpos.value @@ -128,10 +129,10 @@ trait Printers extends api.Printers { self: SymbolTable => body if (condition) print(")") } - - protected def printImplicitInParamsList(vds: List[ValDef]) = + + protected def printImplicitInParamsList(vds: List[ValDef]) = if (vds.nonEmpty) printFlags(vds.head.mods.flags & IMPLICIT, "") - + def printValueParams(ts: List[ValDef], inParentheses: Boolean = true): Unit = parenthesize(inParentheses){ printImplicitInParamsList(ts) @@ -191,7 +192,7 @@ trait Printers extends api.Printers { self: SymbolTable => private var currentOwner: Symbol = NoSymbol private var selectorType: Type = NoType - + protected def printPackageDef(tree: PackageDef, separator: String) = { val PackageDef(packaged, stats) = tree printAnnotations(tree) @@ -275,6 +276,7 @@ trait Printers extends api.Printers { self: SymbolTable => printValueParams print(" => ", body, ")") if (printIds && tree.symbol != null) print("#" + tree.symbol.id) + if (printOwners && tree.symbol != null) print("@" + tree.symbol.owner.id) } protected def printSuper(tree: Super, resultName: => String) = { @@ -511,7 +513,7 @@ trait Printers extends api.Printers { self: SymbolTable => out.print(if (arg == null) "null" else arg.toString) } } - + // it's the printer for trees after parser and before typer phases class ParsedTreePrinter(out: PrintWriter) extends TreePrinter(out) { override def withTypes = this @@ -537,13 +539,13 @@ trait Printers extends api.Printers { self: SymbolTable => import Chars._ val decName = name.decoded val bslash = '\\' - val brackets = List('[',']','(',')','{','}') + val brackets = List('[',']','(',')','{','}') def addBackquotes(s: String) = - if (decoded && (decName.exists(ch => brackets.contains(ch) || isWhitespace(ch)) || + if (decoded && (decName.exists(ch => brackets.contains(ch) || isWhitespace(ch)) || (name.isOperatorName && decName.exists(isOperatorPart) && decName.exists(isScalaLetter) && !decName.contains(bslash)))) s"`$s`" else s - + if (name == nme.CONSTRUCTOR) "this" else addBackquotes(quotedName(name, decoded)) } @@ -556,7 +558,7 @@ trait Printers extends api.Printers { self: SymbolTable => qualIsIntLit && name.isOperatorName } - protected def needsParentheses(parent: Tree)(insideIf: Boolean = true, insideMatch: Boolean = true, + protected def needsParentheses(parent: Tree)(insideIf: Boolean = true, insideMatch: Boolean = true, insideTry: Boolean = true, insideAnnotated: Boolean = true, insideBlock: Boolean = true, insideLabelDef: Boolean = true) = { parent match { case _: If => insideIf @@ -572,10 +574,10 @@ trait Printers extends api.Printers { self: SymbolTable => protected def checkForBlank(cond: Boolean) = if (cond) " " else "" protected def blankForOperatorName(name: Name) = checkForBlank(name.isOperatorName) protected def blankForName(name: Name) = checkForBlank(name.isOperatorName || name.endsWith("_")) - + protected def resolveSelect(t: Tree): String = { t match { - // case for: 1) (if (a) b else c).meth1.meth2 or 2) 1 + 5 should be represented as (1).+(5) + // case for: 1) (if (a) b else c).meth1.meth2 or 2) 1 + 5 should be represented as (1).+(5) case Select(qual, name) if (name.isTermName && needsParentheses(qual)(insideLabelDef = false)) || isIntLitWithDecodedOp(qual, name) => s"(${resolveSelect(qual)}).${printedName(name)}" case Select(qual, name) if name.isTermName => s"${resolveSelect(qual)}.${printedName(name)}" case Select(qual, name) if name.isTypeName => s"${resolveSelect(qual)}#${blankForOperatorName(name)}%${printedName(name)}" @@ -591,7 +593,7 @@ trait Printers extends api.Printers { self: SymbolTable => trees match { case Nil => trees case init :+ last => last match { - case Select(Ident(sc), name) if traitsToRemove.contains(name) && sc == nme.scala_ => + case Select(Ident(sc), name) if traitsToRemove.contains(name) && sc == nme.scala_ => removeDefaultTraitsFromList(init, traitsToRemove) case _ => trees } @@ -637,7 +639,7 @@ trait Printers extends api.Printers { self: SymbolTable => val mutableOrOverride = mods.isOverride || mods.isMutable val hideCtorMods = mods.isParamAccessor && mods.isPrivateLocal && !mutableOrOverride val hideCaseCtorMods = mods.isCaseAccessor && mods.isPublic && !mutableOrOverride - + if (primaryCtorParam && !(hideCtorMods || hideCaseCtorMods)) { printModifiers(mods, primaryCtorParam) print(if (mods.isMutable) "var " else "val "); @@ -657,14 +659,14 @@ trait Printers extends api.Printers { self: SymbolTable => printParam(tree, primaryCtorParam = false) } - protected def printArgss(argss: List[List[Tree]]) = + protected def printArgss(argss: List[List[Tree]]) = argss foreach {x: List[Tree] => if (!(x.isEmpty && argss.size == 1)) printRow(x, "(", ", ", ")")} - + override def printAnnotations(tree: MemberDef) = { val annots = tree.mods.annotations annots foreach {annot => printAnnot(annot); print(" ")} } - + protected def printAnnot(tree: Tree) = { tree match { case treeInfo.Applied(core, _, argss) => @@ -675,10 +677,10 @@ trait Printers extends api.Printers { self: SymbolTable => } printArgss(argss) case _ => super.printTree(tree) - } + } } - override def printTree(tree: Tree): Unit = { + override def printTree(tree: Tree): Unit = { parentsStack.push(tree) tree match { case cl @ ClassDef(mods, name, tparams, impl) => @@ -809,15 +811,15 @@ trait Printers extends api.Printers { self: SymbolTable => } case _ => None } - + if (printedParents.nonEmpty) { val (clParent :: traits) = printedParents print(clParent) val constrArgss = ap match { case Some(treeInfo.Applied(_, _, argss)) => argss - case _ => Nil - } + case _ => Nil + } printArgss(constrArgss) if (traits.nonEmpty) { printRow(traits, " with ", " with ", "") @@ -907,7 +909,7 @@ trait Printers extends api.Printers { self: SymbolTable => case Apply(fun, vargs) => tree match { // processing methods ending on colons (x \: list) - case Apply(Block(l1 @ List(sVD: ValDef), a1 @ Apply(Select(_, methodName), l2 @ List(Ident(iVDName)))), l3) + case Apply(Block(l1 @ List(sVD: ValDef), a1 @ Apply(Select(_, methodName), l2 @ List(Ident(iVDName)))), l3) if sVD.mods.isSynthetic && treeInfo.isLeftAssoc(methodName) && sVD.name == iVDName => val printBlock = Block(l1, Apply(a1, l3)) print(printBlock) @@ -972,7 +974,7 @@ trait Printers extends api.Printers { self: SymbolTable => case AppliedTypeTree(tp, args) => // it's possible to have (=> String) => String type but Function1[=> String, String] is not correct val containsByNameTypeParam = args exists treeInfo.isByNameParamType - + if (containsByNameTypeParam) { print("(") printRow(args.init, "(", ", ", ")") @@ -1139,6 +1141,7 @@ trait Printers extends api.Printers { self: SymbolTable => else if (sym.isStatic && (sym.isClass || sym.isModule)) print(sym.fullName) else print(sym.name) if (printIds) print("#", sym.id) + if (printOwners) print("@", sym.owner.id) if (printKinds) print("#", sym.abbreviatedKindString) if (printMirrors) print("%M", footnotes.put[scala.reflect.api.Mirror[_]](mirrorThatLoaded(sym))) case tag: TypeTag[_] => diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index b363123542..17691fe085 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2472,6 +2472,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * If !settings.debug translates expansions of operators back to operator symbol. * E.g. $eq => =. * If settings.uniqid, adds id. + * If settings.Yshowsymowners, adds owner's id * If settings.Yshowsymkinds, adds abbreviated symbol kind. */ def nameString: String = { @@ -2492,7 +2493,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => } /** If settings.uniqid is set, the symbol's id, else "" */ - final def idString = if (settings.uniqid.value) "#"+id else "" + final def idString = { + val id_s = if (settings.uniqid.value) "#"+id else "" + val owner_s = if (settings.Yshowsymowners.value) "@"+owner.id else "" + id_s + owner_s + } /** String representation, including symbol's kind e.g., "class Foo", "method Bar". * If hasMeaninglessName is true, uses the owner's name to disambiguate identity. diff --git a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala index 816916787e..048fe9ef37 100644 --- a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala +++ b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala @@ -39,6 +39,7 @@ abstract class MutableSettings extends AbsSettings { def Xprintpos: BooleanSetting def Yposdebug: BooleanSetting def Yrangepos: BooleanSetting + def Yshowsymowners: BooleanSetting def Yshowsymkinds: BooleanSetting def breakCycles: BooleanSetting def debug: BooleanSetting diff --git a/src/reflect/scala/reflect/runtime/Settings.scala b/src/reflect/scala/reflect/runtime/Settings.scala index de5ba99900..d46846fc21 100644 --- a/src/reflect/scala/reflect/runtime/Settings.scala +++ b/src/reflect/scala/reflect/runtime/Settings.scala @@ -36,6 +36,7 @@ private[reflect] class Settings extends MutableSettings { val Xprintpos = new BooleanSetting(false) val Yposdebug = new BooleanSetting(false) val Yrangepos = new BooleanSetting(false) + val Yshowsymowners = new BooleanSetting(false) val Yshowsymkinds = new BooleanSetting(false) val breakCycles = new BooleanSetting(false) val debug = new BooleanSetting(false) -- cgit v1.2.3 From a02e053a5dec134f7c7dc53a2c1091039218237d Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 22 Jan 2014 13:18:35 +0300 Subject: SI-5920 enables default and named args in macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When producing an initial spec for macros two years ago, we sort of glossed over named/default arguments in macro applications, leaving them for future work. Once the aforementioned future has come, I’ve made several attempts at making things operational (e.g. last summer), but it’s always been unclear how to marry the quite complex desugaring that tryNamesDefaults performs with the expectations of macro programmers to see unsugared trees in macro impl parameters. Here’s the list of problems that arise when trying to encode named/default arguments of macro applications: 1) When inside macro impls we don’t really care about synthetic vals that are typically introduced to preserve evaluation order in non-positional method applications. When we inline those synthetics, we lose information about evaluation order, which is something that we wouldn’t like to lose in the general case. 2) More importantly, it’s also not very exciting to see invocations of default getters that stand for unspecified default arguments. Ideally, we would like to provide macro programmers with right-hand sides of those default getters, but that is: a) impossible in the current implementation of default parameters, b) would anyway bring scoping problems that we’re not ready to deal with just yet. Being constantly unhappy with potential solutions to the aforementioned problems, I’ve been unable to nail this down until the last weekend, when I realized that: 1) even though we can’t express potential twists in evaluation order within linearly ordered macro impl params, we can use c.macroApplication to store all the named arguments we want, 2) even though we can’t get exactly what we want for default arguments, we can represent them with EmptyTree’s, which is not ideal, but pretty workable. That’s what has been put into life in this commit. As a pleasant side-effect, now the macro engine doesn’t have to reinvent the wheel wrt reporting errors about insufficient arg or arglist count. Since this logic is intertwined with the tryNamesDefaults desugaring, we previously couldn’t make use of it and had to roll our own logic that checked that the number of arguments and parameters of macro applications correspond to each other. Now it’s all deduplicated and consistent. --- .../tools/nsc/typechecker/ContextErrors.scala | 28 +++---- .../scala/tools/nsc/typechecker/Macros.scala | 93 ++++++++++++++++------ .../tools/nsc/typechecker/StdAttachments.scala | 3 + .../scala/tools/nsc/typechecker/Typers.scala | 19 ++--- src/reflect/scala/reflect/internal/TreeInfo.scala | 11 +-- test/files/neg/macro-argc-mismatch.check | 49 ++++++++++++ test/files/neg/macro-argc-mismatch/Macros_1.scala | 16 ++++ test/files/neg/macro-argc-mismatch/Test_2.scala | 19 +++++ test/files/neg/macro-invalidusage-badargs.check | 5 +- test/files/neg/macro-qmarkqmarkqmark.check | 2 +- test/files/neg/t7157.check | 36 ++++++--- test/files/run/macro-expand-default-named.check | 56 +++++++++++++ .../run/macro-expand-default-named/Impls_1.scala | 37 +++++++++ .../macro-expand-default-named/Macros_Test_2.scala | 71 +++++++++++++++++ test/files/run/macro-expand-ownerchain-a.check | 2 + .../run/macro-expand-ownerchain-a/Macros_1.scala | 11 +++ .../run/macro-expand-ownerchain-a/Test_2.scala | 4 + ...alidusage-partialapplication-with-tparams.check | 2 +- .../macro-invalidusage-partialapplication.check | 2 +- test/files/run/reify-repl-fail-gracefully.check | 2 +- test/pending/run/macro-expand-default.flags | 1 - .../pending/run/macro-expand-default/Impls_1.scala | 10 --- .../run/macro-expand-default/Macros_Test_2.scala | 8 -- test/pending/run/macro-expand-named.flags | 1 - test/pending/run/macro-expand-named/Impls_1.scala | 10 --- .../run/macro-expand-named/Macros_Test_2.scala | 5 -- 26 files changed, 384 insertions(+), 119 deletions(-) create mode 100644 test/files/neg/macro-argc-mismatch.check create mode 100644 test/files/neg/macro-argc-mismatch/Macros_1.scala create mode 100644 test/files/neg/macro-argc-mismatch/Test_2.scala create mode 100644 test/files/run/macro-expand-default-named.check create mode 100644 test/files/run/macro-expand-default-named/Impls_1.scala create mode 100644 test/files/run/macro-expand-default-named/Macros_Test_2.scala create mode 100644 test/files/run/macro-expand-ownerchain-a.check create mode 100644 test/files/run/macro-expand-ownerchain-a/Macros_1.scala create mode 100644 test/files/run/macro-expand-ownerchain-a/Test_2.scala delete mode 100644 test/pending/run/macro-expand-default.flags delete mode 100644 test/pending/run/macro-expand-default/Impls_1.scala delete mode 100644 test/pending/run/macro-expand-default/Macros_Test_2.scala delete mode 100644 test/pending/run/macro-expand-named.flags delete mode 100644 test/pending/run/macro-expand-named/Impls_1.scala delete mode 100644 test/pending/run/macro-expand-named/Macros_Test_2.scala (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 2043eb5d5d..40b97394f2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -501,10 +501,6 @@ trait ContextErrors { } // doTypeApply - //tryNamesDefaults - def NamedAndDefaultArgumentsNotSupportedForMacros(tree: Tree, fun: Tree) = - NormalTypeError(tree, "macro applications do not support named and/or default arguments") - def TooManyArgsNamesDefaultsError(tree: Tree, fun: Tree) = NormalTypeError(tree, "too many arguments for "+treeSymTypeMsg(fun)) @@ -603,12 +599,11 @@ trait ContextErrors { //adapt def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = { + val errorExplanation = "missing arguments for " + meth.fullLocationString + val suggestPartialApplication = ";\nfollow this method with `_' if you want to treat it as a partially applied function" val message = - if (meth.isMacro) MacroTooFewArgumentListsMessage - else "missing arguments for " + meth.fullLocationString + ( - if (meth.isConstructor) "" - else ";\nfollow this method with `_' if you want to treat it as a partially applied function" - ) + if (meth.isMacro || meth.isConstructor) errorExplanation + else errorExplanation + suggestPartialApplication issueNormalTypeError(tree, message) setError(tree) } @@ -748,15 +743,12 @@ trait ContextErrors { throw MacroExpansionException } - private def MacroTooFewArgumentListsMessage = "too few argument lists for macro invocation" - def MacroTooFewArgumentListsError(expandee: Tree) = macroExpansionError2(expandee, MacroTooFewArgumentListsMessage) - - private def MacroTooManyArgumentListsMessage = "too many argument lists for macro invocation" - def MacroTooManyArgumentListsError(expandee: Tree) = macroExpansionError2(expandee, MacroTooManyArgumentListsMessage) - - def MacroTooFewArgumentsError(expandee: Tree) = macroExpansionError2(expandee, "too few arguments for macro invocation") - - def MacroTooManyArgumentsError(expandee: Tree) = macroExpansionError2(expandee, "too many arguments for macro invocation") + def MacroFastTrackFailed(expandee: Tree) = { + // here we speculate that the reason why FastTrackEntry.validate failed is the lack arguments for a given method + // that's not ideal, but on the other hand this allows us to keep FastTrack simple without hooking errorgen into it + MissingArgsForMethodTpeError(expandee, expandee.symbol) + throw MacroExpansionException + } def MacroGeneratedAbort(expandee: Tree, ex: AbortMacroException) = { // errors have been reported by the macro itself, so we do nothing here diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index cf82d6baac..29b3ec7f3e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -353,7 +353,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { new { val universe: self.global.type = self.global val callsiteTyper: universe.analyzer.Typer = typer.asInstanceOf[global.analyzer.Typer] - val expandee = universe.analyzer.macroExpanderAttachment(expandeeTree).original orElse duplicateAndKeepPositions(expandeeTree) + val expandee = universe.analyzer.macroExpanderAttachment(expandeeTree).desugared orElse duplicateAndKeepPositions(expandeeTree) } with UnaffiliatedMacroContext { val prefix = Expr[Nothing](prefixTree)(TypeTag.Nothing) override def toString = "MacroContext(%s@%s +%d)".format(expandee.symbol.name, expandee.pos, enclosingMacros.length - 1 /* exclude myself */) @@ -371,7 +371,15 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { def standardMacroArgs(typer: Typer, expandee: Tree): MacroArgs = { val macroDef = expandee.symbol val paramss = macroDef.paramss - val treeInfo.Applied(core, targs, argss) = expandee + val treeInfo.Applied(core, targs, maybeNamedArgss) = expandee + val argss = map2(maybeNamedArgss, paramss)((args, params) => { + if (args.exists(_.isInstanceOf[AssignOrNamedArg])) { + val sorted = ListBuffer.fill(params.length)(EmptyTree: Tree) + args foreach { case AssignOrNamedArg(Ident(name), arg) => sorted(params.indexWhere(_.name == name)) = arg } + sorted.toList + } else if (params.length == args.length) args + else args ++ List.fill(params.length - args.length)(EmptyTree) + }) val prefix = core match { case Select(qual, _) => qual; case _ => EmptyTree } val context = expandee.attachments.get[MacroRuntimeAttachment].flatMap(_.macroContext).getOrElse(macroContext(typer, prefix, expandee)) @@ -383,16 +391,11 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { |paramss: $paramss """.trim) - import typer.TyperErrorGen._ - val isNullaryArgsEmptyParams = argss.isEmpty && paramss == ListOfNil - if (paramss.length < argss.length) MacroTooManyArgumentListsError(expandee) - if (paramss.length > argss.length && !isNullaryArgsEmptyParams) MacroTooFewArgumentListsError(expandee) - val macroImplArgs: List[Any] = if (fastTrack contains macroDef) { // Take a dry run of the fast track implementation if (fastTrack(macroDef) validate expandee) argss.flatten - else MacroTooFewArgumentListsError(expandee) + else typer.TyperErrorGen.MacroFastTrackFailed(expandee) } else { def calculateMacroArgs(binding: MacroImplBinding) = { @@ -403,14 +406,6 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // wrap argss in c.Expr if necessary (i.e. if corresponding macro impl param is of type c.Expr[T]) // expand varargs (nb! varargs can apply to any parameter section, not necessarily to the last one) val trees = map3(argss, paramss, signature)((args, defParams, implParams) => { - val isVarargs = isVarArgsList(defParams) - if (isVarargs) { - if (defParams.length > args.length + 1) MacroTooFewArgumentsError(expandee) - } else { - if (defParams.length < args.length) MacroTooManyArgumentsError(expandee) - if (defParams.length > args.length) MacroTooFewArgumentsError(expandee) - } - val wrappedArgs = mapWithIndex(args)((arg, j) => { val fingerprint = implParams(min(j, implParams.length - 1)) fingerprint match { @@ -421,7 +416,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } }) - if (isVarargs) { + if (isVarArgsList(defParams)) { val (normal, varargs) = wrappedArgs splitAt (defParams.length - 1) normal :+ varargs // pack all varargs into a single Seq argument (varargs Scala style) } else wrappedArgs @@ -529,9 +524,11 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * the expandee with an error marker set if there has been an error */ abstract class MacroExpander(val typer: Typer, val expandee: Tree) { + val symbol = expandee match { case Block(_, expr) => expr.symbol; case tree => tree.symbol } + def onSuccess(expanded: Tree): Tree def onFallback(expanded: Tree): Tree - def onSuppressed(expandee: Tree): Tree = expandee + def onSuppressed(expanded: Tree): Tree = expanded def onDelayed(expanded: Tree): Tree = expanded def onSkipped(expanded: Tree): Tree = expanded def onFailure(expanded: Tree): Tree = { typer.infer.setError(expandee); expandee } @@ -551,15 +548,15 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) try { withInfoLevel(nodePrinters.InfoLevel.Quiet) { // verbose printing might cause recursive macro expansions - if (expandee.symbol.isErroneous || (expandee exists (_.isErroneous))) { - val reason = if (expandee.symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" + if (symbol.isErroneous || (expandee exists (_.isErroneous)) || (desugared exists (_.isErroneous))) { + val reason = if (symbol.isErroneous) "not found or incompatible macro implementation" else "erroneous arguments" macroLogVerbose(s"cancelled macro expansion because of $reason: $expandee") onFailure(typer.infer.setError(expandee)) } else try { val expanded = { - val runtime = macroRuntime(expandee) - if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) - else macroExpandWithoutRuntime(typer, expandee) + val runtime = macroRuntime(desugared) + if (runtime != null) macroExpandWithRuntime(typer, desugared, runtime) + else macroExpandWithoutRuntime(typer, desugared) } expanded match { case Success(expanded) => @@ -591,7 +588,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { extends MacroExpander(typer, expandee) { lazy val innerPt = { val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe - if (isBlackbox(expandee)) tp + if (isBlackbox(symbol)) tp else { // approximation is necessary for whitebox macros to guide type inference // read more in the comments for onDelayed below @@ -599,6 +596,50 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { deriveTypeWithWildcards(undetparams)(tp) } } + override protected def expand(desugared: Tree) = { + // SI-5940 in order for a macro expansion that involves named or default arguments + // to see the actual prefix and arguments being passed by the user instead of their desugarings + // we need to inline synthetics in case when `fun` is actually a macro + // underlying macro implementation is going to get explicitly passed arguments in correct order + // and the rest (defaults filled in by the vanilla part of `tryNamesDefaults`) will become empty trees + // in order for the macro to be able to account for evaluation order, the original is provided in `c.macroApplication` + // of course, ideally we would like to provide the impl with right-hand sides of those default arguments + // but currently that is flat out impossible because of the difference in scopes + // anyway this is already an improvement over the former status quo when named/default invocations were outright prohibited + def undoNamesDefaults(tree: Tree): Tree = { + val (qualsym, qual, vdefs0, app @ Applied(_, _, argss)) = tree match { + case Block((qualdef @ ValDef(_, name, _, qual)) +: vdefs, app) if name.startsWith(nme.QUAL_PREFIX) => (qualdef.symbol, qual, vdefs, app) + case Block(vdefs, app) => (NoSymbol, EmptyTree, vdefs, app) + case tree => (NoSymbol, EmptyTree, Nil, tree) + } + val vdefs = vdefs0.map{ case vdef: ValDef => vdef } + def hasNamesDefaults(args: List[Tree]) = { + args.exists(arg => isDefaultGetter(arg) || vdefs.exists(_.symbol == arg.symbol)) + } + def undoNamesDefaults(args: List[Tree], depth: Int) = { + def extractRhs(vdef: ValDef) = vdef.rhs.changeOwner(vdef.symbol -> typer.context.owner) + case class Arg(tree: Tree, ipos: Int, inamed: Int) { val param = app.symbol.paramss(depth)(ipos) } + val indexed = args.map(arg => arg -> vdefs.indexWhere(_.symbol == arg.symbol)).zipWithIndex.flatMap({ + /* default */ case ((arg, _), _) if isDefaultGetter(arg) => None + /* positional */ case ((arg, -1), ipos) => Some(Arg(arg, ipos, -1)) + /* default+named */ case ((_, inamed), _) if isDefaultGetter(extractRhs(vdefs(inamed))) => None + /* named */ case ((arg, inamed), ipos) => Some(Arg(extractRhs(vdefs(inamed)), ipos, inamed)) + }) + if (indexed.forall(_.inamed == -1)) indexed.map(_.tree) + else indexed.sortBy(_.inamed).map(arg => AssignOrNamedArg(Ident(arg.param.name), arg.tree)) + } + def loop(tree: Tree, depth: Int): Tree = tree match { + case Apply(fun, args) if hasNamesDefaults(args) => treeCopy.Apply(tree, loop(fun, depth - 1), undoNamesDefaults(args, depth)) + case Apply(fun, args) => treeCopy.Apply(tree, loop(fun, depth - 1), args) + case TypeApply(core, targs) => treeCopy.TypeApply(tree, core, targs) + case Select(core, name) if qualsym != NoSymbol && core.symbol == qualsym => treeCopy.Select(tree, qual, name) + case core => core + } + if (app.symbol == null || app.symbol == NoSymbol || app.exists(_.isErroneous)) tree + else loop(app, depth = argss.length - 1) + } + super.expand(undoNamesDefaults(desugared)) + } override def onSuccess(expanded0: Tree) = { // prematurely annotate the tree with a macro expansion attachment // so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup @@ -616,7 +657,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } } - if (isBlackbox(expandee)) { + if (isBlackbox(symbol)) { val expanded1 = atPos(enclosingMacroPosition.makeTransparent)(Typed(expanded0, TypeTree(innerPt))) typecheck("blackbox typecheck", expanded1, outerPt) } else { @@ -676,7 +717,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // Thanks to that the materializer can take a look at what's going on and react accordingly. val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode if (shouldInstantiate) { - if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt) + if (isBlackbox(symbol)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt) else { forced += delayed typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false) diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index 57f27a05fd..1a6d2f0011 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -85,6 +85,7 @@ trait StdAttachments { tree match { // see the comment to `isMacroExpansionSuppressed` to learn why we need // a special traversal strategy here + case Block(_, expr) => unsuppressMacroExpansion(expr) case Apply(fn, _) => unsuppressMacroExpansion(fn) case TypeApply(fn, _) => unsuppressMacroExpansion(fn) case _ => // do nothing @@ -101,6 +102,8 @@ trait StdAttachments { // we have to account for the fact that during typechecking an expandee might become wrapped, // i.e. surrounded by an inferred implicit argument application or by an inferred type argument application. // in that case the expandee itself will no longer be suppressed and we need to look at the core + // upd. we also need to allow for blocks, because that's what names and defaults are often desugared to + case Block(_, expr) => isMacroExpansionSuppressed(expr) case Apply(fn, _) => isMacroExpansionSuppressed(fn) case TypeApply(fn, _) => isMacroExpansionSuppressed(fn) case _ => false diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 10fe530445..020d26c712 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1155,7 +1155,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case mt: MethodType if mode.typingExprNotFunNotLhs && mt.isImplicit => // (4.1) adaptToImplicitMethod(mt) - case mt: MethodType if mode.typingExprNotFunNotLhs && !hasUndetsInMonoMode && !treeInfo.isMacroApplicationOrBlock(tree) => + case mt: MethodType if mode.typingExprNotFunNotLhs && !hasUndetsInMonoMode => instantiateToMethodType(mt) case _ => vanillaAdapt(tree) @@ -3292,12 +3292,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper */ def tryNamesDefaults: Tree = { val lencmp = compareLengths(args, formals) - - def checkNotMacro() = { - if (treeInfo.isMacroApplication(fun)) - tryTupleApply orElse duplErrorTree(NamedAndDefaultArgumentsNotSupportedForMacros(tree, fun)) - } - if (mt.isErroneous) duplErrTree else if (mode.inPatternMode) { // #2064 @@ -3316,18 +3310,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper else if (allArgsArePositional(argPos) && !isNamedApplyBlock(fun)) { // if there's no re-ordering, and fun is not transformed, no need to transform // more than an optimization, e.g. important in "synchronized { x = update-x }" - checkNotMacro() doTypedApply(tree, fun, namelessArgs, mode, pt) } else { - checkNotMacro() - transformNamedApplication(Typer.this, mode, pt)( - treeCopy.Apply(tree, fun, namelessArgs), argPos) + unsuppressMacroExpansion(transformNamedApplication(Typer.this, mode, pt)( + treeCopy.Apply(tree, suppressMacroExpansion(fun), namelessArgs), argPos)) } } else { // defaults are needed. they are added to the argument list in named style as // calls to the default getters. Example: // foo[Int](a)() ==> foo[Int](a)(b = foo$qual.foo$default$2[Int](a)) - checkNotMacro() // SI-8111 transformNamedApplication eagerly shuffles around the application to preserve // evaluation order. During this process, it calls `changeOwner` on symbols that @@ -3350,7 +3341,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper symsOwnedByContextOwner foreach (_.owner = context.owner) } - val fun1 = transformNamedApplication(Typer.this, mode, pt)(fun, x => x) + val fun1 = transformNamedApplication(Typer.this, mode, pt)(suppressMacroExpansion(fun), x => x) if (fun1.isErroneous) duplErrTree else { assert(isNamedApplyBlock(fun1), fun1) @@ -3376,7 +3367,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // useful when a default doesn't match parameter type, e.g. def f[T](x:T="a"); f[Int]() val note = "Error occurred in an application involving default arguments." if (!(context.diagnostic contains note)) context.diagnostic = note :: context.diagnostic - doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt) + unsuppressMacroExpansion(doTypedApply(tree, if (blockIsEmpty) fun else fun1, allArgs, mode, pt)) } else { rollbackNamesDefaultsOwnerChanges() tryTupleApply orElse duplErrorTree(NotEnoughArgsError(tree, fun, missing)) diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 1eb0743bc8..7ff482d021 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -852,13 +852,8 @@ abstract class TreeInfo { case _ => false }) - def isMacroApplication(tree: Tree): Boolean = !tree.isDef && { - val sym = tree.symbol - sym != null && sym.isTermMacro && !sym.isErroneous - } - - def isMacroApplicationOrBlock(tree: Tree): Boolean = tree match { - case Block(_, expr) => isMacroApplicationOrBlock(expr) - case tree => isMacroApplication(tree) + def isMacroApplication(tree: Tree): Boolean = tree match { + case Block(_, expr) => isMacroApplication(expr) + case tree => !tree.isDef && tree.symbol != null && tree.symbol.isTermMacro && !tree.symbol.isErroneous } } diff --git a/test/files/neg/macro-argc-mismatch.check b/test/files/neg/macro-argc-mismatch.check new file mode 100644 index 0000000000..617daa890c --- /dev/null +++ b/test/files/neg/macro-argc-mismatch.check @@ -0,0 +1,49 @@ +Test_2.scala:4: error: missing arguments for macro method one in object Macros + one + ^ +Test_2.scala:5: error: not enough arguments for macro method one: (x: Int)Unit. +Unspecified value parameter x. + one() + ^ +Test_2.scala:6: error: too many arguments for macro method one: (x: Int)Unit + one(2, 3) + ^ +Test_2.scala:7: error: not enough arguments for macro method one: (x: Int)Unit. +Unspecified value parameter x. + one()() + ^ +Test_2.scala:8: error: Unit does not take parameters + one(1)() + ^ +Test_2.scala:10: error: missing arguments for macro method two in object Macros + two + ^ +Test_2.scala:11: error: not enough arguments for macro method two: (x: Int)(y: Int)Unit. +Unspecified value parameter x. + two() + ^ +Test_2.scala:12: error: too many arguments for macro method two: (x: Int)(y: Int)Unit + two(2, 3) + ^ +Test_2.scala:13: error: not enough arguments for macro method two: (x: Int)(y: Int)Unit. +Unspecified value parameter x. + two()() + ^ +Test_2.scala:14: error: missing arguments for macro method two in object Macros + two(1) + ^ +Test_2.scala:15: error: not enough arguments for macro method two: (y: Int)Unit. +Unspecified value parameter y. + two(1)() + ^ +Test_2.scala:16: error: too many arguments for macro method two: (y: Int)Unit + two(1)(2, 3) + ^ +Test_2.scala:17: error: not enough arguments for macro method two: (y: Int)Unit. +Unspecified value parameter y. + two(1)()() + ^ +Test_2.scala:18: error: Unit does not take parameters + two(1)(1)() + ^ +14 errors found diff --git a/test/files/neg/macro-argc-mismatch/Macros_1.scala b/test/files/neg/macro-argc-mismatch/Macros_1.scala new file mode 100644 index 0000000000..4dca644172 --- /dev/null +++ b/test/files/neg/macro-argc-mismatch/Macros_1.scala @@ -0,0 +1,16 @@ +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context + +object Macros { + def one(x: Int): Unit = macro oneImpl + def oneImpl(c: Context)(x: c.Tree) = { + import c.universe._ + q"()" + } + + def two(x: Int)(y: Int): Unit = macro twoImpl + def twoImpl(c: Context)(x: c.Tree)(y: c.Tree) = { + import c.universe._ + q"()" + } +} diff --git a/test/files/neg/macro-argc-mismatch/Test_2.scala b/test/files/neg/macro-argc-mismatch/Test_2.scala new file mode 100644 index 0000000000..28f9c35654 --- /dev/null +++ b/test/files/neg/macro-argc-mismatch/Test_2.scala @@ -0,0 +1,19 @@ +import Macros._ + +object Test extends App { + one + one() + one(2, 3) + one()() + one(1)() + + two + two() + two(2, 3) + two()() + two(1) + two(1)() + two(1)(2, 3) + two(1)()() + two(1)(1)() +} \ No newline at end of file diff --git a/test/files/neg/macro-invalidusage-badargs.check b/test/files/neg/macro-invalidusage-badargs.check index 4c1115418b..3fd3c53691 100644 --- a/test/files/neg/macro-invalidusage-badargs.check +++ b/test/files/neg/macro-invalidusage-badargs.check @@ -3,13 +3,14 @@ Macros_Test_2.scala:5: error: type mismatch; required: Int foo("42") ^ -Macros_Test_2.scala:6: error: too few argument lists for macro invocation +Macros_Test_2.scala:6: error: missing arguments for macro method foo in object Macros foo ^ Macros_Test_2.scala:7: error: Int does not take parameters foo(4)(2) ^ -Macros_Test_2.scala:8: error: macro applications do not support named and/or default arguments +Macros_Test_2.scala:8: error: not enough arguments for macro method foo: (x: Int)Int. +Unspecified value parameter x. foo() ^ Macros_Test_2.scala:9: error: too many arguments for macro method foo: (x: Int)Int diff --git a/test/files/neg/macro-qmarkqmarkqmark.check b/test/files/neg/macro-qmarkqmarkqmark.check index bc3e25edaf..b4f8ea905f 100644 --- a/test/files/neg/macro-qmarkqmarkqmark.check +++ b/test/files/neg/macro-qmarkqmarkqmark.check @@ -1,7 +1,7 @@ macro-qmarkqmarkqmark.scala:5: error: macro implementation is missing foo1 ^ -macro-qmarkqmarkqmark.scala:8: error: too few argument lists for macro invocation +macro-qmarkqmarkqmark.scala:8: error: missing arguments for macro method foo2 in object Macros foo2 ^ macro-qmarkqmarkqmark.scala:9: error: macro implementation is missing diff --git a/test/files/neg/t7157.check b/test/files/neg/t7157.check index c6a7af9a23..3988460d4b 100644 --- a/test/files/neg/t7157.check +++ b/test/files/neg/t7157.check @@ -7,7 +7,8 @@ Test_2.scala:6: error: too many arguments for macro method m1_0_0: ()Unit Test_2.scala:7: error: too many arguments for macro method m1_0_0: ()Unit m1_0_0(1, 2, 3) ^ -Test_2.scala:9: error: macro applications do not support named and/or default arguments +Test_2.scala:9: error: not enough arguments for macro method m1_1_1: (x: Int)Unit. +Unspecified value parameter x. m1_1_1() ^ Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit @@ -16,22 +17,27 @@ Test_2.scala:11: error: too many arguments for macro method m1_1_1: (x: Int)Unit Test_2.scala:12: error: too many arguments for macro method m1_1_1: (x: Int)Unit m1_1_1(1, 2, 3) ^ -Test_2.scala:14: error: macro applications do not support named and/or default arguments +Test_2.scala:14: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. +Unspecified value parameters x, y. m1_2_2() ^ -Test_2.scala:15: error: macro applications do not support named and/or default arguments +Test_2.scala:15: error: not enough arguments for macro method m1_2_2: (x: Int, y: Int)Unit. +Unspecified value parameter y. m1_2_2(1) ^ Test_2.scala:17: error: too many arguments for macro method m1_2_2: (x: Int, y: Int)Unit m1_2_2(1, 2, 3) ^ -Test_2.scala:24: error: macro applications do not support named and/or default arguments +Test_2.scala:24: error: not enough arguments for macro method m1_1_inf: (x: Int, y: Int*)Unit. +Unspecified value parameters x, y. m1_1_inf() ^ -Test_2.scala:29: error: macro applications do not support named and/or default arguments +Test_2.scala:29: error: not enough arguments for macro method m1_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters x, y, z. m1_2_inf() ^ -Test_2.scala:30: error: macro applications do not support named and/or default arguments +Test_2.scala:30: error: not enough arguments for macro method m1_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters y, z. m1_2_inf(1) ^ Test_2.scala:35: error: too many arguments for macro method m2_0_0: ()Unit @@ -43,7 +49,8 @@ Test_2.scala:36: error: too many arguments for macro method m2_0_0: ()Unit Test_2.scala:37: error: too many arguments for macro method m2_0_0: ()Unit m2_0_0()(1, 2, 3) ^ -Test_2.scala:39: error: macro applications do not support named and/or default arguments +Test_2.scala:39: error: not enough arguments for macro method m2_1_1: (x: Int)Unit. +Unspecified value parameter x. m2_1_1()() ^ Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit @@ -52,22 +59,27 @@ Test_2.scala:41: error: too many arguments for macro method m2_1_1: (x: Int)Unit Test_2.scala:42: error: too many arguments for macro method m2_1_1: (x: Int)Unit m2_1_1()(1, 2, 3) ^ -Test_2.scala:44: error: macro applications do not support named and/or default arguments +Test_2.scala:44: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. +Unspecified value parameters x, y. m2_2_2()() ^ -Test_2.scala:45: error: macro applications do not support named and/or default arguments +Test_2.scala:45: error: not enough arguments for macro method m2_2_2: (x: Int, y: Int)Unit. +Unspecified value parameter y. m2_2_2()(1) ^ Test_2.scala:47: error: too many arguments for macro method m2_2_2: (x: Int, y: Int)Unit m2_2_2()(1, 2, 3) ^ -Test_2.scala:54: error: macro applications do not support named and/or default arguments +Test_2.scala:54: error: not enough arguments for macro method m2_1_inf: (x: Int, y: Int*)Unit. +Unspecified value parameters x, y. m2_1_inf()() ^ -Test_2.scala:59: error: macro applications do not support named and/or default arguments +Test_2.scala:59: error: not enough arguments for macro method m2_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters x, y, z. m2_2_inf()() ^ -Test_2.scala:60: error: macro applications do not support named and/or default arguments +Test_2.scala:60: error: not enough arguments for macro method m2_2_inf: (x: Int, y: Int, z: Int*)Unit. +Unspecified value parameters y, z. m2_2_inf()(1) ^ 24 errors found diff --git a/test/files/run/macro-expand-default-named.check b/test/files/run/macro-expand-default-named.check new file mode 100644 index 0000000000..2d75772572 --- /dev/null +++ b/test/files/run/macro-expand-default-named.check @@ -0,0 +1,56 @@ +Test.this.one(2, -40) = 42 +Test.this.one(y = -40, x = 2) = 42 +Test.this.one(2, -40) = 42 +Test.this.one(100) = 140 +Test.this.one(y = 100) = -98 +Test.this.one(100) = 140 +Test.this.one() = 42 +Test.this.qualone.one(2, -40) = 42 +Test.this.qualone.one(y = -40, x = 2) = 42 +Test.this.qualone.one(2, -40) = 42 +Test.this.qualone.one(x = 100) = 140 +Test.this.qualone.one(y = 100) = -98 +Test.this.qualone.one(x = 100) = 140 +Test.this.qualone.one() = 42 +Test.this.onezero(2, -40)(1, 2) = 41 +Test.this.onezero(y = -40, x = 2)(z = 3, w = 4) = 41 +Test.this.onezero(2, -40)(5, 6) = 41 +Test.this.onezero(100)(7, 8) = 139 +Test.this.onezero(y = 100)(z = 9, w = 10) = -99 +Test.this.onezero(100)(11, 12) = 139 +Test.this.onezero()(13, 14) = 41 +Test.this.qualonezero.onezero(2, -40)(15, 16) = 41 +Test.this.qualonezero.onezero(y = -40, x = 2)(z = 17, w = 18) = 41 +Test.this.qualonezero.onezero(2, -40)(19, 20) = 41 +Test.this.qualonezero.onezero(x = 100)(z = 21, w = 22) = 139 +Test.this.qualonezero.onezero(y = 100)(z = 23, w = 24) = -99 +Test.this.qualonezero.onezero(x = 100)(z = 25, w = 26) = 139 +Test.this.qualonezero.onezero()(z = 27, w = 28) = 41 +Test.this.zeroone(1, 2)(2, -40) = 41 +Test.this.zeroone(x = 3, y = 4)(w = -40, z = 2) = 41 +Test.this.zeroone(5, 6)(2, -40) = 41 +Test.this.zeroone(x = 7, y = 8)(z = 100) = 139 +Test.this.zeroone(x = 9, y = 10)(w = 100) = -99 +Test.this.zeroone(x = 11, y = 12)(z = 100) = 139 +Test.this.zeroone(x = 13, y = 14)() = 41 +Test.this.qualzeroone.zeroone(15, 16)(2, -40) = 41 +Test.this.qualzeroone.zeroone(x = 17, y = 18)(w = -40, z = 2) = 41 +Test.this.qualzeroone.zeroone(19, 20)(2, -40) = 41 +Test.this.qualzeroone.zeroone(x = 21, y = 22)(z = 100) = 139 +Test.this.qualzeroone.zeroone(x = 23, y = 24)(w = 100) = -99 +Test.this.qualzeroone.zeroone(x = 25, y = 26)(z = 100) = 139 +Test.this.qualzeroone.zeroone(x = 27, y = 28)() = 41 +Test.this.oneone(2, -40)(2, -40) = 84 +Test.this.oneone(y = -40, x = 2)(w = -40, z = 2) = 84 +Test.this.oneone(2, -40)(2, -40) = 84 +Test.this.oneone(x = 100)(z = 100) = 280 +Test.this.oneone(y = 100)(w = 100) = -196 +Test.this.oneone(x = 100)(z = 100) = 280 +Test.this.oneone()() = 84 +Test.this.qualoneone.oneone(2, -40)(2, -40) = 84 +Test.this.qualoneone.oneone(y = -40, x = 2)(w = -40, z = 2) = 84 +Test.this.qualoneone.oneone(2, -40)(2, -40) = 84 +Test.this.qualoneone.oneone(x = 100)(z = 100) = 280 +Test.this.qualoneone.oneone(y = 100)(w = 100) = -196 +Test.this.qualoneone.oneone(x = 100)(z = 100) = 280 +Test.this.qualoneone.oneone()() = 84 diff --git a/test/files/run/macro-expand-default-named/Impls_1.scala b/test/files/run/macro-expand-default-named/Impls_1.scala new file mode 100644 index 0000000000..73774cd56a --- /dev/null +++ b/test/files/run/macro-expand-default-named/Impls_1.scala @@ -0,0 +1,37 @@ +import scala.reflect.macros.blackbox.Context + +object Impls { + def one(c: Context)(x: c.Tree, y: c.Tree) = { + import c.universe._ + val x1 = x orElse q"2" + val y1 = y orElse q"-40" + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1))" + } + + def onezero(c: Context)(x: c.Tree, y: c.Tree)(z: c.Tree, w: c.Tree) = { + import c.universe._ + val x1 = x orElse q"2" + val y1 = y orElse q"-40" + val z1 = z + val w1 = w + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1 + $z1 - $w1))" + } + + def zeroone(c: Context)(x: c.Tree, y: c.Tree)(z: c.Tree, w: c.Tree) = { + import c.universe._ + val x1 = x + val y1 = y + val z1 = z orElse q"2" + val w1 = w orElse q"-40" + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1 + $z1 - $w1))" + } + + def oneone(c: Context)(x: c.Tree, y: c.Tree)(z: c.Tree, w: c.Tree) = { + import c.universe._ + val x1 = x orElse q"2" + val y1 = y orElse q"-40" + val z1 = z orElse q"2" + val w1 = w orElse q"-40" + q"println(${c.macroApplication.toString + " = "} + ($x1 - $y1 + $z1 - $w1))" + } +} \ No newline at end of file diff --git a/test/files/run/macro-expand-default-named/Macros_Test_2.scala b/test/files/run/macro-expand-default-named/Macros_Test_2.scala new file mode 100644 index 0000000000..e58eddd9a3 --- /dev/null +++ b/test/files/run/macro-expand-default-named/Macros_Test_2.scala @@ -0,0 +1,71 @@ +import scala.language.experimental.macros + +object Test extends App { + def one(x: Int = 2, y: Int = -40): Unit = macro Impls.one + one(2, -40) + one(y = -40, x = 2) + one(x = 2, y = -40) + one(x = 100) + one(y = 100) + one(100) + one() + var qualone = this + qualone.one(2, -40) + qualone.one(y = -40, x = 2) + qualone.one(x = 2, y = -40) + qualone.one(x = 100) + qualone.one(y = 100) + qualone.one(100) + qualone.one() + + def onezero(x: Int = 2, y: Int = -40)(z: Int, w: Int): Unit = macro Impls.onezero + onezero(2, -40)(1, 2) + onezero(y = -40, x = 2)(3, 4) + onezero(x = 2, y = -40)(5, 6) + onezero(x = 100)(7, 8) + onezero(y = 100)(9, 10) + onezero(100)(11, 12) + onezero()(13, 14) + var qualonezero = this + qualonezero.onezero(2, -40)(15, 16) + qualonezero.onezero(y = -40, x = 2)(17, 18) + qualonezero.onezero(x = 2, y = -40)(19, 20) + qualonezero.onezero(x = 100)(21, 22) + qualonezero.onezero(y = 100)(23, 24) + qualonezero.onezero(100)(25, 26) + qualonezero.onezero()(27, 28) + + def zeroone(x: Int, y: Int)(z: Int = 2, w: Int = -40): Unit = macro Impls.zeroone + zeroone(1, 2)(2, -40) + zeroone(3, 4)(w = -40, z = 2) + zeroone(5, 6)(z = 2, w = -40) + zeroone(7, 8)(z = 100) + zeroone(9, 10)(w = 100) + zeroone(11, 12)(100) + zeroone(13, 14)() + var qualzeroone = this + qualzeroone.zeroone(15, 16)(2, -40) + qualzeroone.zeroone(17, 18)(w = -40, z = 2) + qualzeroone.zeroone(19, 20)(z = 2, w = -40) + qualzeroone.zeroone(21, 22)(z = 100) + qualzeroone.zeroone(23, 24)(w = 100) + qualzeroone.zeroone(25, 26)(100) + qualzeroone.zeroone(27, 28)() + + def oneone(x: Int = 2, y: Int = -40)(z: Int = 2, w: Int = -40): Unit = macro Impls.oneone + oneone(2, -40)(2, -40) + oneone(y = -40, x = 2)(w = -40, z = 2) + oneone(x = 2, y = -40)(z = 2, w = -40) + oneone(x = 100)(z = 100) + oneone(y = 100)(w = 100) + oneone(100)(100) + oneone()() + var qualoneone = this + qualoneone.oneone(2, -40)(2, -40) + qualoneone.oneone(y = -40, x = 2)(w = -40, z = 2) + qualoneone.oneone(x = 2, y = -40)(z = 2, w = -40) + qualoneone.oneone(x = 100)(z = 100) + qualoneone.oneone(y = 100)(w = 100) + qualoneone.oneone(100)(100) + qualoneone.oneone()() +} \ No newline at end of file diff --git a/test/files/run/macro-expand-ownerchain-a.check b/test/files/run/macro-expand-ownerchain-a.check new file mode 100644 index 0000000000..51993f072d --- /dev/null +++ b/test/files/run/macro-expand-ownerchain-a.check @@ -0,0 +1,2 @@ +2 +2 diff --git a/test/files/run/macro-expand-ownerchain-a/Macros_1.scala b/test/files/run/macro-expand-ownerchain-a/Macros_1.scala new file mode 100644 index 0000000000..0d11c24ad1 --- /dev/null +++ b/test/files/run/macro-expand-ownerchain-a/Macros_1.scala @@ -0,0 +1,11 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +object Macros { + def impl(c: Context)(x: c.Tree, y: c.Tree) = { + import c.universe._ + q"println($x)" + } + + def foo(x: Int, y: Int): Unit = macro impl +} \ No newline at end of file diff --git a/test/files/run/macro-expand-ownerchain-a/Test_2.scala b/test/files/run/macro-expand-ownerchain-a/Test_2.scala new file mode 100644 index 0000000000..738afc75df --- /dev/null +++ b/test/files/run/macro-expand-ownerchain-a/Test_2.scala @@ -0,0 +1,4 @@ +object Test extends App { + Macros.foo(y = 1, x = ((x: Int) => x)(2)) + Macros.foo(y = 1, x = {val x = 2; x}) +} \ No newline at end of file diff --git a/test/files/run/macro-invalidusage-partialapplication-with-tparams.check b/test/files/run/macro-invalidusage-partialapplication-with-tparams.check index 6cbcb9e5af..f1061e00f7 100644 --- a/test/files/run/macro-invalidusage-partialapplication-with-tparams.check +++ b/test/files/run/macro-invalidusage-partialapplication-with-tparams.check @@ -1,3 +1,3 @@ reflective compilation has failed: -too few argument lists for macro invocation +missing arguments for macro method foo in object Macros diff --git a/test/files/run/macro-invalidusage-partialapplication.check b/test/files/run/macro-invalidusage-partialapplication.check index 6cbcb9e5af..f1061e00f7 100644 --- a/test/files/run/macro-invalidusage-partialapplication.check +++ b/test/files/run/macro-invalidusage-partialapplication.check @@ -1,3 +1,3 @@ reflective compilation has failed: -too few argument lists for macro invocation +missing arguments for macro method foo in object Macros diff --git a/test/files/run/reify-repl-fail-gracefully.check b/test/files/run/reify-repl-fail-gracefully.check index 29ccee3cc6..c78d95dbed 100644 --- a/test/files/run/reify-repl-fail-gracefully.check +++ b/test/files/run/reify-repl-fail-gracefully.check @@ -10,7 +10,7 @@ import scala.reflect.runtime.universe._ scala> scala> reify -:12: error: too few argument lists for macro invocation +:12: error: missing arguments for macro method reify in class Universe reify ^ diff --git a/test/pending/run/macro-expand-default.flags b/test/pending/run/macro-expand-default.flags deleted file mode 100644 index cd66464f2f..0000000000 --- a/test/pending/run/macro-expand-default.flags +++ /dev/null @@ -1 +0,0 @@ --language:experimental.macros \ No newline at end of file diff --git a/test/pending/run/macro-expand-default/Impls_1.scala b/test/pending/run/macro-expand-default/Impls_1.scala deleted file mode 100644 index fd5d8d7f18..0000000000 --- a/test/pending/run/macro-expand-default/Impls_1.scala +++ /dev/null @@ -1,10 +0,0 @@ -import scala.reflect.macros.blackbox.Context - -object Impls { - def foo(c: Context)(x: c.Expr[Int], y: c.Expr[Int]) = { - import c.universe._ - val sum = Apply(Select(x.tree, TermName("$minus")), List(y.tree)) - val body = Apply(Select(Ident(definitions.PredefModule), TermName("println")), List(sum)) - Expr[Unit](body) - } -} \ No newline at end of file diff --git a/test/pending/run/macro-expand-default/Macros_Test_2.scala b/test/pending/run/macro-expand-default/Macros_Test_2.scala deleted file mode 100644 index 92fe84d04a..0000000000 --- a/test/pending/run/macro-expand-default/Macros_Test_2.scala +++ /dev/null @@ -1,8 +0,0 @@ -object Test extends App { - def foo(x: Int = 2, y: Int = -40) = macro Impls.foo - foo(y = -40, x = 2) - foo(x = 2, y = -40) - foo(x = 100) - foo(y = 100) - foo() -} \ No newline at end of file diff --git a/test/pending/run/macro-expand-named.flags b/test/pending/run/macro-expand-named.flags deleted file mode 100644 index cd66464f2f..0000000000 --- a/test/pending/run/macro-expand-named.flags +++ /dev/null @@ -1 +0,0 @@ --language:experimental.macros \ No newline at end of file diff --git a/test/pending/run/macro-expand-named/Impls_1.scala b/test/pending/run/macro-expand-named/Impls_1.scala deleted file mode 100644 index fd5d8d7f18..0000000000 --- a/test/pending/run/macro-expand-named/Impls_1.scala +++ /dev/null @@ -1,10 +0,0 @@ -import scala.reflect.macros.blackbox.Context - -object Impls { - def foo(c: Context)(x: c.Expr[Int], y: c.Expr[Int]) = { - import c.universe._ - val sum = Apply(Select(x.tree, TermName("$minus")), List(y.tree)) - val body = Apply(Select(Ident(definitions.PredefModule), TermName("println")), List(sum)) - Expr[Unit](body) - } -} \ No newline at end of file diff --git a/test/pending/run/macro-expand-named/Macros_Test_2.scala b/test/pending/run/macro-expand-named/Macros_Test_2.scala deleted file mode 100644 index abebcf8448..0000000000 --- a/test/pending/run/macro-expand-named/Macros_Test_2.scala +++ /dev/null @@ -1,5 +0,0 @@ -object Test extends App { - def foo(x: Int, y: Int) = macro Impls.foo - foo(y = -40, x = 2) - foo(x = 2, y = -40) -} \ No newline at end of file -- cgit v1.2.3 From ff4cfd575a9b731adc60582de447fafb5303eb49 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 11 Feb 2014 13:53:32 -0800 Subject: SI-8266 Deprecate octal escapes in f-interpolator Also turns the f-interpolator into a migration assistant by suggesting alternatives for the standard escapes. --- .../scala/tools/reflect/FormatInterpolator.scala | 51 +++++++++++++++++++++- src/library/scala/StringContext.scala | 11 +++-- test/files/neg/t8266-invalid-interp.check | 10 +++++ test/files/neg/t8266-invalid-interp.scala | 9 ++++ test/files/run/t8266-octal-interp.check | 30 +++++++++++++ test/files/run/t8266-octal-interp.flags | 1 + test/files/run/t8266-octal-interp.scala | 16 +++++++ 7 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 test/files/neg/t8266-invalid-interp.check create mode 100644 test/files/neg/t8266-invalid-interp.scala create mode 100644 test/files/run/t8266-octal-interp.check create mode 100644 test/files/run/t8266-octal-interp.flags create mode 100644 test/files/run/t8266-octal-interp.scala (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index d5e674ebae..0258002850 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -81,7 +81,56 @@ abstract class FormatInterpolator { case Literal(Constant(x: String)) => x case _ => throw new IllegalArgumentException("internal error: argument parts must be a list of string literals") } - val s = StringContext.treatEscapes(s0) + def escapeHatch: PartialFunction[Throwable, String] = { + // trailing backslash, octal escape, or other + case e: StringContext.InvalidEscapeException => + def errPoint = part.pos withPoint (part.pos.point + e.index) + def octalOf(c: Char) = Character.digit(c, 8) + def alt = { + def altOf(i: Int) = i match { + case '\b' => "\\b" + case '\t' => "\\t" + case '\n' => "\\n" + case '\f' => "\\f" + case '\r' => "\\r" + case '\"' => "\\u0022" // $" in future + case '\'' => "'" + case '\\' => """\\""" + case x => "\\u%04x" format x + } + val suggest = { + val r = "([0-7]{1,3}).*".r + (s0 drop e.index + 1) match { + case r(n) => altOf { (0 /: n) { case (a, o) => (8 * a) + (o - '0') } } + case _ => "" + } + } + val txt = + if ("" == suggest) "" + else s", use $suggest instead" + txt + } + def badOctal = { + def msg(what: String) = s"Octal escape literals are $what$alt." + if (settings.future) { + c.error(errPoint, msg("unsupported")) + s0 + } else { + c.enclosingUnit.deprecationWarning(errPoint, msg("deprecated")) + try StringContext.treatEscapes(s0) catch escapeHatch + } + } + if (e.index == s0.length - 1) { + c.error(errPoint, """Trailing '\' escapes nothing.""") + s0 + } else if (octalOf(s0(e.index + 1)) >= 0) { + badOctal + } else { + c.error(errPoint, e.getMessage) + s0 + } + } + val s = try StringContext.processEscapes(s0) catch escapeHatch val ms = fpat findAllMatchIn s def errorLeading(op: Conversion) = op.errorAt(Spec, s"conversions must follow a splice; ${Conversion.literalHelp}") diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index 2d79452c5d..cd928a2b61 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -172,8 +172,8 @@ object StringContext { * @param str The offending string * @param idx The index of the offending backslash character in `str`. */ - class InvalidEscapeException(str: String, idx: Int) - extends IllegalArgumentException("invalid escape character at index "+idx+" in \""+str+"\"") + class InvalidEscapeException(str: String, @deprecatedName('idx) val index: Int) + extends IllegalArgumentException("invalid escape character at index "+index+" in \""+str+"\"") /** Expands standard Scala escape sequences in a string. * Escape sequences are: @@ -184,7 +184,11 @@ object StringContext { * @param str A string that may contain escape sequences * @return The string with all escape sequences expanded. */ - def treatEscapes(str: String): String = { + def treatEscapes(str: String): String = treatEscapes0(str, strict = false) + + def processEscapes(str: String): String = treatEscapes0(str, strict = true) + + private def treatEscapes0(str: String, strict: Boolean): String = { lazy val bldr = new java.lang.StringBuilder val len = str.length var start = 0 @@ -201,6 +205,7 @@ object StringContext { idx += 1 if (idx >= len) throw new InvalidEscapeException(str, cur) if ('0' <= str(idx) && str(idx) <= '7') { + if (strict) throw new InvalidEscapeException(str, cur) val leadch = str(idx) var oct = leadch - '0' idx += 1 diff --git a/test/files/neg/t8266-invalid-interp.check b/test/files/neg/t8266-invalid-interp.check new file mode 100644 index 0000000000..70dd4081b0 --- /dev/null +++ b/test/files/neg/t8266-invalid-interp.check @@ -0,0 +1,10 @@ +t8266-invalid-interp.scala:4: error: Trailing '\' escapes nothing. + f"a\", + ^ +t8266-invalid-interp.scala:5: error: invalid escape character at index 1 in "a\xc" + f"a\xc", + ^ +t8266-invalid-interp.scala:7: error: invalid escape character at index 1 in "a\vc" + f"a\vc" + ^ +three errors found diff --git a/test/files/neg/t8266-invalid-interp.scala b/test/files/neg/t8266-invalid-interp.scala new file mode 100644 index 0000000000..4b26546880 --- /dev/null +++ b/test/files/neg/t8266-invalid-interp.scala @@ -0,0 +1,9 @@ + +trait X { + def f = Seq( + f"a\", + f"a\xc", + // following could suggest \u000b for vertical tab, similar for \a alert + f"a\vc" + ) +} diff --git a/test/files/run/t8266-octal-interp.check b/test/files/run/t8266-octal-interp.check new file mode 100644 index 0000000000..6e9454119b --- /dev/null +++ b/test/files/run/t8266-octal-interp.check @@ -0,0 +1,30 @@ +t8266-octal-interp.scala:4: warning: Octal escape literals are deprecated, use \b instead. + f"a\10c", + ^ +t8266-octal-interp.scala:5: warning: Octal escape literals are deprecated, use \t instead. + f"a\11c", + ^ +t8266-octal-interp.scala:6: warning: Octal escape literals are deprecated, use \n instead. + f"a\12c", + ^ +t8266-octal-interp.scala:7: warning: Octal escape literals are deprecated, use \r instead. + f"a\15c", + ^ +t8266-octal-interp.scala:8: warning: Octal escape literals are deprecated, use \u0022 instead. + f"a\42c", + ^ +t8266-octal-interp.scala:9: warning: Octal escape literals are deprecated, use \\ instead. + f"a\134c", + ^ +t8266-octal-interp.scala:10: warning: Octal escape literals are deprecated, use \u0069 instead. + f"a\15151515c" + ^ +ac +a c +a +c +a +c +a"c +a\c +ai51515c diff --git a/test/files/run/t8266-octal-interp.flags b/test/files/run/t8266-octal-interp.flags new file mode 100644 index 0000000000..dcc59ebe32 --- /dev/null +++ b/test/files/run/t8266-octal-interp.flags @@ -0,0 +1 @@ +-deprecation diff --git a/test/files/run/t8266-octal-interp.scala b/test/files/run/t8266-octal-interp.scala new file mode 100644 index 0000000000..f85ae0367d --- /dev/null +++ b/test/files/run/t8266-octal-interp.scala @@ -0,0 +1,16 @@ + +trait X { + def f = Seq( + f"a\10c", + f"a\11c", + f"a\12c", + f"a\15c", + f"a\42c", + f"a\134c", + f"a\15151515c" + ) +} + +object Test extends App with X { + f foreach println +} -- cgit v1.2.3 From 609047ba372ceaf06916d3361954bc949a6906ee Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 12 Feb 2014 12:11:40 +0100 Subject: typecheck(q"class C") no longer crashes MemberDefs alone can't be typechecked as is, because namer only names contents of PackageDefs, Templates and Blocks. And, if not named, a tree can't be typed. This commit solves this problem by wrapping typecheckees in a trivial block and then unwrapping the result when it returns back from the typechecker. --- .../scala/reflect/macros/contexts/Typers.scala | 16 ++-- .../scala/tools/reflect/ToolBoxFactory.scala | 104 ++++++++++----------- src/reflect/scala/reflect/internal/Trees.scala | 9 ++ test/files/run/t7185.check | 4 +- test/files/run/typecheck.check | 0 test/files/run/typecheck/Macros_1.scala | 12 +++ test/files/run/typecheck/Test_2.scala | 10 ++ 7 files changed, 86 insertions(+), 69 deletions(-) create mode 100644 test/files/run/typecheck.check create mode 100644 test/files/run/typecheck/Macros_1.scala create mode 100644 test/files/run/typecheck/Test_2.scala (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/reflect/macros/contexts/Typers.scala b/src/compiler/scala/reflect/macros/contexts/Typers.scala index c1ab17027f..f80b43b636 100644 --- a/src/compiler/scala/reflect/macros/contexts/Typers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Typers.scala @@ -16,15 +16,11 @@ trait Typers { def typecheck(tree: Tree, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree = { macroLogVerbose("typechecking %s with expected type %s, implicit views = %s, macros = %s".format(tree, pt, !withImplicitViewsDisabled, !withMacrosDisabled)) val context = callsiteTyper.context - val wrapper1 = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _) - val wrapper2 = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _) - def wrapper (tree: => Tree) = wrapper1(wrapper2(tree)) - // if you get a "silent mode is not available past typer" here - // don't rush to change the typecheck not to use the silent method when the silent parameter is false - // typechecking uses silent anyways (e.g. in typedSelect), so you'll only waste your time - // I'd advise fixing the root cause: finding why the context is not set to report errors - // (also see reflect.runtime.ToolBoxes.typeCheckExpr for a workaround that might work for you) - wrapper(callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(tree), pt), reportAmbiguousErrors = false) match { + val withImplicitFlag = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _) + val withMacroFlag = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _) + def withContext(tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) + def typecheckInternal(tree: Tree) = callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(tree), pt), reportAmbiguousErrors = false) + universe.wrappingIntoTerm(tree)(wrappedTree => withContext(typecheckInternal(wrappedTree) match { case universe.analyzer.SilentResultValue(result) => macroLogVerbose(result) result @@ -32,7 +28,7 @@ trait Typers { macroLogVerbose(error.err.errMsg) if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) universe.EmptyTree - }) + })) } def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index 7bae3203c2..d459b4f981 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -64,7 +64,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => lastSeenContext = null } - def verify(expr: Tree): Unit = { + def verify(expr: Tree): Tree = { // Previously toolboxes used to typecheck their inputs before compiling. // Actually, the initial demo by Martin first typechecked the reified tree, // then ran it, which typechecked it again, and only then launched the @@ -86,14 +86,8 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => |if you have troubles tracking free type variables, consider using -Xlog-free-types """.stripMargin.trim) } - } - - def wrapIntoTerm(tree: Tree): Tree = - if (!tree.isTerm) Block(List(tree), Literal(Constant(()))) else tree - def unwrapFromTerm(tree: Tree): Tree = tree match { - case Block(List(tree), Literal(Constant(()))) => tree - case tree => tree + expr } def extractFreeTerms(expr0: Tree, wrapFreeTermRefs: Boolean): (Tree, scala.collection.mutable.LinkedHashMap[FreeTermSymbol, TermName]) = { @@ -122,53 +116,51 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => (expr, freeTermNames) } - def transformDuringTyper(expr0: Tree, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean)(transform: (analyzer.Typer, Tree) => Tree): Tree = { - verify(expr0) - - // need to wrap the expr, because otherwise you won't be able to typecheck macros against something that contains free vars - val exprAndFreeTerms = extractFreeTerms(expr0, wrapFreeTermRefs = false) - var expr = exprAndFreeTerms._1 - val freeTerms = exprAndFreeTerms._2 - val dummies = freeTerms.map{ case (freeTerm, name) => ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) }.toList - expr = Block(dummies, wrapIntoTerm(expr)) - - // [Eugene] how can we implement that? - // !!! Why is this is in the empty package? If it's only to make - // it inaccessible then please put it somewhere designed for that - // rather than polluting the empty package with synthetics. - val ownerClass = rootMirror.EmptyPackageClass.newClassSymbol(newTypeName("")) - build.setTypeSignature(ownerClass, ClassInfoType(List(ObjectTpe), newScope, ownerClass)) - val owner = ownerClass.newLocalDummy(expr.pos) - val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(expr, owner)) - val wrapper1 = if (!withImplicitViewsDisabled) (currentTyper.context.withImplicitsEnabled[Tree] _) else (currentTyper.context.withImplicitsDisabled[Tree] _) - val wrapper2 = if (!withMacrosDisabled) (currentTyper.context.withMacrosEnabled[Tree] _) else (currentTyper.context.withMacrosDisabled[Tree] _) - def wrapper (tree: => Tree) = wrapper1(wrapper2(tree)) - - val run = new Run - run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works - phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled - currentTyper.context.setReportErrors() // need to manually set context mode, otherwise typer.silent will throw exceptions - reporter.reset() - - val expr1 = wrapper(transform(currentTyper, expr)) - var (dummies1, unwrapped) = expr1 match { - case Block(dummies, unwrapped) => ((dummies, unwrapped)) - case unwrapped => ((Nil, unwrapped)) - } - val invertedIndex = freeTerms map (_.swap) - // todo. also fixup singleton types - unwrapped = new Transformer { - override def transform(tree: Tree): Tree = - tree match { - case Ident(name: TermName) if invertedIndex contains name => - Ident(invertedIndex(name)) setType tree.tpe - case _ => - super.transform(tree) - } - }.transform(unwrapped) - new TreeTypeSubstituter(dummies1 map (_.symbol), dummies1 map (dummy => SingleType(NoPrefix, invertedIndex(dummy.symbol.name.toTermName)))).traverse(unwrapped) - unwrapped = if (expr0.isTerm) unwrapped else unwrapFromTerm(unwrapped) - unwrapped + def transformDuringTyper(expr: Tree, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean)(transform: (analyzer.Typer, Tree) => Tree): Tree = { + wrappingIntoTerm(verify(expr))(expr1 => { + // need to extract free terms, because otherwise you won't be able to typecheck macros against something that contains them + val exprAndFreeTerms = extractFreeTerms(expr1, wrapFreeTermRefs = false) + var expr2 = exprAndFreeTerms._1 + val freeTerms = exprAndFreeTerms._2 + val dummies = freeTerms.map{ case (freeTerm, name) => ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) }.toList + expr2 = Block(dummies, expr2) + + // !!! Why is this is in the empty package? If it's only to make + // it inaccessible then please put it somewhere designed for that + // rather than polluting the empty package with synthetics. + // [Eugene] how can we implement that? + val ownerClass = rootMirror.EmptyPackageClass.newClassSymbol(newTypeName("")) + build.setTypeSignature(ownerClass, ClassInfoType(List(ObjectTpe), newScope, ownerClass)) + val owner = ownerClass.newLocalDummy(expr2.pos) + val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(expr2, owner)) + val withImplicitFlag = if (!withImplicitViewsDisabled) (currentTyper.context.withImplicitsEnabled[Tree] _) else (currentTyper.context.withImplicitsDisabled[Tree] _) + val withMacroFlag = if (!withMacrosDisabled) (currentTyper.context.withMacrosEnabled[Tree] _) else (currentTyper.context.withMacrosDisabled[Tree] _) + def withContext (tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) + + val run = new Run + run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works + phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled + currentTyper.context.setReportErrors() // need to manually set context mode, otherwise typer.silent will throw exceptions + reporter.reset() + + val expr3 = withContext(transform(currentTyper, expr2)) + var (dummies1, result) = expr3 match { + case Block(dummies, result) => ((dummies, result)) + case result => ((Nil, result)) + } + val invertedIndex = freeTerms map (_.swap) + result = new Transformer { + override def transform(tree: Tree): Tree = + tree match { + case Ident(name: TermName) if invertedIndex contains name => + Ident(invertedIndex(name)) setType tree.tpe + case _ => + super.transform(tree) + } + }.transform(result) + new TreeTypeSubstituter(dummies1 map (_.symbol), dummies1 map (dummy => SingleType(NoPrefix, invertedIndex(dummy.symbol.name.toTermName)))).traverse(result) + result + }) } def typecheck(expr: Tree, pt: Type, silent: Boolean, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean): Tree = @@ -207,7 +199,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => } def compile(expr0: Tree): () => Any = { - val expr = wrapIntoTerm(expr0) + val expr = build.SyntacticBlock(expr0 :: Nil) val freeTerms = expr.freeTerms // need to calculate them here, because later on they will be erased val thunks = freeTerms map (fte => () => fte.value) // need to be lazy in order not to distort evaluation order diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 4a518f6c56..91e80f8989 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1676,6 +1676,15 @@ trait Trees extends api.Trees { def duplicateAndKeepPositions(tree: Tree) = new Duplicator(focusPositions = false) transform tree + // this is necessary to avoid crashes like https://github.com/scalamacros/paradise/issues/1 + // when someone tries to c.typecheck a naked MemberDef + def wrappingIntoTerm(tree: Tree)(op: Tree => Tree): Tree = { + op(build.SyntacticBlock(tree :: Nil)) match { + case build.SyntacticBlock(tree :: Nil) => tree + case tree => tree + } + } + // ------ copiers ------------------------------------------- def copyDefDef(tree: Tree)( diff --git a/test/files/run/t7185.check b/test/files/run/t7185.check index ebf85b731f..2b4adf36b4 100644 --- a/test/files/run/t7185.check +++ b/test/files/run/t7185.check @@ -24,9 +24,7 @@ tree: reflect.runtime.universe.Apply = scala> {val tb = reflect.runtime.currentMirror.mkToolBox(); tb.typecheck(tree): Any} res0: Any = { - { - $read.O.apply() - } + $read.O.apply() } scala> diff --git a/test/files/run/typecheck.check b/test/files/run/typecheck.check new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/files/run/typecheck/Macros_1.scala b/test/files/run/typecheck/Macros_1.scala new file mode 100644 index 0000000000..ee1c8da763 --- /dev/null +++ b/test/files/run/typecheck/Macros_1.scala @@ -0,0 +1,12 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +object Macros { + def impl(c: Context) = { + import c.universe._ + c.typecheck(q"class C") + q"()" + } + + def foo: Any = macro impl +} \ No newline at end of file diff --git a/test/files/run/typecheck/Test_2.scala b/test/files/run/typecheck/Test_2.scala new file mode 100644 index 0000000000..01bf5198cc --- /dev/null +++ b/test/files/run/typecheck/Test_2.scala @@ -0,0 +1,10 @@ +import scala.reflect.runtime.universe._ +import scala.reflect.runtime.{currentMirror => cm} +import scala.tools.reflect.ToolBox + +object Test extends App { + Macros.foo + + val tb = cm.mkToolBox() + tb.typecheck(q"class C") +} \ No newline at end of file -- cgit v1.2.3 From c85435d91e26b97e22470af74138b4e8d4c3ae41 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Thu, 13 Feb 2014 02:34:14 +0100 Subject: SI-8270 unconfuses bundles and vanilla macros This fixes a mistake in macro impl ref typechecking that used to have an heuristic to figure out whether it looks at a bundle method ref or at a vanilla object method ref. Under some circumstances the heuristic could fail, and then the macro engine would reject perfectly good macro impls. Now every macro impl ref is typechecked twice - once as a bundle method ref and once as a vanilla object method ref. Results are then analyzed, checked against ambiguities (which are now correctly reported instead of incorrectly prioritizing towards bundles) and delivered to the macro engine. The only heuristic left in place is the one that's used to report errors. If both bundle and vanilla typechecks fail, then if a bundle candidate looks sufficiently similar to a bundle, a bundle typecheck error is reported providing some common bundle definition hints. --- .../macros/compiler/DefaultMacroCompiler.scala | 68 +++- .../scala/reflect/macros/compiler/Errors.scala | 173 ++++++----- .../scala/reflect/macros/compiler/Resolvers.scala | 78 ++--- .../scala/reflect/macros/compiler/Validators.scala | 344 +++++++++++---------- .../scala/tools/nsc/typechecker/Typers.scala | 6 +- test/files/neg/macro-bundle-ambiguous.check | 5 + test/files/neg/macro-bundle-ambiguous.scala | 14 + test/files/neg/macro-bundle-priority-bundle.check | 8 + test/files/neg/macro-bundle-priority-bundle.scala | 14 + .../neg/macro-bundle-priority-nonbundle.check | 8 + .../neg/macro-bundle-priority-nonbundle.scala | 14 + test/files/neg/macro-quasiquotes.check | 2 +- .../pos/macro-bundle-disambiguate-bundle.check | 0 .../pos/macro-bundle-disambiguate-bundle.scala | 14 + .../pos/macro-bundle-disambiguate-nonbundle.check | 0 .../pos/macro-bundle-disambiguate-nonbundle.scala | 14 + 16 files changed, 454 insertions(+), 308 deletions(-) create mode 100644 test/files/neg/macro-bundle-ambiguous.check create mode 100644 test/files/neg/macro-bundle-ambiguous.scala create mode 100644 test/files/neg/macro-bundle-priority-bundle.check create mode 100644 test/files/neg/macro-bundle-priority-bundle.scala create mode 100644 test/files/neg/macro-bundle-priority-nonbundle.check create mode 100644 test/files/neg/macro-bundle-priority-nonbundle.scala create mode 100644 test/files/pos/macro-bundle-disambiguate-bundle.check create mode 100644 test/files/pos/macro-bundle-disambiguate-bundle.scala create mode 100644 test/files/pos/macro-bundle-disambiguate-nonbundle.check create mode 100644 test/files/pos/macro-bundle-disambiguate-nonbundle.scala (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala index 2e82e34bd9..1413065a27 100644 --- a/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala +++ b/src/compiler/scala/reflect/macros/compiler/DefaultMacroCompiler.scala @@ -8,6 +8,11 @@ abstract class DefaultMacroCompiler extends Resolvers with Errors { val global: Global import global._ + import analyzer._ + import treeInfo._ + import definitions._ + val runDefinitions = currentRun.runDefinitions + import runDefinitions.{Predef_???, _} val typer: global.analyzer.Typer val context = typer.context @@ -15,13 +20,72 @@ abstract class DefaultMacroCompiler extends Resolvers val macroDdef: DefDef lazy val macroDef = macroDdef.symbol + case class MacroImplRefCompiler(untypedMacroImplRef: Tree, isImplBundle: Boolean) extends Resolver with Validator with Error private case class MacroImplResolutionException(pos: Position, msg: String) extends Exception def abort(pos: Position, msg: String) = throw MacroImplResolutionException(pos, msg) + /** Resolves a macro impl reference provided in the right-hand side of the given macro definition. + * + * Acceptable shapes of the right-hand side: + * 1) [].[[]] // vanilla macro impl ref + * 2) [].[[]] // shiny new macro bundle impl ref + * + * Produces a tree, which represents a reference to a macro implementation if everything goes well, + * otherwise reports found errors and returns EmptyTree. The resulting tree should have the following format: + * + * qualifier.method[targs] + * + * Qualifier here might be omitted (local macro defs), be a static object (vanilla macro defs) + * or be a dummy instance of a macro bundle (e.g. new MyMacro(???).expand). + */ def resolveMacroImpl: Tree = { + def tryCompile(compiler: MacroImplRefCompiler): scala.util.Try[Tree] = { + try { compiler.validateMacroImplRef(); scala.util.Success(compiler.macroImplRef) } + catch { case ex: MacroImplResolutionException => scala.util.Failure(ex) } + } + val vanillaImplRef = MacroImplRefCompiler(macroDdef.rhs.duplicate, isImplBundle = false) + val (maybeBundleRef, methName, targs) = macroDdef.rhs.duplicate match { + case Applied(Select(Applied(RefTree(qual, bundleName), _, Nil), methName), targs, Nil) => + (RefTree(qual, bundleName.toTypeName), methName, targs) + case Applied(Ident(methName), targs, Nil) => + (Ident(context.owner.enclClass), methName, targs) + case _ => + (EmptyTree, TermName(""), Nil) + } + val bundleImplRef = MacroImplRefCompiler( + atPos(macroDdef.rhs.pos)(gen.mkTypeApply(Select(New(maybeBundleRef, List(List(Ident(Predef_???)))), methName), targs)), + isImplBundle = true + ) + val vanillaResult = tryCompile(vanillaImplRef) + val bundleResult = tryCompile(bundleImplRef) + + def ensureUnambiguousSuccess() = { + // we now face a hard choice of whether to report ambiguity: + // 1) when there are eponymous methods in both bundle and object + // 2) when both references to eponymous methods are resolved successfully + // doing #1 would cause less confusion in the long run, but it would also cause more frequent source incompatibilities + // e.g. it would fail to compile https://github.com/ReifyIt/basis + // therefore here we go for #2 + // if (vanillaImplRef.looksCredible && bundleImplRef.looksCredible) MacroImplAmbiguousError() + if (vanillaResult.isSuccess && bundleResult.isSuccess) MacroImplAmbiguousError() + } + + def reportMostAppropriateFailure() = { + typer.silent(_.typedTypeConstructor(maybeBundleRef)) match { + case SilentResultValue(result) if looksLikeMacroBundleType(result.tpe) => + val bundle = result.tpe.typeSymbol + if (!isMacroBundleType(bundle.tpe)) MacroBundleWrongShapeError() + if (!bundle.owner.isStaticOwner) MacroBundleNonStaticError() + bundleResult.get + case _ => + vanillaResult.get + } + } + try { - validateMacroImplRef() - macroImplRef + if (vanillaResult.isSuccess || bundleResult.isSuccess) ensureUnambiguousSuccess() + if (vanillaResult.isFailure && bundleResult.isFailure) reportMostAppropriateFailure() + vanillaResult.orElse(bundleResult).get } catch { case MacroImplResolutionException(pos, msg) => context.error(pos, msg) diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala index 280baa2a42..490ab3657a 100644 --- a/src/compiler/scala/reflect/macros/compiler/Errors.scala +++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala @@ -13,12 +13,9 @@ trait Errors extends Traces { import treeInfo._ import typer.TyperErrorGen._ import typer.infer.InferErrorGen._ - private val runDefinitions = currentRun.runDefinitions import runDefinitions._ def globalSettings = global.settings - // sanity check errors - private def implRefError(message: String) = { val Applied(culprit, _, _) = macroDdef.rhs abort(culprit.pos, message) @@ -33,111 +30,125 @@ trait Errors extends Traces { abort(culprit.pos, message) } - def MacroImplReferenceWrongShapeError() = implRefError( + def MacroImplAmbiguousError() = implRefError( + "macro implementation reference is ambiguous: makes sense both as\n"+ + "a macro bundle method reference and a vanilla object method reference") + + def MacroBundleNonStaticError() = bundleRefError("macro bundles must be static") + + def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be concrete classes having a single constructor with a `val c: Context` parameter") + + trait Error { + self: MacroImplRefCompiler => + + // sanity check errors + + def MacroImplReferenceWrongShapeError() = implRefError( "macro implementation reference has wrong shape. required:\n"+ "macro [].[[]] or\n" + "macro [].[[]]") - def MacroImplWrongNumberOfTypeArgumentsError() = { - val diagnostic = if (macroImpl.typeParams.length > targs.length) "has too few type arguments" else "has too many arguments" - implRefError(s"macro implementation reference $diagnostic for " + treeSymTypeMsg(macroImplRef)) - } - - def MacroImplNotPublicError() = implRefError("macro implementation must be public") + def MacroImplWrongNumberOfTypeArgumentsError() = { + val diagnostic = if (macroImpl.typeParams.length > targs.length) "has too few type arguments" else "has too many arguments" + implRefError(s"macro implementation reference $diagnostic for " + treeSymTypeMsg(macroImplRef)) + } - def MacroImplOverloadedError() = implRefError("macro implementation cannot be overloaded") + private def macroImplementationWording = + if (isImplBundle) "bundle implementation" + else "macro implementation" - def MacroImplNonTagImplicitParameters(params: List[Symbol]) = implRefError("macro implementations cannot have implicit parameters other than WeakTypeTag evidences") + def MacroImplNotPublicError() = implRefError(s"${macroImplementationWording} must be public") - def MacroBundleNonStaticError() = bundleRefError("macro bundles must be static") + def MacroImplOverloadedError() = implRefError(s"${macroImplementationWording} cannot be overloaded") - def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be concrete classes having a single constructor with a `val c: Context` parameter") + def MacroImplNonTagImplicitParameters(params: List[Symbol]) = implRefError(s"${macroImplementationWording}s cannot have implicit parameters other than WeakTypeTag evidences") - // compatibility errors + // compatibility errors - // helpers + // helpers - private def lengthMsg(flavor: String, violation: String, extra: Symbol) = { - val noun = if (flavor == "value") "parameter" else "type parameter" - val message = noun + " lists have different length, " + violation + " extra " + noun - val suffix = if (extra ne NoSymbol) " " + extra.defString else "" - message + suffix - } + private def lengthMsg(flavor: String, violation: String, extra: Symbol) = { + val noun = if (flavor == "value") "parameter" else "type parameter" + val message = noun + " lists have different length, " + violation + " extra " + noun + val suffix = if (extra ne NoSymbol) " " + extra.defString else "" + message + suffix + } - private def abbreviateCoreAliases(s: String): String = { - val coreAliases = List("WeakTypeTag", "Expr", "Tree") - coreAliases.foldLeft(s)((res, x) => res.replace("c.universe." + x, "c." + x)) - } + private def abbreviateCoreAliases(s: String): String = { + val coreAliases = List("WeakTypeTag", "Expr", "Tree") + coreAliases.foldLeft(s)((res, x) => res.replace("c.universe." + x, "c." + x)) + } - private def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean, untype: Boolean) = { - def preprocess(tpe: Type) = if (untype) untypeMetalevel(tpe) else tpe - var pssPart = (pss map (ps => ps map (p => p.defStringSeenAs(preprocess(p.info))) mkString ("(", ", ", ")"))).mkString - if (abbreviate) pssPart = abbreviateCoreAliases(pssPart) - var retPart = preprocess(restpe).toString - if (abbreviate || macroDdef.tpt.tpe == null) retPart = abbreviateCoreAliases(retPart) - pssPart + ": " + retPart - } + private def showMeth(pss: List[List[Symbol]], restpe: Type, abbreviate: Boolean, untype: Boolean) = { + def preprocess(tpe: Type) = if (untype) untypeMetalevel(tpe) else tpe + var pssPart = (pss map (ps => ps map (p => p.defStringSeenAs(preprocess(p.info))) mkString ("(", ", ", ")"))).mkString + if (abbreviate) pssPart = abbreviateCoreAliases(pssPart) + var retPart = preprocess(restpe).toString + if (abbreviate || macroDdef.tpt.tpe == null) retPart = abbreviateCoreAliases(retPart) + pssPart + ": " + retPart + } - // not exactly an error generator, but very related - // and I dearly wanted to push it away from Macros.scala - private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { - val verbose = macroDebugVerbose - - def check(rtpe: Type, atpe: Type): Boolean = { - def success() = { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } - (rtpe, atpe) match { - case _ if rtpe eq atpe => success() - case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) - case (ExprClassOf(_), TreeType()) if rtpe.prefix =:= atpe.prefix => success() - case (SubtreeType(), ExprClassOf(_)) if rtpe.prefix =:= atpe.prefix => success() - case _ => rtpe <:< atpe + // not exactly an error generator, but very related + // and I dearly wanted to push it away from Macros.scala + private def checkConforms(slot: String, rtpe: Type, atpe: Type) = { + val verbose = macroDebugVerbose + + def check(rtpe: Type, atpe: Type): Boolean = { + def success() = { if (verbose) println(rtpe + " <: " + atpe + "?" + EOL + "true"); true } + (rtpe, atpe) match { + case _ if rtpe eq atpe => success() + case (TypeRef(_, RepeatedParamClass, rtpe :: Nil), TypeRef(_, RepeatedParamClass, atpe :: Nil)) => check(rtpe, atpe) + case (ExprClassOf(_), TreeType()) if rtpe.prefix =:= atpe.prefix => success() + case (SubtreeType(), ExprClassOf(_)) if rtpe.prefix =:= atpe.prefix => success() + case _ => rtpe <:< atpe + } } - } - val ok = - if (verbose) withTypesExplained(check(rtpe, atpe)) - else check(rtpe, atpe) - if (!ok) { - if (!verbose) explainTypes(rtpe, atpe) - val msg = { - val ss = Seq(rtpe, atpe) map (this abbreviateCoreAliases _.toString) - s"type mismatch for $slot: ${ss(0)} does not conform to ${ss(1)}" + val ok = + if (verbose) withTypesExplained(check(rtpe, atpe)) + else check(rtpe, atpe) + if (!ok) { + if (!verbose) explainTypes(rtpe, atpe) + val msg = { + val ss = Seq(rtpe, atpe) map (this abbreviateCoreAliases _.toString) + s"type mismatch for $slot: ${ss(0)} does not conform to ${ss(1)}" + } + compatibilityError(msg) } - compatibilityError(msg) } - } - private def compatibilityError(message: String) = - implRefError( - "macro implementation has incompatible shape:"+ - "\n required: " + showMeth(rparamss, rret, abbreviate = true, untype = false) + - "\n or : " + showMeth(rparamss, rret, abbreviate = true, untype = true) + - "\n found : " + showMeth(aparamss, aret, abbreviate = false, untype = false) + - "\n" + message) + private def compatibilityError(message: String) = + implRefError( + s"${macroImplementationWording} has incompatible shape:"+ + "\n required: " + showMeth(rparamss, rret, abbreviate = true, untype = false) + + "\n or : " + showMeth(rparamss, rret, abbreviate = true, untype = true) + + "\n found : " + showMeth(aparamss, aret, abbreviate = false, untype = false) + + "\n" + message) - def MacroImplParamssMismatchError() = compatibilityError("number of parameter sections differ") + def MacroImplParamssMismatchError() = compatibilityError("number of parameter sections differ") - def MacroImplExtraParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(lengthMsg("value", "found", aparams(rparams.length))) + def MacroImplExtraParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(lengthMsg("value", "found", aparams(rparams.length))) - def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) + def MacroImplMissingParamsError(aparams: List[Symbol], rparams: List[Symbol]) = compatibilityError(abbreviateCoreAliases(lengthMsg("value", "required", rparams(aparams.length)))) - def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("parameter " + rparam.name, rparam.tpe, atpe) + def checkMacroImplParamTypeMismatch(atpe: Type, rparam: Symbol) = checkConforms("parameter " + rparam.name, rparam.tpe, atpe) - def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkConforms("return type", atpe, rret) + def checkMacroImplResultTypeMismatch(atpe: Type, rret: Type) = checkConforms("return type", atpe, rret) - def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) + def MacroImplParamNameMismatchError(aparam: Symbol, rparam: Symbol) = compatibilityError("parameter names differ: " + rparam.name + " != " + aparam.name) - def MacroImplVarargMismatchError(aparam: Symbol, rparam: Symbol) = { - def fail(paramName: Name) = compatibilityError("types incompatible for parameter " + paramName + ": corresponding is not a vararg parameter") - if (isRepeated(rparam) && !isRepeated(aparam)) fail(rparam.name) - if (!isRepeated(rparam) && isRepeated(aparam)) fail(aparam.name) - } + def MacroImplVarargMismatchError(aparam: Symbol, rparam: Symbol) = { + def fail(paramName: Name) = compatibilityError("types incompatible for parameter " + paramName + ": corresponding is not a vararg parameter") + if (isRepeated(rparam) && !isRepeated(aparam)) fail(rparam.name) + if (!isRepeated(rparam) && isRepeated(aparam)) fail(aparam.name) + } - def MacroImplTargMismatchError(atargs: List[Type], atparams: List[Symbol]) = - compatibilityError(NotWithinBoundsErrorMessage("", atargs, atparams, macroDebugVerbose || settings.explaintypes.value)) + def MacroImplTargMismatchError(atargs: List[Type], atparams: List[Symbol]) = + compatibilityError(NotWithinBoundsErrorMessage("", atargs, atparams, macroDebugVerbose || settings.explaintypes.value)) - def MacroImplTparamInstantiationError(atparams: List[Symbol], e: NoInstance) = { - val badps = atparams map (_.defString) mkString ", " - compatibilityError(f"type parameters $badps cannot be instantiated%n${e.getMessage}") + def MacroImplTparamInstantiationError(atparams: List[Symbol], e: NoInstance) = { + val badps = atparams map (_.defString) mkString ", " + compatibilityError(f"type parameters $badps cannot be instantiated%n${e.getMessage}") + } } } diff --git a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala index d35f1c32a9..807fb688a0 100644 --- a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala +++ b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala @@ -12,61 +12,35 @@ trait Resolvers { import definitions._ import treeInfo._ import gen._ - private val runDefinitions = currentRun.runDefinitions - import runDefinitions.{Predef_???, _} + import runDefinitions._ - /** Resolves a macro impl reference provided in the right-hand side of the given macro definition. - * - * Acceptable shapes of the right-hand side: - * 1) [].[[]] // vanilla macro def - * 2) [].[[]] // shiny new macro bundle - * - * Produces a tree, which represents a reference to a macro implementation if everything goes well, - * otherwise reports found errors and returns EmptyTree. The resulting tree should have the following format: - * - * qualifier.method[targs] - * - * Qualifier here might be omitted (local macro defs), be a static object (vanilla macro defs) - * or be a dummy instance of a macro bundle (e.g. new MyMacro(???).expand). - */ - lazy val macroImplRef: Tree = { - val (maybeBundleRef, methName, targs) = macroDdef.rhs match { - case Applied(Select(Applied(RefTree(qual, bundleName), _, Nil), methName), targs, Nil) => - (RefTree(qual, bundleName.toTypeName), methName, targs) - case Applied(Ident(methName), targs, Nil) => - (Ident(context.owner.enclClass), methName, targs) - case _ => - (EmptyTree, TermName(""), Nil) - } + trait Resolver { + self: MacroImplRefCompiler => - val untypedImplRef = typer.silent(_.typedTypeConstructor(maybeBundleRef)) match { - case SilentResultValue(result) if looksLikeMacroBundleType(result.tpe) => - val bundle = result.tpe.typeSymbol - if (!isMacroBundleType(bundle.tpe)) MacroBundleWrongShapeError() - if (!bundle.owner.isStaticOwner) MacroBundleNonStaticError() - atPos(macroDdef.rhs.pos)(gen.mkTypeApply(Select(New(bundle, Ident(Predef_???)), methName), targs)) - case _ => - macroDdef.rhs - } + val isImplBundle: Boolean + val isImplMethod = !isImplBundle - val typedImplRef = typer.silent(_.typed(markMacroImplRef(untypedImplRef)), reportAmbiguousErrors = false) - typedImplRef match { - case SilentResultValue(success) => success - case SilentTypeError(err) => abort(err.errPos, err.errMsg) + lazy val looksCredible: Boolean = { + val Applied(core, _, _) = untypedMacroImplRef + typer.silent(_.typed(markMacroImplRef(core)), reportAmbiguousErrors = false).nonEmpty } - } - // FIXME: cannot write this concisely because of SI-7507 - // lazy val (isImplBundle, macroImplOwner, macroImpl, macroImplTargs) = - private lazy val dissectedMacroImplRef = - macroImplRef match { - case MacroImplReference(isBundle, isBlackbox, owner, meth, targs) => (isBundle, isBlackbox, owner, meth, targs) - case _ => MacroImplReferenceWrongShapeError() - } - lazy val isImplBundle = dissectedMacroImplRef._1 - lazy val isImplMethod = !isImplBundle - lazy val isImplBlackbox = dissectedMacroImplRef._2 - lazy val macroImplOwner = dissectedMacroImplRef._3 - lazy val macroImpl = dissectedMacroImplRef._4 - lazy val targs = dissectedMacroImplRef._5 + lazy val macroImplRef: Tree = + typer.silent(_.typed(markMacroImplRef(untypedMacroImplRef)), reportAmbiguousErrors = false) match { + case SilentResultValue(success) => success + case SilentTypeError(err) => abort(err.errPos, err.errMsg) + } + + // FIXME: cannot write this concisely because of SI-7507 + // lazy val (_, macroImplOwner, macroImpl, macroImplTargs) = + private lazy val dissectedMacroImplRef = + macroImplRef match { + case MacroImplReference(isBundle, isBlackbox, owner, meth, targs) => (isBlackbox, owner, meth, targs) + case _ => MacroImplReferenceWrongShapeError() + } + lazy val isImplBlackbox = dissectedMacroImplRef._1 + lazy val macroImplOwner = dissectedMacroImplRef._2 + lazy val macroImpl = dissectedMacroImplRef._3 + lazy val targs = dissectedMacroImplRef._4 + } } diff --git a/src/compiler/scala/reflect/macros/compiler/Validators.scala b/src/compiler/scala/reflect/macros/compiler/Validators.scala index 02c1f7c431..fc118028dd 100644 --- a/src/compiler/scala/reflect/macros/compiler/Validators.scala +++ b/src/compiler/scala/reflect/macros/compiler/Validators.scala @@ -1,9 +1,7 @@ package scala.reflect.macros package compiler -import java.util.UUID.randomUUID import scala.reflect.internal.Flags._ -import scala.reflect.macros.TypecheckException trait Validators { self: DefaultMacroCompiler => @@ -11,189 +9,193 @@ trait Validators { import global._ import analyzer._ import definitions._ - private val runDefinitions = currentRun.runDefinitions import runDefinitions.{Predef_???, _} - def validateMacroImplRef() = { - sanityCheck() - if (macroImpl != Predef_???) checkMacroDefMacroImplCorrespondence() - } + trait Validator { + self: MacroImplRefCompiler => - private def sanityCheck() = { - if (!macroImpl.isMethod) MacroImplReferenceWrongShapeError() - if (macroImpl.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError() - if (!macroImpl.isPublic) MacroImplNotPublicError() - if (macroImpl.isOverloaded) MacroImplOverloadedError() - val implicitParams = aparamss.flatten filter (_.isImplicit) - if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) - val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner - val declaredInStaticObject = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner - if (!declaredInStaticObject) MacroImplReferenceWrongShapeError() - } + def validateMacroImplRef() = { + sanityCheck() + if (macroImpl != Predef_???) checkMacroDefMacroImplCorrespondence() + } - private def checkMacroDefMacroImplCorrespondence() = { - val atvars = atparams map freshVar - def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) - - // we only check strict correspondence between value parameterss - // type parameters of macro defs and macro impls don't have to coincide with each other - if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() - map2(aparamss, rparamss)((aparams, rparams) => { - if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) - if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) - }) - - try { - // cannot fuse this map2 and the map2 above because if aparamss.flatten != rparamss.flatten - // then `atpeToRtpe` is going to fail with an unsound substitution - map2(aparamss.flatten, rparamss.flatten)((aparam, rparam) => { - if (aparam.name != rparam.name && !rparam.isSynthetic) MacroImplParamNameMismatchError(aparam, rparam) - if (isRepeated(aparam) ^ isRepeated(rparam)) MacroImplVarargMismatchError(aparam, rparam) - val aparamtpe = aparam.tpe match { - case MacroContextType(tpe) => tpe - case tpe => tpe - } - checkMacroImplParamTypeMismatch(atpeToRtpe(aparamtpe), rparam) + private def sanityCheck() = { + if (!macroImpl.isMethod) MacroImplReferenceWrongShapeError() + if (macroImpl.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError() + if (!macroImpl.isPublic) MacroImplNotPublicError() + if (macroImpl.isOverloaded) MacroImplOverloadedError() + val implicitParams = aparamss.flatten filter (_.isImplicit) + if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) + val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner + val effectivelyStatic = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner + val correctBundleness = if (isImplMethod) macroImplOwner.isModuleClass else macroImplOwner.isClass && !macroImplOwner.isModuleClass + if (!effectivelyStatic || !correctBundleness) MacroImplReferenceWrongShapeError() + } + + private def checkMacroDefMacroImplCorrespondence() = { + val atvars = atparams map freshVar + def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars) + + // we only check strict correspondence between value parameterss + // type parameters of macro defs and macro impls don't have to coincide with each other + if (aparamss.length != rparamss.length) MacroImplParamssMismatchError() + map2(aparamss, rparamss)((aparams, rparams) => { + if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams) + if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams) }) - checkMacroImplResultTypeMismatch(atpeToRtpe(aret), rret) + try { + // cannot fuse this map2 and the map2 above because if aparamss.flatten != rparamss.flatten + // then `atpeToRtpe` is going to fail with an unsound substitution + map2(aparamss.flatten, rparamss.flatten)((aparam, rparam) => { + if (aparam.name != rparam.name && !rparam.isSynthetic) MacroImplParamNameMismatchError(aparam, rparam) + if (isRepeated(aparam) ^ isRepeated(rparam)) MacroImplVarargMismatchError(aparam, rparam) + val aparamtpe = aparam.tpe match { + case MacroContextType(tpe) => tpe + case tpe => tpe + } + checkMacroImplParamTypeMismatch(atpeToRtpe(aparamtpe), rparam) + }) - val maxLubDepth = lubDepth(aparamss.flatten map (_.tpe)) max lubDepth(rparamss.flatten map (_.tpe)) - val atargs = solvedTypes(atvars, atparams, atparams map varianceInType(aret), upper = false, maxLubDepth) - val boundsOk = typer.silent(_.infer.checkBounds(macroDdef, NoPrefix, NoSymbol, atparams, atargs, "")) - boundsOk match { - case SilentResultValue(true) => // do nothing, success - case SilentResultValue(false) | SilentTypeError(_) => MacroImplTargMismatchError(atargs, atparams) + checkMacroImplResultTypeMismatch(atpeToRtpe(aret), rret) + + val maxLubDepth = lubDepth(aparamss.flatten map (_.tpe)) max lubDepth(rparamss.flatten map (_.tpe)) + val atargs = solvedTypes(atvars, atparams, atparams map varianceInType(aret), upper = false, maxLubDepth) + val boundsOk = typer.silent(_.infer.checkBounds(macroDdef, NoPrefix, NoSymbol, atparams, atargs, "")) + boundsOk match { + case SilentResultValue(true) => // do nothing, success + case SilentResultValue(false) | SilentTypeError(_) => MacroImplTargMismatchError(atargs, atparams) + } + } catch { + case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex) } - } catch { - case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex) } - } - // aXXX (e.g. aparamss) => characteristics of the actual macro impl signature extracted from the macro impl ("a" stands for "actual") - // rXXX (e.g. rparamss) => characteristics of the reference macro impl signature synthesized from the macro def ("r" stands for "reference") - // FIXME: cannot write this concisely because of SI-7507 - //lazy val MacroImplSig(atparams, aparamss, aret) = macroImplSig - //lazy val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig - lazy val atparams = macroImplSig.tparams - lazy val aparamss = macroImplSig.paramss - lazy val aret = macroImplSig.ret - lazy val rparamss = referenceMacroImplSig.paramss - lazy val rret = referenceMacroImplSig.ret - - // Technically this can be just an alias to MethodType, but promoting it to a first-class entity - // provides better encapsulation and convenient syntax for pattern matching. - private case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) { - private def tparams_s = if (tparams.isEmpty) "" else tparams.map(_.defString).mkString("[", ", ", "]") - private def paramss_s = paramss map (ps => ps.map(s => s"${s.name}: ${s.tpe_*}").mkString("(", ", ", ")")) mkString "" - override def toString = "MacroImplSig(" + tparams_s + paramss_s + ret + ")" - } + // aXXX (e.g. aparamss) => characteristics of the actual macro impl signature extracted from the macro impl ("a" stands for "actual") + // rXXX (e.g. rparamss) => characteristics of the reference macro impl signature synthesized from the macro def ("r" stands for "reference") + // FIXME: cannot write this concisely because of SI-7507 + //lazy val MacroImplSig(atparams, aparamss, aret) = macroImplSig + //lazy val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig + lazy val atparams = macroImplSig.tparams + lazy val aparamss = macroImplSig.paramss + lazy val aret = macroImplSig.ret + lazy val rparamss = referenceMacroImplSig.paramss + lazy val rret = referenceMacroImplSig.ret + + // Technically this can be just an alias to MethodType, but promoting it to a first-class entity + // provides better encapsulation and convenient syntax for pattern matching. + private case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) { + private def tparams_s = if (tparams.isEmpty) "" else tparams.map(_.defString).mkString("[", ", ", "]") + private def paramss_s = paramss map (ps => ps.map(s => s"${s.name}: ${s.tpe_*}").mkString("(", ", ", ")")) mkString "" + override def toString = "MacroImplSig(" + tparams_s + paramss_s + ret + ")" + } - /** An actual macro implementation signature extracted from a macro implementation method. - * - * For the following macro impl: - * def fooBar[T: c.WeakTypeTag] - * (c: scala.reflect.macros.blackbox.Context) - * (xs: c.Expr[List[T]]) - * : c.Expr[T] = ... - * - * This function will return: - * (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] - * - * Note that type tag evidence parameters are not included into the result. - * Type tag context bounds for macro impl tparams are optional. - * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. - * - * This method cannot be reduced to just macroImpl.info, because macro implementations might - * come in different shapes. If the implementation is an apply method of a *box.Macro-compatible object, - * then it won't have (c: *box.Context) in its parameters, but will rather refer to *boxMacro.c. - * - * @param macroImpl The macro implementation symbol - */ - private lazy val macroImplSig: MacroImplSig = { - val tparams = macroImpl.typeParams - val paramss = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => NoSymbol) - val ret = macroImpl.info.finalResultType - MacroImplSig(tparams, paramss, ret) - } + /** An actual macro implementation signature extracted from a macro implementation method. + * + * For the following macro impl: + * def fooBar[T: c.WeakTypeTag] + * (c: scala.reflect.macros.blackbox.Context) + * (xs: c.Expr[List[T]]) + * : c.Expr[T] = ... + * + * This function will return: + * (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] + * + * Note that type tag evidence parameters are not included into the result. + * Type tag context bounds for macro impl tparams are optional. + * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. + * + * This method cannot be reduced to just macroImpl.info, because macro implementations might + * come in different shapes. If the implementation is an apply method of a *box.Macro-compatible object, + * then it won't have (c: *box.Context) in its parameters, but will rather refer to *boxMacro.c. + * + * @param macroImpl The macro implementation symbol + */ + private lazy val macroImplSig: MacroImplSig = { + val tparams = macroImpl.typeParams + val paramss = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => NoSymbol) + val ret = macroImpl.info.finalResultType + MacroImplSig(tparams, paramss, ret) + } - /** A reference macro implementation signature extracted from a given macro definition. - * - * For the following macro def: - * def foo[T](xs: List[T]): T = macro fooBar - * - * This function will return: - * (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] or - * (c: scala.reflect.macros.whitebox.Context)(xs: c.Expr[List[T]])c.Expr[T] - * - * Note that type tag evidence parameters are not included into the result. - * Type tag context bounds for macro impl tparams are optional. - * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. - * - * Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us. - * Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info - * means asking for cyclic reference errors. - * - * We need macro implementation symbol as well, because the return type of the macro definition might be omitted, - * and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can - * use that symbol without a risk of running into cycles. - * - * @param typer Typechecker of `macroDdef` - * @param macroDdef The macro definition tree - * @param macroImpl The macro implementation symbol - */ - private lazy val referenceMacroImplSig: MacroImplSig = { - // had to move method's body to an object because of the recursive dependencies between sigma and param - object SigGenerator { - val cache = scala.collection.mutable.Map[Symbol, Symbol]() - val ctxTpe = if (isImplBlackbox) BlackboxContextClass.tpe else WhiteboxContextClass.tpe - val ctxPrefix = - if (isImplMethod) singleType(NoPrefix, makeParam(nme.macroContext, macroDdef.pos, ctxTpe, SYNTHETIC)) - else singleType(ThisType(macroImpl.owner), macroImpl.owner.tpe.member(nme.c)) - val paramss = - if (isImplMethod) List(ctxPrefix.termSymbol) :: mmap(macroDdef.vparamss)(param) - else mmap(macroDdef.vparamss)(param) - val macroDefRet = - if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe - else computeMacroDefTypeFromMacroImplRef(macroDdef, macroImplRef) orElse AnyTpe - val implReturnType = sigma(increaseMetalevel(ctxPrefix, macroDefRet)) - - object SigmaTypeMap extends TypeMap { - def mapPrefix(pre: Type) = pre match { - case ThisType(sym) if sym == macroDef.owner => - singleType(singleType(ctxPrefix, MacroContextPrefix), ExprValue) - case SingleType(NoPrefix, sym) => - mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) - case _ => - mapOver(pre) - } - def apply(tp: Type): Type = tp match { - case TypeRef(pre, sym, args) => - val pre1 = mapPrefix(pre) - val args1 = mapOverArgs(args, sym.typeParams) - if ((pre eq pre1) && (args eq args1)) tp - else typeRef(pre1, sym, args1) - case _ => - mapOver(tp) + /** A reference macro implementation signature extracted from a given macro definition. + * + * For the following macro def: + * def foo[T](xs: List[T]): T = macro fooBar + * + * This function will return: + * (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] or + * (c: scala.reflect.macros.whitebox.Context)(xs: c.Expr[List[T]])c.Expr[T] + * + * Note that type tag evidence parameters are not included into the result. + * Type tag context bounds for macro impl tparams are optional. + * Therefore compatibility checks ignore such parameters, and we don't need to bother about them here. + * + * Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us. + * Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info + * means asking for cyclic reference errors. + * + * We need macro implementation symbol as well, because the return type of the macro definition might be omitted, + * and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can + * use that symbol without a risk of running into cycles. + * + * @param typer Typechecker of `macroDdef` + * @param macroDdef The macro definition tree + * @param macroImpl The macro implementation symbol + */ + private lazy val referenceMacroImplSig: MacroImplSig = { + // had to move method's body to an object because of the recursive dependencies between sigma and param + object SigGenerator { + val cache = scala.collection.mutable.Map[Symbol, Symbol]() + val ctxTpe = if (isImplBlackbox) BlackboxContextClass.tpe else WhiteboxContextClass.tpe + val ctxPrefix = + if (isImplMethod) singleType(NoPrefix, makeParam(nme.macroContext, macroDdef.pos, ctxTpe, SYNTHETIC)) + else singleType(ThisType(macroImpl.owner), macroImpl.owner.tpe.member(nme.c)) + val paramss = + if (isImplMethod) List(ctxPrefix.termSymbol) :: mmap(macroDdef.vparamss)(param) + else mmap(macroDdef.vparamss)(param) + val macroDefRet = + if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe + else computeMacroDefTypeFromMacroImplRef(macroDdef, macroImplRef) orElse AnyTpe + val implReturnType = sigma(increaseMetalevel(ctxPrefix, macroDefRet)) + + object SigmaTypeMap extends TypeMap { + def mapPrefix(pre: Type) = pre match { + case ThisType(sym) if sym == macroDef.owner => + singleType(singleType(ctxPrefix, MacroContextPrefix), ExprValue) + case SingleType(NoPrefix, sym) => + mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue)) + case _ => + mapOver(pre) + } + def apply(tp: Type): Type = tp match { + case TypeRef(pre, sym, args) => + val pre1 = mapPrefix(pre) + val args1 = mapOverArgs(args, sym.typeParams) + if ((pre eq pre1) && (args eq args1)) tp + else typeRef(pre1, sym, args1) + case _ => + mapOver(tp) + } } + def sigma(tpe: Type): Type = SigmaTypeMap(tpe) + + def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) = + macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe + def param(tree: Tree): Symbol = ( + cache.getOrElseUpdate(tree.symbol, { + val sym = tree.symbol + assert(sym.isTerm, s"sym = $sym, tree = $tree") + makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxPrefix, sym.tpe)), sym.flags) + }) + ) } - def sigma(tpe: Type): Type = SigmaTypeMap(tpe) - - def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) = - macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe - def param(tree: Tree): Symbol = ( - cache.getOrElseUpdate(tree.symbol, { - val sym = tree.symbol - assert(sym.isTerm, s"sym = $sym, tree = $tree") - makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxPrefix, sym.tpe)), sym.flags) - }) - ) - } - import SigGenerator._ - macroLogVerbose(s"generating macroImplSigs for: $macroDdef") - val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) - macroLogVerbose(s"result is: $result") - result + import SigGenerator._ + macroLogVerbose(s"generating macroImplSigs for: $macroDdef") + val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType) + macroLogVerbose(s"result is: $result") + result + } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 1a53fef4aa..093bb46284 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -56,6 +56,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } sealed abstract class SilentResult[+T] { + def isEmpty: Boolean + def nonEmpty = !isEmpty + @inline final def fold[U](none: => U)(f: T => U): U = this match { case SilentResultValue(value) => f(value) case _ => none @@ -74,6 +77,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } class SilentTypeError private(val errors: List[AbsTypeError]) extends SilentResult[Nothing] { + override def isEmpty = true def err: AbsTypeError = errors.head def reportableErrors = errors match { case (e1: AmbiguousImplicitTypeError) +: _ => @@ -87,7 +91,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def unapply(error: SilentTypeError): Option[AbsTypeError] = error.errors.headOption } - case class SilentResultValue[+T](value: T) extends SilentResult[T] { } + case class SilentResultValue[+T](value: T) extends SilentResult[T] { override def isEmpty = false } def newTyper(context: Context): Typer = new NormalTyper(context) diff --git a/test/files/neg/macro-bundle-ambiguous.check b/test/files/neg/macro-bundle-ambiguous.check new file mode 100644 index 0000000000..8430496455 --- /dev/null +++ b/test/files/neg/macro-bundle-ambiguous.check @@ -0,0 +1,5 @@ +macro-bundle-ambiguous.scala:13: error: macro implementation reference is ambiguous: makes sense both as +a macro bundle method reference and a vanilla object method reference + def foo: Unit = macro Macros.impl + ^ +one error found diff --git a/test/files/neg/macro-bundle-ambiguous.scala b/test/files/neg/macro-bundle-ambiguous.scala new file mode 100644 index 0000000000..92c359d9a9 --- /dev/null +++ b/test/files/neg/macro-bundle-ambiguous.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl = ??? +} + +object Macros { + def impl(c: Context) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +} \ No newline at end of file diff --git a/test/files/neg/macro-bundle-priority-bundle.check b/test/files/neg/macro-bundle-priority-bundle.check new file mode 100644 index 0000000000..c6cea72ba6 --- /dev/null +++ b/test/files/neg/macro-bundle-priority-bundle.check @@ -0,0 +1,8 @@ +macro-bundle-priority-bundle.scala:13: error: bundle implementation has incompatible shape: + required: : Macros.this.c.Expr[Unit] + or : : Macros.this.c.Tree + found : (x: Macros.this.c.Tree): Nothing +number of parameter sections differ + def foo: Unit = macro Macros.impl + ^ +one error found diff --git a/test/files/neg/macro-bundle-priority-bundle.scala b/test/files/neg/macro-bundle-priority-bundle.scala new file mode 100644 index 0000000000..ce831a7121 --- /dev/null +++ b/test/files/neg/macro-bundle-priority-bundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl(x: c.Tree) = ??? +} + +object Macros { + def impl(c: Context)(x: c.Tree) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +} \ No newline at end of file diff --git a/test/files/neg/macro-bundle-priority-nonbundle.check b/test/files/neg/macro-bundle-priority-nonbundle.check new file mode 100644 index 0000000000..0d03b5074b --- /dev/null +++ b/test/files/neg/macro-bundle-priority-nonbundle.check @@ -0,0 +1,8 @@ +macro-bundle-priority-nonbundle.scala:13: error: macro implementation has incompatible shape: + required: (c: scala.reflect.macros.whitebox.Context): c.Expr[Unit] + or : (c: scala.reflect.macros.whitebox.Context): c.Tree + found : (c: scala.reflect.macros.whitebox.Context)(x: c.Tree): Nothing +number of parameter sections differ + def foo: Unit = macro Macros.impl + ^ +one error found diff --git a/test/files/neg/macro-bundle-priority-nonbundle.scala b/test/files/neg/macro-bundle-priority-nonbundle.scala new file mode 100644 index 0000000000..8dc00f6dd3 --- /dev/null +++ b/test/files/neg/macro-bundle-priority-nonbundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: scala.reflect.api.Universe) { + def impl(x: c.Tree) = ??? +} + +object Macros { + def impl(c: Context)(x: c.Tree) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +} \ No newline at end of file diff --git a/test/files/neg/macro-quasiquotes.check b/test/files/neg/macro-quasiquotes.check index c690b61fe1..a985aee156 100644 --- a/test/files/neg/macro-quasiquotes.check +++ b/test/files/neg/macro-quasiquotes.check @@ -1,4 +1,4 @@ -Macros_1.scala:14: error: macro implementation has incompatible shape: +Macros_1.scala:14: error: bundle implementation has incompatible shape: required: (x: Impls.this.c.Expr[Int]): Impls.this.c.Expr[Unit] or : (x: Impls.this.c.Tree): Impls.this.c.Tree found : (x: Impls.this.c.universe.Block): Impls.this.c.Tree diff --git a/test/files/pos/macro-bundle-disambiguate-bundle.check b/test/files/pos/macro-bundle-disambiguate-bundle.check new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/files/pos/macro-bundle-disambiguate-bundle.scala b/test/files/pos/macro-bundle-disambiguate-bundle.scala new file mode 100644 index 0000000000..04809317e1 --- /dev/null +++ b/test/files/pos/macro-bundle-disambiguate-bundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl = ??? +} + +object Macros { + def impl(c: Context)(x: c.Tree) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +} \ No newline at end of file diff --git a/test/files/pos/macro-bundle-disambiguate-nonbundle.check b/test/files/pos/macro-bundle-disambiguate-nonbundle.check new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/files/pos/macro-bundle-disambiguate-nonbundle.scala b/test/files/pos/macro-bundle-disambiguate-nonbundle.scala new file mode 100644 index 0000000000..cb66f28a0b --- /dev/null +++ b/test/files/pos/macro-bundle-disambiguate-nonbundle.scala @@ -0,0 +1,14 @@ +import scala.reflect.macros.whitebox._ +import scala.language.experimental.macros + +class Macros(val c: Context) { + def impl(x: c.Tree) = ??? +} + +object Macros { + def impl(c: Context) = ??? +} + +object Test extends App { + def foo: Unit = macro Macros.impl +} \ No newline at end of file -- cgit v1.2.3 From 37f822eb66dc18a2b3bfdaa672081b6f08ef5688 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 13 Feb 2014 22:13:23 -0800 Subject: SI-7711 Do not emit extra argv in script body Take away `argv` and make `args` the standard parameter name. This is a quick fix to avoid "unused local" lint error. All the examples use `args`; in particular, "Step 4. Write some Scala scripts" in "Programming in Scala" uses `args`. I see the footnote there is also where Odersky concatenation is specified, `"Hello, "+ args(0) +"!"` with no space next to the literals. Also removes `argv` from `StdNames`. Was torn whether just to add `argc`. Maybe start a new project to house Names, emeritus. --- src/compiler/scala/tools/nsc/ScriptRunner.scala | 4 ++-- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 9 +++------ src/manual/scala/man1/scala.scala | 6 +++--- src/reflect/scala/reflect/internal/StdNames.scala | 1 - 4 files changed, 8 insertions(+), 12 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index d553d71bf5..c2d62db558 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -19,7 +19,7 @@ import util.Exceptional.unwrap * exec scala "$0" "$@" * !# * Console.println("Hello, world!") - * argv.toList foreach Console.println + * args.toList foreach Console.println * *

And here is a batch file example on Windows XP:

*
@@ -29,7 +29,7 @@ import util.Exceptional.unwrap
  *    goto :eof
  *    ::!#
  *    Console.println("Hello, world!")
- *    argv.toList foreach Console.println
+ *    args.toList foreach Console.println
  *  
* * @author Lex Spoon diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index e3d2bf14a0..5dadbd0825 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -413,12 +413,10 @@ self => * * {{{ * object moduleName { - * def main(argv: Array[String]): Unit = { - * val args = argv + * def main(args: Array[String]): Unit = * new AnyRef { * stmts * } - * } * } * }}} */ @@ -433,9 +431,8 @@ self => // def main def mainParamType = AppliedTypeTree(Ident(tpnme.Array), List(Ident(tpnme.String))) - def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.argv, mainParamType, EmptyTree)) - def mainSetArgv = List(ValDef(NoMods, nme.args, TypeTree(), Ident(nme.argv))) - def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), Block(mainSetArgv, gen.mkAnonymousNew(stmts))) + def mainParameter = List(ValDef(Modifiers(Flags.PARAM), nme.args, mainParamType, EmptyTree)) + def mainDef = DefDef(NoMods, nme.main, Nil, List(mainParameter), scalaDot(tpnme.Unit), gen.mkAnonymousNew(stmts)) // object Main def moduleName = newTermName(ScriptRunner scriptMain settings) diff --git a/src/manual/scala/man1/scala.scala b/src/manual/scala/man1/scala.scala index 6b3be8b77f..92d9c59cca 100644 --- a/src/manual/scala/man1/scala.scala +++ b/src/manual/scala/man1/scala.scala @@ -215,7 +215,7 @@ object scala extends Command { "exec scala \"$0\" \"$@\"\n" + "!#\n" + "Console.println(\"Hello, world!\")\n" + - "argv.toList foreach Console.println"), + "args.toList foreach Console.println"), "Here is a complete Scala script for MS Windows: ", @@ -226,7 +226,7 @@ object scala extends Command { "goto :eof\n" + "::!#\n" + "Console.println(\"Hello, world!\")\n" + - "argv.toList foreach Console.println"), + "args.toList foreach Console.println"), "If you want to use the compilation cache to speed up multiple executions " + "of the script, then add " & Mono("-savecompiled") & " to the scala " + @@ -237,7 +237,7 @@ object scala extends Command { "exec scala -savecompiled \"$0\" \"$@\"\n" + "!#\n" + "Console.println(\"Hello, world!\")\n" + - "argv.toList foreach Console.println")) + "args.toList foreach Console.println")) val exitStatus = Section("EXIT STATUS", diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 679186f938..923102a049 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -640,7 +640,6 @@ trait StdNames { val applyDynamicNamed: NameType = "applyDynamicNamed" val applyOrElse: NameType = "applyOrElse" val args : NameType = "args" - val argv : NameType = "argv" val arrayClass: NameType = "arrayClass" val array_apply : NameType = "array_apply" val array_clone : NameType = "array_clone" -- cgit v1.2.3 From 640e27950001d6eddca89654b93e042a38822557 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 15 Feb 2014 14:14:13 +0100 Subject: SI-3452 A better fix for static forwarder generic sigs The previous commit fixed this in the wrong way. The addition to the test case (testing an inherited method from a base class in addition to the test with a mxin method) still failed. Like mixin, static forwarder generation uses the exact erased siganture of the forwardee for the forwarder. It really ought to use the as-seen-from signature (adding requisite boxing/ unboxing), but until we do that we have to avoid emitting generic signatures that are incoherent. --- .../scala/tools/nsc/backend/jvm/GenASM.scala | 36 +++++++++++++--------- test/files/run/t3452b.check | 1 + test/files/run/t3452b/J_2.java | 1 + test/files/run/t3452b/S_1.scala | 9 +++++- 4 files changed, 31 insertions(+), 16 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index b276572f9b..0474482db9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -821,15 +821,32 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def getCurrentCUnit(): CompilationUnit + final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = { + if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 + else { + // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. + // By rights, it should use the signature as-seen-from the module class, and add suitable + // primitive and value-class boxing/unboxing. + // But for now, just like we did in mixin, we just avoid writing a wrong generic signature + // (one that doesn't erase to the actual signature). See run/t3452b for a test case. + val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) + val erasedMemberType = erasure.erasure(sym)(memberTpe) + if (erasedMemberType =:= sym.info) + getGenericSignature(sym, moduleClass, memberTpe) + else null + } + } + /** @return * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). * - otherwise the signature in question */ def getGenericSignature(sym: Symbol, owner: Symbol): String = { - - if (!needsGenericSignature(sym)) { return null } - val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + getGenericSignature(sym, owner, memberTpe) + } + def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = { + if (!needsGenericSignature(sym)) { return null } val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) if (jsOpt.isEmpty) { return null } @@ -1064,18 +1081,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = ( - // only add generic signature if method concrete; bug #1745 - if (m.isDeferred) null else { - val clazz = module.linkedClassOfClass - val m1 = ( - if ((clazz.info member m.name) eq NoSymbol) - enteringErasure(m.cloneSymbol(clazz, Flags.METHOD | Flags.STATIC)) - else m - ) - getGenericSignature(m1, clazz) - } - ) + val jgensig = staticForwarderGenericSignature(m, module) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) diff --git a/test/files/run/t3452b.check b/test/files/run/t3452b.check index 4f9ed634b4..204c3d0437 100644 --- a/test/files/run/t3452b.check +++ b/test/files/run/t3452b.check @@ -1 +1,2 @@ Search received: test +SearchC received: test diff --git a/test/files/run/t3452b/J_2.java b/test/files/run/t3452b/J_2.java index 24c27b88bd..839f334508 100644 --- a/test/files/run/t3452b/J_2.java +++ b/test/files/run/t3452b/J_2.java @@ -1,5 +1,6 @@ public class J_2 { public static void j() { StringSearch.search("test"); + StringSearch.searchC("test"); } } diff --git a/test/files/run/t3452b/S_1.scala b/test/files/run/t3452b/S_1.scala index 3ab19ed031..a209f12035 100644 --- a/test/files/run/t3452b/S_1.scala +++ b/test/files/run/t3452b/S_1.scala @@ -5,6 +5,13 @@ trait Search[M] { } } -object StringSearch extends Search[String] +class SearchC[M] { + def searchC(input: M): C[Int] = { + println("SearchC received: " + input) + null + } +} + +object StringSearch extends SearchC[String] with Search[String] trait C[T] -- cgit v1.2.3 From 827ac1ffef8e13ed30c366e9802cd0bf5502cb50 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 15 Feb 2014 14:26:10 +0100 Subject: SI-3452 Refactoring code in GenASM For a non copypasta for of reuse in GenBCode. --- .../scala/tools/nsc/backend/jvm/GenASM.scala | 291 +++++++++++---------- 1 file changed, 146 insertions(+), 145 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 0474482db9..01793633f5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -20,7 +20,7 @@ import scala.annotation.tailrec * * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf */ -abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { +abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { self => import global._ import icodes._ import icodes.opcodes._ @@ -803,151 +803,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { annot.args.isEmpty && !annot.matches(DeprecatedAttr) - // @M don't generate java generics sigs for (members of) implementation - // classes, as they are monomorphic (TODO: ok?) - private def needsGenericSignature(sym: Symbol) = !( - // PP: This condition used to include sym.hasExpandedName, but this leads - // to the total loss of generic information if a private member is - // accessed from a closure: both the field and the accessor were generated - // without it. This is particularly bad because the availability of - // generic information could disappear as a consequence of a seemingly - // unrelated change. - settings.Ynogenericsig - || sym.isArtifact - || sym.isLiftedMethod - || sym.isBridge - || (sym.ownerChain exists (_.isImplClass)) - ) - def getCurrentCUnit(): CompilationUnit - final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = { - if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 - else { - // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. - // By rights, it should use the signature as-seen-from the module class, and add suitable - // primitive and value-class boxing/unboxing. - // But for now, just like we did in mixin, we just avoid writing a wrong generic signature - // (one that doesn't erase to the actual signature). See run/t3452b for a test case. - val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) - val erasedMemberType = erasure.erasure(sym)(memberTpe) - if (erasedMemberType =:= sym.info) - getGenericSignature(sym, moduleClass, memberTpe) - else null - } - } - - /** @return - * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). - * - otherwise the signature in question - */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = { - val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) - getGenericSignature(sym, owner, memberTpe) - } - def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type): String = { - if (!needsGenericSignature(sym)) { return null } - - val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) - if (jsOpt.isEmpty) { return null } - - val sig = jsOpt.get - log(sig) // This seems useful enough in the general case. - - def wrap(op: => Unit) = { - try { op; true } - catch { case _: Throwable => false } - } - - if (settings.Xverify) { - // Run the signature parser to catch bogus signatures. - val isValidSignature = wrap { - // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) - import scala.tools.asm.util.CheckClassAdapter - if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar - else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } - else { CheckClassAdapter checkClassSignature sig } - } - - if(!isValidSignature) { - getCurrentCUnit().warning(sym.pos, - """|compiler bug: created invalid generic signature for %s in %s - |signature: %s - |if this is reproducible, please report bug at https://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) - return null - } - } - - if ((settings.check containsName phaseName)) { - val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) - val bytecodeTpe = owner.thisType.memberInfo(sym) - if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { - getCurrentCUnit().warning(sym.pos, - """|compiler bug: created generic signature for %s in %s that does not conform to its erasure - |signature: %s - |original type: %s - |normalized type: %s - |erasure type: %s - |if this is reproducible, please report bug at http://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) - return null - } - } - - sig - } - - def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { - val ca = new Array[Char](bytes.length) - var idx = 0 - while(idx < bytes.length) { - val b: Byte = bytes(idx) - assert((b & ~0x7f) == 0) - ca(idx) = b.asInstanceOf[Char] - idx += 1 - } - - ca - } - - private def arrEncode(sb: ScalaSigBytes): Array[String] = { - var strs: List[String] = Nil - val bSeven: Array[Byte] = sb.sevenBitsMayBeZero - // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) - var prevOffset = 0 - var offset = 0 - var encLength = 0 - while(offset < bSeven.length) { - val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) - val newEncLength = encLength.toLong + deltaEncLength - if(newEncLength >= 65535) { - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - encLength = 0 - prevOffset = offset - } else { - encLength += deltaEncLength - offset += 1 - } - } - if(prevOffset < offset) { - assert(offset == bSeven.length) - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - } - assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? - strs.reverse.toArray - } - - private def strEncode(sb: ScalaSigBytes): String = { - val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) - new java.lang.String(ca) - // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) - // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) - // debug assert(enc(idx) == bvA.getByte(idx + 2)) - // debug assert(bvA.getLength == enc.size + 2) - } + def getGenericSignature(sym: Symbol, owner: Symbol) = self.getGenericSignature(sym, owner, getCurrentCUnit()) def emitArgument(av: asm.AnnotationVisitor, name: String, @@ -1081,7 +939,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = staticForwarderGenericSignature(m, module) + val jgensig = staticForwarderGenericSignature(m, module, getCurrentCUnit()) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) @@ -3320,4 +3178,147 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) + + final def staticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol, unit: CompilationUnit): String = { + if (sym.isDeferred) null // only add generic signature if method concrete; bug #1745 + else { + // SI-3452 Static forwarder generation uses the same erased signature as the method if forwards to. + // By rights, it should use the signature as-seen-from the module class, and add suitable + // primitive and value-class boxing/unboxing. + // But for now, just like we did in mixin, we just avoid writing a wrong generic signature + // (one that doesn't erase to the actual signature). See run/t3452b for a test case. + val memberTpe = enteringErasure(moduleClass.thisType.memberInfo(sym)) + val erasedMemberType = erasure.erasure(sym)(memberTpe) + if (erasedMemberType =:= sym.info) + getGenericSignature(sym, moduleClass, memberTpe, unit) + else null + } + } + + /** @return + * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). + * - otherwise the signature in question + */ + def getGenericSignature(sym: Symbol, owner: Symbol, unit: CompilationUnit): String = { + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + getGenericSignature(sym, owner, memberTpe, unit) + } + def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type, unit: CompilationUnit): String = { + if (!needsGenericSignature(sym)) { return null } + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _: Throwable => false } + } + + if (settings.Xverify) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } // requires asm-util.jar + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if(!isValidSignature) { + unit.warning(sym.pos, + """|compiler bug: created invalid generic signature for %s in %s + |signature: %s + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + unit.warning(sym.pos, + """|compiler bug: created generic signature for %s in %s that does not conform to its erasure + |signature: %s + |original type: %s + |normalized type: %s + |erasure type: %s + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) + return null + } + } + + sig + } + + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.length) + var idx = 0 + while(idx < bytes.length) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + + ca + } + + private def arrEncode(sb: ScalaSigBytes): Array[String] = { + var strs: List[String] = Nil + val bSeven: Array[Byte] = sb.sevenBitsMayBeZero + // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) + var prevOffset = 0 + var offset = 0 + var encLength = 0 + while(offset < bSeven.length) { + val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) + val newEncLength = encLength.toLong + deltaEncLength + if(newEncLength >= 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += deltaEncLength + offset += 1 + } + } + if(prevOffset < offset) { + assert(offset == bSeven.length) + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + } + assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? + strs.reverse.toArray + } + + private def strEncode(sb: ScalaSigBytes): String = { + val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) + new java.lang.String(ca) + // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) + // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) + // debug assert(enc(idx) == bvA.getByte(idx + 2)) + // debug assert(bvA.getLength == enc.size + 2) + } } -- cgit v1.2.3 From c7a5aab0d34243bfec9de2882594927b460b1ec9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 15 Feb 2014 14:30:50 +0100 Subject: SI-3452 Reduce low-hanging duplication bewteen GenASM and GenBCode --- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 125 +-------------------- .../scala/tools/nsc/backend/jvm/GenASM.scala | 2 +- 2 files changed, 4 insertions(+), 123 deletions(-) (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 18ccced75e..feaccd05ed 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -583,53 +583,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { trait BCAnnotGen extends BCInnerClassGen { - /* - * can-multi-thread - */ - def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { - val ca = new Array[Char](bytes.length) - var idx = 0 - while (idx < bytes.length) { - val b: Byte = bytes(idx) - assert((b & ~0x7f) == 0) - ca(idx) = b.asInstanceOf[Char] - idx += 1 - } - - ca - } - - /* - * can-multi-thread - */ - private def arrEncode(sb: ScalaSigBytes): Array[String] = { - var strs: List[String] = Nil - val bSeven: Array[Byte] = sb.sevenBitsMayBeZero - // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) - var prevOffset = 0 - var offset = 0 - var encLength = 0 - while (offset < bSeven.size) { - val deltaEncLength = (if (bSeven(offset) == 0) 2 else 1) - val newEncLength = encLength.toLong + deltaEncLength - if (newEncLength >= 65535) { - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - encLength = 0 - prevOffset = offset - } else { - encLength += deltaEncLength - offset += 1 - } - } - if (prevOffset < offset) { - assert(offset == bSeven.length) - val ba = bSeven.slice(prevOffset, offset) - strs ::= new java.lang.String(ubytesToCharArray(ba)) - } - assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? - mkArrayReverse(strs) - } + import genASM.{ubytesToCharArray, arrEncode} /* * can-multi-thread @@ -676,7 +630,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { av.visit(name, strEncode(sb)) } else { val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } + for(arg <- genASM.arrEncode(sb)) { arrAnnotV.visit(name, arg) } arrAnnotV.visitEnd() } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. @@ -772,25 +726,6 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { trait BCJGenSigGen { - // @M don't generate java generics sigs for (members of) implementation - // classes, as they are monomorphic (TODO: ok?) - /* - * must-single-thread - */ - private def needsGenericSignature(sym: Symbol) = !( - // PP: This condition used to include sym.hasExpandedName, but this leads - // to the total loss of generic information if a private member is - // accessed from a closure: both the field and the accessor were generated - // without it. This is particularly bad because the availability of - // generic information could disappear as a consequence of a seemingly - // unrelated change. - settings.Ynogenericsig - || sym.isArtifact - || sym.isLiftedMethod - || sym.isBridge - || (sym.ownerChain exists (_.isImplClass)) - ) - def getCurrentCUnit(): CompilationUnit /* @return @@ -799,61 +734,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * must-single-thread */ - def getGenericSignature(sym: Symbol, owner: Symbol): String = { - - if (!needsGenericSignature(sym)) { return null } - - val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) - - val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) - if (jsOpt.isEmpty) { return null } - - val sig = jsOpt.get - log(sig) // This seems useful enough in the general case. - - def wrap(op: => Unit) = { - try { op; true } - catch { case _: Throwable => false } - } - - if (settings.Xverify) { - // Run the signature parser to catch bogus signatures. - val isValidSignature = wrap { - // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) - import scala.tools.asm.util.CheckClassAdapter - if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } - else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } - else { CheckClassAdapter checkClassSignature sig } - } - - if (!isValidSignature) { - getCurrentCUnit().warning(sym.pos, - """|compiler bug: created invalid generic signature for %s in %s - |signature: %s - |if this is reproducible, please report bug at https://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) - return null - } - } - - if ((settings.check containsName phaseName)) { - val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) - val bytecodeTpe = owner.thisType.memberInfo(sym) - if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { - getCurrentCUnit().warning(sym.pos, - """|compiler bug: created generic signature for %s in %s that does not conform to its erasure - |signature: %s - |original type: %s - |normalized type: %s - |erasure type: %s - |if this is reproducible, please report bug at http://issues.scala-lang.org/ - """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) - return null - } - } - - sig - } + def getGenericSignature(sym: Symbol, owner: Symbol): String = genASM.getGenericSignature(sym, owner, getCurrentCUnit()) } // end of trait BCJGenSigGen diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 01793633f5..004204ff91 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -3284,7 +3284,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { ca } - private def arrEncode(sb: ScalaSigBytes): Array[String] = { + final def arrEncode(sb: ScalaSigBytes): Array[String] = { var strs: List[String] = Nil val bSeven: Array[Byte] = sb.sevenBitsMayBeZero // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) -- cgit v1.2.3 From 653c404d7f1307251625a5569d53421d27f248cf Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 15 Feb 2014 14:33:31 +0100 Subject: SI-3452 GenBCode version of the static-forwarder signature fix Shares the code with the GenASM version of the fix. --- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 2 +- test/files/run/t3452b-bcode.check | 2 ++ test/files/run/t3452b-bcode.flags | 1 + test/files/run/t3452b-bcode/J_2.java | 6 ++++++ test/files/run/t3452b-bcode/S_1.scala | 17 +++++++++++++++++ test/files/run/t3452b-bcode/S_3.scala | 5 +++++ 6 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t3452b-bcode.check create mode 100644 test/files/run/t3452b-bcode.flags create mode 100644 test/files/run/t3452b-bcode/J_2.java create mode 100644 test/files/run/t3452b-bcode/S_1.scala create mode 100644 test/files/run/t3452b-bcode/S_3.scala (limited to 'src/compiler/scala') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index feaccd05ed..359e5d6c29 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -787,7 +787,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + val jgensig = genASM.staticForwarderGenericSignature(m, module, getCurrentCUnit()) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) diff --git a/test/files/run/t3452b-bcode.check b/test/files/run/t3452b-bcode.check new file mode 100644 index 0000000000..204c3d0437 --- /dev/null +++ b/test/files/run/t3452b-bcode.check @@ -0,0 +1,2 @@ +Search received: test +SearchC received: test diff --git a/test/files/run/t3452b-bcode.flags b/test/files/run/t3452b-bcode.flags new file mode 100644 index 0000000000..c30091d3de --- /dev/null +++ b/test/files/run/t3452b-bcode.flags @@ -0,0 +1 @@ +-Ybackend:GenBCode diff --git a/test/files/run/t3452b-bcode/J_2.java b/test/files/run/t3452b-bcode/J_2.java new file mode 100644 index 0000000000..839f334508 --- /dev/null +++ b/test/files/run/t3452b-bcode/J_2.java @@ -0,0 +1,6 @@ +public class J_2 { + public static void j() { + StringSearch.search("test"); + StringSearch.searchC("test"); + } +} diff --git a/test/files/run/t3452b-bcode/S_1.scala b/test/files/run/t3452b-bcode/S_1.scala new file mode 100644 index 0000000000..a209f12035 --- /dev/null +++ b/test/files/run/t3452b-bcode/S_1.scala @@ -0,0 +1,17 @@ +trait Search[M] { + def search(input: M): C[Int] = { + println("Search received: " + input) + null + } +} + +class SearchC[M] { + def searchC(input: M): C[Int] = { + println("SearchC received: " + input) + null + } +} + +object StringSearch extends SearchC[String] with Search[String] + +trait C[T] diff --git a/test/files/run/t3452b-bcode/S_3.scala b/test/files/run/t3452b-bcode/S_3.scala new file mode 100644 index 0000000000..102b433f47 --- /dev/null +++ b/test/files/run/t3452b-bcode/S_3.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + J_2.j() + } +} -- cgit v1.2.3