diff options
Diffstat (limited to 'src/compiler')
32 files changed, 1063 insertions, 707 deletions
diff --git a/src/compiler/scala/reflect/macros/compiler/Errors.scala b/src/compiler/scala/reflect/macros/compiler/Errors.scala index 4c30a9a85c..280baa2a42 100644 --- a/src/compiler/scala/reflect/macros/compiler/Errors.scala +++ b/src/compiler/scala/reflect/macros/compiler/Errors.scala @@ -51,7 +51,7 @@ trait Errors extends Traces { def MacroBundleNonStaticError() = bundleRefError("macro bundles must be static") - def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be monomorphic traits extending either BlackboxMacro or WhiteboxMacro and not implementing their `val c: BlackboxContext/WhiteboxContext` member") + def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be concrete classes having a single constructor with a `val c: Context` parameter") // compatibility errors diff --git a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala index e4851632a5..d35f1c32a9 100644 --- a/src/compiler/scala/reflect/macros/compiler/Resolvers.scala +++ b/src/compiler/scala/reflect/macros/compiler/Resolvers.scala @@ -40,36 +40,11 @@ trait Resolvers { } val untypedImplRef = typer.silent(_.typedTypeConstructor(maybeBundleRef)) match { - case SilentResultValue(result) if mightBeMacroBundleType(result.tpe) => - val bundleProto = result.tpe.typeSymbol - val bundlePkg = bundleProto.enclosingPackageClass - if (!isMacroBundleProtoType(bundleProto.tpe)) MacroBundleWrongShapeError() - if (!bundleProto.owner.isStaticOwner) MacroBundleNonStaticError() - - // synthesize the bundle, i.e. given a static `trait Foo extends Macro { def expand = ... } ` - // create a top-level definition `class Foo$Bundle(val c: BlackboxContext/WhiteboxContext) extends Foo` in a package next to `Foo` - val bundlePid = gen.mkUnattributedRef(bundlePkg) - val bundlePrefix = - if (bundlePkg == EmptyPackageClass) bundleProto.fullName('$') - else bundleProto.fullName('$').substring(bundlePkg.fullName('$').length + 1) - val bundleName = TypeName(bundlePrefix + tpnme.MACRO_BUNDLE_SUFFIX) - val existingBundle = bundleProto.enclosingPackageClass.info.decl(bundleName) - if (!currentRun.compiles(existingBundle)) { - val contextType = if (isBlackboxMacroBundleType(bundleProto.tpe)) BlackboxContextClass.tpe else WhiteboxContextClass.tpe - def mkContextValDef(flags: Long) = ValDef(Modifiers(flags), nme.c, TypeTree(contextType), EmptyTree) - val contextField = mkContextValDef(PARAMACCESSOR) - val contextParam = mkContextValDef(PARAM | PARAMACCESSOR) - val bundleCtor = DefDef(Modifiers(), nme.CONSTRUCTOR, Nil, List(List(contextParam)), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))) - val bundleParent = gen.mkAppliedTypeTree(Ident(bundleProto), bundleProto.typeParams.map(sym => Ident(sym.name))) - val bundleTemplate = Template(List(bundleParent), noSelfType, List(contextField, bundleCtor)) - val bundle = atPos(bundleProto.pos)(ClassDef(NoMods, bundleName, bundleProto.typeParams.map(TypeDef(_)), bundleTemplate)) - currentRun.compileLate(bundleName + ".scala", PackageDef(bundlePid, List(bundle))) - } - - // synthesize the macro impl reference, which is going to look like: - // `new FooBundle(???).macroName` plus the optional type arguments - val bundleInstance = New(Select(bundlePid, bundleName), List(List(Ident(Predef_???)))) - atPos(macroDdef.rhs.pos)(gen.mkTypeApply(Select(bundleInstance, methName), targs)) + 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 } diff --git a/src/compiler/scala/reflect/macros/compiler/Validators.scala b/src/compiler/scala/reflect/macros/compiler/Validators.scala index 5936b52890..02c1f7c431 100644 --- a/src/compiler/scala/reflect/macros/compiler/Validators.scala +++ b/src/compiler/scala/reflect/macros/compiler/Validators.scala @@ -26,9 +26,9 @@ trait Validators { if (macroImpl.isOverloaded) MacroImplOverloadedError() val implicitParams = aparamss.flatten filter (_.isImplicit) if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams) - val declaredInStaticObject = isImplMethod && (macroImplOwner.isStaticOwner || macroImplOwner.moduleClass.isStaticOwner) - val declaredInTopLevelClass = isImplBundle && macroImplOwner.owner.isPackageClass - if (!declaredInStaticObject && !declaredInTopLevelClass) MacroImplReferenceWrongShapeError() + val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner + val declaredInStaticObject = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner + if (!declaredInStaticObject) MacroImplReferenceWrongShapeError() } private def checkMacroDefMacroImplCorrespondence() = { @@ -93,20 +93,20 @@ trait Validators { * * For the following macro impl: * def fooBar[T: c.WeakTypeTag] - * (c: scala.reflect.macros.BlackboxContext) + * (c: scala.reflect.macros.blackbox.Context) * (xs: c.Expr[List[T]]) * : c.Expr[T] = ... * * This function will return: - * (c: scala.reflect.macros.BlackboxContext)(xs: c.Expr[List[T]])c.Expr[T] + * (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 BlackboxMacro/WhiteboxMacro-compatible object, - * then it won't have (c: BlackboxContext/WhiteboxContext) in its parameters, but will rather refer to BlackboxMacro/WhiteboxMacro.c. + * 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 */ @@ -123,8 +123,8 @@ trait Validators { * def foo[T](xs: List[T]): T = macro fooBar * * This function will return: - * (c: scala.reflect.macros.BlackboxContext)(xs: c.Expr[List[T]])c.Expr[T] or - * (c: scala.reflect.macros.WhiteboxContext)(xs: c.Expr[List[T]])c.Expr[T] + * (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. diff --git a/src/compiler/scala/reflect/macros/contexts/Context.scala b/src/compiler/scala/reflect/macros/contexts/Context.scala index 7b79b52a18..87dac18849 100644 --- a/src/compiler/scala/reflect/macros/contexts/Context.scala +++ b/src/compiler/scala/reflect/macros/contexts/Context.scala @@ -3,8 +3,8 @@ package contexts import scala.tools.nsc.Global -abstract class Context extends scala.reflect.macros.BlackboxContext - with scala.reflect.macros.WhiteboxContext +abstract class Context extends scala.reflect.macros.blackbox.Context + with scala.reflect.macros.whitebox.Context with Aliases with Enclosures with Names diff --git a/src/compiler/scala/reflect/macros/contexts/Enclosures.scala b/src/compiler/scala/reflect/macros/contexts/Enclosures.scala index bb88c8d5e1..5e931817b5 100644 --- a/src/compiler/scala/reflect/macros/contexts/Enclosures.scala +++ b/src/compiler/scala/reflect/macros/contexts/Enclosures.scala @@ -8,10 +8,6 @@ trait Enclosures { import universe._ - type MacroRole = analyzer.MacroRole - def APPLY_ROLE = analyzer.APPLY_ROLE - def macroRole: MacroRole - private lazy val site = callsiteTyper.context private lazy val enclTrees = site.enclosingContextChain map (_.tree) private lazy val enclPoses = enclosingMacros map (_.macroApplication.pos) filterNot (_ eq NoPosition) diff --git a/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala index 450cb4d9ea..ecdd48db22 100644 --- a/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala +++ b/src/compiler/scala/reflect/macros/runtime/JavaReflectionRuntimes.scala @@ -2,7 +2,9 @@ package scala.reflect.macros package runtime import scala.reflect.runtime.ReflectionUtils -import scala.reflect.macros.{Context => ApiContext} +import scala.reflect.macros.blackbox.{Context => BlackboxContext} +import scala.reflect.macros.whitebox.{Context => WhiteboxContext} +import java.lang.reflect.{Constructor => jConstructor} trait JavaReflectionRuntimes { self: scala.tools.nsc.typechecker.Analyzer => @@ -19,8 +21,15 @@ trait JavaReflectionRuntimes { macroLogVerbose(s"successfully loaded macro impl as ($implClass, $implMeth)") args => { val implObj = - if (isBundle) implClass.getConstructor(classOf[ApiContext]).newInstance(args.c) - else ReflectionUtils.staticSingletonInstance(implClass) + if (isBundle) { + def isMacroContext(clazz: Class[_]) = clazz == classOf[BlackboxContext] || clazz == classOf[WhiteboxContext] + def isBundleCtor(ctor: jConstructor[_]) = ctor.getParameterTypes match { + case Array(param) if isMacroContext(param) => true + case _ => false + } + val Array(bundleCtor) = implClass.getConstructors.filter(isBundleCtor) + bundleCtor.newInstance(args.c) + } else ReflectionUtils.staticSingletonInstance(implClass) val implArgs = if (isBundle) args.others else args.c +: args.others implMeth.invoke(implObj, implArgs.asInstanceOf[Seq[AnyRef]]: _*) } diff --git a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala index 7de3341304..5fd9c0db34 100644 --- a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala +++ b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala @@ -4,7 +4,7 @@ package runtime import scala.reflect.internal.Flags._ import scala.reflect.runtime.ReflectionUtils -trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes { +trait MacroRuntimes extends JavaReflectionRuntimes { self: scala.tools.nsc.typechecker.Analyzer => import global._ @@ -19,8 +19,14 @@ trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes * @return Requested runtime if macro implementation can be loaded successfully from either of the mirrors, * `null` otherwise. */ + def macroRuntime(expandee: Tree): MacroRuntime = pluginsMacroRuntime(expandee) + + /** Default implementation of `macroRuntime`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroRuntime for more details) + */ private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime] - def macroRuntime(macroDef: Symbol): MacroRuntime = { + def standardMacroRuntime(expandee: Tree): MacroRuntime = { + val macroDef = expandee.symbol macroLogVerbose(s"looking for macro implementation: $macroDef") if (fastTrack contains macroDef) { macroLogVerbose("macro expansion is serviced by a fast track") @@ -43,8 +49,7 @@ trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes /** Abstracts away resolution of macro runtimes. */ type MacroRuntime = MacroArgs => Any - class MacroRuntimeResolver(val macroDef: Symbol) extends JavaReflectionResolvers - with ScalaReflectionResolvers { + class MacroRuntimeResolver(val macroDef: Symbol) extends JavaReflectionResolvers { val binding = loadMacroImplBinding(macroDef).get val isBundle = binding.isBundle val className = binding.className @@ -57,7 +62,6 @@ trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes try { macroLogVerbose(s"resolving macro implementation as $className.$methName (isBundle = $isBundle)") macroLogVerbose(s"classloader is: ${ReflectionUtils.show(defaultMacroClassloader)}") - // resolveScalaReflectionRuntime(defaultMacroClassloader) resolveJavaReflectionRuntime(defaultMacroClassloader) } catch { case ex: Exception => diff --git a/src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala deleted file mode 100644 index 50f64310f8..0000000000 --- a/src/compiler/scala/reflect/macros/runtime/ScalaReflectionRuntimes.scala +++ /dev/null @@ -1,31 +0,0 @@ -package scala.reflect.macros -package runtime - -import scala.reflect.runtime.{universe => ru} - -trait ScalaReflectionRuntimes { - self: scala.tools.nsc.typechecker.Analyzer => - - trait ScalaReflectionResolvers { - self: MacroRuntimeResolver => - - def resolveScalaReflectionRuntime(classLoader: ClassLoader): MacroRuntime = { - val macroMirror: ru.JavaMirror = ru.runtimeMirror(classLoader) - val implContainerSym = macroMirror.classSymbol(Class.forName(className, true, classLoader)) - val implMethSym = implContainerSym.typeSignature.member(ru.TermName(methName)).asMethod - macroLogVerbose(s"successfully loaded macro impl as ($implContainerSym, $implMethSym)") - args => { - val implContainer = - if (isBundle) { - val implCtorSym = implContainerSym.typeSignature.member(ru.nme.CONSTRUCTOR).asMethod - macroMirror.reflectClass(implContainerSym).reflectConstructor(implCtorSym)(args.c) - } else { - macroMirror.reflectModule(implContainerSym.module.asModule).instance - } - val implMeth = macroMirror.reflect(implContainer).reflectMethod(implMethSym) - val implArgs = if (isBundle) args.others else args.c +: args.others - implMeth(implArgs: _*) - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 5f079a428b..5492e563dd 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1710,25 +1710,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } } - /** Create and compile a synthetic compilation unit from the provided tree. - * - * This needs to create a virtual file underlying the compilation unit in order to appease SBT. - * However this file cannot have a randomly generated name, because then SBT 0.13 goes into a vicious loop - * as described on the mailing list: https://groups.google.com/forum/#!msg/scala-user/r1SgSoVfs0U/Wv4av0LOKukJ - * Therefore I have introduced an additional parameter that makes everyone specify meaningful file names. - */ - def compileLate(virtualFileName: String, code: PackageDef) { - // compatibility with SBT - // on the one hand, we need to specify some jfile here, otherwise sbt crashes with an NPE (SI-6870) - // on the other hand, we can't specify the obvious enclosingUnit, because then sbt somehow fails to run tests using type macros - val fakeJfile = new java.io.File(virtualFileName) - val virtualFile = new VirtualFile(virtualFileName) { override def file = fakeJfile } - val sourceFile = new BatchSourceFile(virtualFile, code.toString) - val unit = new CompilationUnit(sourceFile) - unit.body = code - compileLate(unit) - } - /** Reset package class to state at typer (not sure what this * is needed for?) */ diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 61ea9230a7..d122a1a207 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -623,15 +623,6 @@ self => syntaxError(tpt.pos, "no * parameter type allowed here", skipIt = false) } - /** Check that tree is a legal clause of a forSome. */ - def checkLegalExistential(t: Tree) = t match { - case TypeDef(_, _, _, TypeBoundsTree(_, _)) | - ValDef(_, _, _, EmptyTree) | EmptyTree => - ; - case _ => - syntaxError(t.pos, "not a legal existential clause", skipIt = false) - } - /* -------------- TOKEN CLASSES ------------------------------------------- */ def isModifier: Boolean = in.token match { @@ -885,9 +876,14 @@ self => } } private def makeExistentialTypeTree(t: Tree) = { - val whereClauses = refinement() - whereClauses foreach checkLegalExistential - ExistentialTypeTree(t, whereClauses) + // EmptyTrees in the result of refinement() stand for parse errors + // so it's okay for us to filter them out here + ExistentialTypeTree(t, refinement() flatMap { + case t @ TypeDef(_, _, _, TypeBoundsTree(_, _)) => Some(t) + case t @ ValDef(_, _, _, EmptyTree) => Some(t) + case EmptyTree => None + case _ => syntaxError(t.pos, "not a legal existential clause", skipIt = false); None + }) } /** {{{ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala index 5be5abd895..dd2d63ad17 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala @@ -835,6 +835,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, if (sym.isArtifact) ACC_SYNTHETIC else 0, if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.hasEnumFlag) ACC_ENUM else 0, if (sym.isVarargsMethod) ACC_VARARGS else 0, if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 ) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index e92f8c2541..7e1a82a155 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -244,6 +244,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, if (sym.isArtifact) ACC_SYNTHETIC else 0, if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.hasEnumFlag) ACC_ENUM else 0, if (sym.isVarargsMethod) ACC_VARARGS else 0, if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 ) diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index 7932dd3459..9875d27047 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -792,7 +792,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { val superclazz = AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType)) addCompanionObject(consts ::: statics ::: predefs, atPos(pos) { - ClassDef(mods, name, List(), + ClassDef(mods | Flags.ENUM, name, List(), makeTemplate(superclazz :: interfaces, body)) }) } @@ -811,10 +811,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { skipAhead() accept(RBRACE) } - // The STABLE flag is to signal to namer that this was read from a - // java enum, and so should be given a Constant type (thereby making - // it usable in annotations.) - ValDef(Modifiers(Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr) + ValDef(Modifiers(Flags.ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr) } } diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index 183752d4a2..7837f9a11a 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -126,10 +126,11 @@ object Plugin { } /** Load all plugins specified by the arguments. - * Each of `jars` must be a valid plugin archive or exploded archive. + * Each location of `paths` must be a valid plugin archive or exploded archive. + * Each of `paths` must define one plugin. * Each of `dirs` may be a directory containing arbitrary plugin archives. * Skips all plugins named in `ignoring`. - * A single classloader is created and used to load all of them. + * A classloader is created to load each plugin. */ def loadAllFrom( paths: List[List[Path]], diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 7568c789fb..6ec364bcb6 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -94,11 +94,11 @@ trait ScalaSettings extends AbsScalaSettings val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.") val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)") - val plugin = MultiStringSetting("-Xplugin", "file", "Load one or more plugins from files.") - val disable = MultiStringSetting("-Xplugin-disable", "plugin", "Disable the given plugin(s).") + val plugin = MultiStringSetting("-Xplugin", "paths", "Load a plugin from each classpath.") + val disable = MultiStringSetting("-Xplugin-disable", "plugin", "Disable plugins by name.") val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.") - val require = MultiStringSetting("-Xplugin-require", "plugin", "Abort unless the given plugin(s) are available.") - val pluginsDir = StringSetting ("-Xpluginsdir", "path", "Path to search compiler plugins.", Defaults.scalaPluginPath) + val require = MultiStringSetting("-Xplugin-require", "plugin", "Abort if a named plugin is not loaded.") + val pluginsDir = StringSetting ("-Xpluginsdir", "path", "Path to search for plugin archives.", Defaults.scalaPluginPath) val Xprint = PhasesSetting ("-Xprint", "Print out program after") val writeICode = PhasesSetting ("-Xprint-icode", "Log internal icode to *.icode files after", "icode") val Xprintpos = BooleanSetting ("-Xprint-pos", "Print tree positions, as offsets.") @@ -172,7 +172,8 @@ trait ScalaSettings extends AbsScalaSettings val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") val Ymemberpos = StringSetting ("-Yshow-member-pos", "output style", "Show start and end positions of members", "") withPostSetHook (_ => Yrangepos.value = true) val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.") - val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") + val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal) + val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") withDeprecationMessage(s"Use ${Ymacroexpand.name}:${MacroExpand.None}") withPostSetHook(_ => Ymacroexpand.value = MacroExpand.None) val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects") val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "") @@ -249,4 +250,9 @@ trait ScalaSettings extends AbsScalaSettings def isBCodeAskedFor = (Ybackend.value != "GenASM") def isICodeAskedFor = ((Ybackend.value == "GenASM") || optimiseSettings.exists(_.value) || writeICode.isSetByUser) + object MacroExpand { + val None = "none" + val Normal = "normal" + val Discard = "discard" + } } diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 2b96961291..664645e53e 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -515,7 +515,7 @@ abstract class ClassfileParser { val info = readType() val sym = ownerForFlags(jflags).newValue(name.toTermName, NoPosition, sflags) - // Note: the info may be overrwritten later with a generic signature + // Note: the info may be overwritten later with a generic signature // parsed from SignatureATTR sym setInfo { if (jflags.isEnum) ConstantType(Constant(sym)) diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index 9738769db9..f14fce5de9 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -481,18 +481,33 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { * For instance, say we have a Scala class: * * class Cls { - * // ... - * def someSymbol = `symbolic - * // ... + * def someSymbol1 = 'Symbolic1 + * def someSymbol2 = 'Symbolic2 + * def sameSymbol1 = 'Symbolic1 + * val someSymbol3 = 'Symbolic3 * } * * After transformation, this class looks like this: * * class Cls { - * private "static" val <some_name>$symbolic = Symbol("symbolic") - * // ... - * def someSymbol = <some_name>$symbolic - * // ... + * private <static> var symbol$1: scala.Symbol + * private <static> var symbol$2: scala.Symbol + * private <static> var symbol$3: scala.Symbol + * private val someSymbol3: scala.Symbol + * + * private <static> def <clinit> = { + * symbol$1 = Symbol.apply("Symbolic1") + * symbol$2 = Symbol.apply("Symbolic2") + * } + * + * private def <init> = { + * someSymbol3 = symbol$3 + * } + * + * def someSymbol1 = symbol$1 + * def someSymbol2 = symbol$2 + * def sameSymbol1 = symbol$1 + * val someSymbol3 = someSymbol3 * } * * The reasoning behind this transformation is the following. Symbols get interned - they are stored @@ -502,17 +517,17 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { * is accessed only once during class loading, and after that, the unique symbol is in the static * member. Hence, it is cheap to both reach the unique symbol and do equality checks on it. * - * And, finally, be advised - scala symbol literal and the Symbol class of the compiler + * And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler * have little in common. */ case Apply(fn, (arg @ Literal(Constant(symname: String))) :: Nil) if fn.symbol == Symbol_apply => def transformApply = { - // add the symbol name to a map if it's not there already - val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil) - val staticFieldSym = getSymbolStaticField(tree.pos, symname, rhs, tree) - // create a reference to a static field - val ntree = typedWithPos(tree.pos)(REF(staticFieldSym)) - super.transform(ntree) + // add the symbol name to a map if it's not there already + val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil) + val staticFieldSym = getSymbolStaticField(tree.pos, symname, rhs, tree) + // create a reference to a static field + val ntree = typedWithPos(tree.pos)(REF(staticFieldSym)) + super.transform(ntree) } transformApply diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 844774e75f..ef50ae276f 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -457,12 +457,11 @@ abstract class UnCurry extends InfoTransform else super.transform(tree) case UnApply(fn, args) => - val fn1 = transform(fn) - val args1 = transformTrees(fn.symbol.name match { - case nme.unapply => args - case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, localTyper.expectedPatternTypes(fn, args)) - case _ => sys.error("internal error: UnApply node has wrong symbol") - }) + val fn1 = transform(fn) + val args1 = fn.symbol.name match { + case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, patmat.alignPatterns(tree).expectedTypes) + case _ => args + } treeCopy.UnApply(tree, fn1, args1) case Apply(fn, args) => diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index 63f4a4bf25..699e98f963 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -31,6 +31,30 @@ trait MatchTranslation { trait MatchTranslator extends TreeMakers with TreeMakerWarnings { import typer.context + /** A conservative approximation of which patterns do not discern anything. + * They are discarded during the translation. + */ + object WildcardPattern { + def unapply(pat: Tree): Boolean = pat match { + case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! + case Star(WildcardPattern()) => true + case x: Ident => treeInfo.isVarPattern(x) + case Alternative(ps) => ps forall unapply + case EmptyTree => true + case _ => false + } + } + + object PatternBoundToUnderscore { + def unapply(pat: Tree): Boolean = pat match { + case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol! + case Ident(nme.WILDCARD) => true + case Alternative(ps) => ps forall unapply + case Typed(PatternBoundToUnderscore(), _) => true + case _ => false + } + } + object SymbolBound { def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match { case Bind(_, expr) if hasSym(tree) => Some(tree.symbol -> expr) @@ -86,10 +110,8 @@ trait MatchTranslation { // example check: List[Int] <:< ::[Int] private def extractorStep(): TranslationStep = { - import extractor.{ paramType, treeMaker } - if (!extractor.isTyped) - ErrorUtils.issueNormalTypeError(tree, "Could not typecheck extractor call: "+ extractor)(context) - + def paramType = extractor.aligner.wholeType + import extractor.treeMaker // chain a type-testing extractor before the actual extractor call // it tests the type, checks the outer pointer and casts to the expected type // TODO: the outer check is mandated by the spec for case classes, but we do it for user-defined unapplies as well [SPEC] @@ -355,36 +377,20 @@ trait MatchTranslation { object ExtractorCall { // TODO: check unargs == args def apply(tree: Tree): ExtractorCall = tree match { - case UnApply(unfun, args) => new ExtractorCallRegular(unfun, args) // extractor - case Apply(fun, args) => new ExtractorCallProd(fun, args) // case class + case UnApply(unfun, args) => new ExtractorCallRegular(alignPatterns(tree), unfun, args) // extractor + case Apply(fun, args) => new ExtractorCallProd(alignPatterns(tree), fun, args) // case class } } - abstract class ExtractorCall { + abstract class ExtractorCall(val aligner: PatternAligned) { + import aligner._ def fun: Tree def args: List[Tree] - val nbSubPats = args.length - val starLength = if (hasStar) 1 else 0 - val nonStarLength = args.length - starLength - - // everything okay, captain? - def isTyped: Boolean - def isSeq: Boolean - - private def hasStar = nbSubPats > 0 && isStar(args.last) - private def isNonEmptySeq = nbSubPats > 0 && isSeq - - /** This is special cased so that a single pattern will accept any extractor - * result, even if it's a tuple (SI-6675) - */ - def isSingle = nbSubPats == 1 && !isSeq - - // to which type should the previous binder be casted? - def paramType : Type - - protected def rawSubPatTypes: List[Type] - protected def resultType: Type + // don't go looking for selectors if we only expect one pattern + def rawSubPatTypes = aligner.extractedTypes + def resultInMonad = if (isBool) UnitTpe else typeOfMemberNamedGet(resultType) + def resultType = fun.tpe.finalResultType /** Create the TreeMaker that embodies this extractor call * @@ -407,24 +413,14 @@ trait MatchTranslation { lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) - private def nonStarSubPatTypes = formalTypes(rawInit :+ repeatedType, nonStarLength) + private def nonStarSubPatTypes = aligner.typedNonStarPatterns map (_.tpe) - def subPatTypes: List[Type] = ( - if (rawSubPatTypes.isEmpty || !isSeq) rawSubPatTypes - else if (hasStar) nonStarSubPatTypes :+ sequenceType - else nonStarSubPatTypes - ) - - private def rawGet = typeOfMemberNamedGetOrSelf(resultType) - private def rawInit = rawSubPatTypes dropRight 1 - protected def sequenceType = typeOfLastSelectorOrSelf(rawGet) - protected def elementType = elementTypeOfLastSelectorOrSelf(rawGet) - protected def repeatedType = scalaRepeatedType(elementType) + def subPatTypes: List[Type] = typedPatterns map (_.tpe) - // rawSubPatTypes.last is the Seq, thus there are `rawSubPatTypes.length - 1` non-seq elements in the tuple - protected def firstIndexingBinder = rawSubPatTypes.length - 1 - protected def lastIndexingBinder = nbSubPats - 1 - starLength - protected def expectedLength = lastIndexingBinder - firstIndexingBinder + 1 + // there are `productArity` non-seq elements in the tuple. + protected def firstIndexingBinder = productArity + protected def expectedLength = elementArity + protected def lastIndexingBinder = totalArity - starArity - 1 private def productElemsToN(binder: Symbol, n: Int): List[Tree] = 1 to n map tupleSel(binder) toList private def genTake(binder: Symbol, n: Int): List[Tree] = (0 until n).toList map (codegen index seqTree(binder)) @@ -438,12 +434,12 @@ trait MatchTranslation { // referenced by `binder` protected def subPatRefsSeq(binder: Symbol): List[Tree] = { def lastTrees: List[Tree] = ( - if (!hasStar) Nil + if (!aligner.isStar) Nil else if (expectedLength == 0) seqTree(binder) :: Nil else genDrop(binder, expectedLength) ) // this error-condition has already been checked by checkStarPatOK: - // if(isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if(lastIsStar) 1 else 0) == nbSubPats, "(resultInMonad, ts, subPatTypes, subPats)= "+(resultInMonad, ts, subPatTypes, subPats)) + // if(isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if(lastIsStar) 1 else 0) == totalArity, "(resultInMonad, ts, subPatTypes, subPats)= "+(resultInMonad, ts, subPatTypes, subPats)) // [1] there are `firstIndexingBinder` non-seq tuple elements preceding the Seq // [2] then we have to index the binder that represents the sequence for the remaining subpatterns, except for... @@ -457,8 +453,10 @@ trait MatchTranslation { // the trees that select the subpatterns on the extractor's result, referenced by `binder` // require (nbSubPats > 0 && (!lastIsStar || isSeq)) - protected def subPatRefs(binder: Symbol): List[Tree] = - if (isNonEmptySeq) subPatRefsSeq(binder) else productElemsToN(binder, nbSubPats) + protected def subPatRefs(binder: Symbol): List[Tree] = ( + if (totalArity > 0 && isSeq) subPatRefsSeq(binder) + else productElemsToN(binder, totalArity) + ) private def compareInts(t1: Tree, t2: Tree) = gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil) @@ -478,7 +476,7 @@ trait MatchTranslation { // when the last subpattern is a wildcard-star the expectedLength is but a lower bound // (otherwise equality is required) def compareOp: (Tree, Tree) => Tree = - if (hasStar) _ INT_>= _ + if (aligner.isStar) _ INT_>= _ else _ INT_== _ // `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero` @@ -487,26 +485,14 @@ trait MatchTranslation { def checkedLength: Option[Int] = // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - if (!isSeq || expectedLength < starLength) None + if (!isSeq || expectedLength < starArity) None else Some(expectedLength) } // TODO: to be called when there's a def unapplyProd(x: T): U // U must have N members _1,..., _N -- the _i are type checked, call their type Ti, // for now only used for case classes -- pretending there's an unapplyProd that's the identity (and don't call it) - class ExtractorCallProd(val fun: Tree, val args: List[Tree]) extends ExtractorCall { - private def constructorTp = fun.tpe - - def isTyped = fun.isTyped - - // to which type should the previous binder be casted? - def paramType = constructorTp.finalResultType - def resultType = fun.tpe.finalResultType - - def isSeq = isVarArgTypes(rawSubPatTypes) - - protected def rawSubPatTypes = constructorTp.paramTypes - + class ExtractorCallProd(aligner: PatternAligned, val fun: Tree, val args: List[Tree]) extends ExtractorCall(aligner) { /** Create the TreeMaker that embodies this extractor call * * `binder` has been casted to `paramType` if necessary @@ -535,20 +521,11 @@ trait MatchTranslation { if (accessors isDefinedAt (i-1)) REF(binder) DOT accessors(i-1) else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN } - - override def toString() = s"ExtractorCallProd($fun:${fun.tpe} / ${fun.symbol} / args=$args)" } - class ExtractorCallRegular(extractorCallIncludingDummy: Tree, val args: List[Tree]) extends ExtractorCall { + class ExtractorCallRegular(aligner: PatternAligned, extractorCallIncludingDummy: Tree, val args: List[Tree]) extends ExtractorCall(aligner) { val Unapplied(fun) = extractorCallIncludingDummy - def tpe = fun.tpe - def paramType = firstParamType(tpe) - def resultType = tpe.finalResultType - def isTyped = (tpe ne NoType) && fun.isTyped && (resultInMonad ne ErrorType) - def isSeq = fun.symbol.name == nme.unapplySeq - def isBool = resultType =:= BooleanTpe - /** Create the TreeMaker that embodies this extractor call * * `binder` has been casted to `paramType` if necessary @@ -571,7 +548,7 @@ trait MatchTranslation { ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)( subPatBinders, subPatRefs(binder), - isBool, + aligner.isBool, checkedLength, patBinderOrCasted, ignoredSubPatBinders @@ -583,9 +560,9 @@ trait MatchTranslation { else super.seqTree(binder) // the trees that select the subpatterns on the extractor's result, referenced by `binder` - // require (nbSubPats > 0 && (!lastIsStar || isSeq)) + // require (totalArity > 0 && (!lastIsStar || isSeq)) override protected def subPatRefs(binder: Symbol): List[Tree] = - if (isSingle) REF(binder) :: Nil // special case for extractors + if (aligner.isSingle) REF(binder) :: Nil // special case for extractors else super.subPatRefs(binder) protected def spliceApply(binder: Symbol): Tree = { @@ -606,40 +583,7 @@ trait MatchTranslation { splice transform extractorCallIncludingDummy } - // what's the extractor's result type in the monad? It is the type of its nullary member `get`. - protected lazy val resultInMonad: Type = if (isBool) UnitTpe else typeOfMemberNamedGet(resultType) - - protected lazy val rawSubPatTypes = ( - if (isBool) Nil - else if (isSingle) resultInMonad :: Nil // don't go looking for selectors if we only expect one pattern - else typesOfSelectorsOrSelf(resultInMonad) - ) - - override def toString() = s"ExtractorCallRegular($fun: $tpe / ${fun.symbol})" - } - - /** A conservative approximation of which patterns do not discern anything. - * They are discarded during the translation. - */ - object WildcardPattern { - def unapply(pat: Tree): Boolean = pat match { - case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! - case Star(WildcardPattern()) => true - case x: Ident => treeInfo.isVarPattern(x) - case Alternative(ps) => ps forall unapply - case EmptyTree => true - case _ => false - } - } - - object PatternBoundToUnderscore { - def unapply(pat: Tree): Boolean = pat match { - case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol! - case Ident(nme.WILDCARD) => true - case Alternative(ps) => ps forall unapply - case Typed(PatternBoundToUnderscore(), _) => true - case _ => false - } + override def rawSubPatTypes = aligner.extractor.varargsTypes } } } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala index 7df03044aa..a80f158949 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala @@ -395,8 +395,10 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp))) lazy val outerTestNeeded = ( - !((expectedTp.prefix eq NoPrefix) || expectedTp.prefix.typeSymbol.isPackageClass) - && needsOuterTest(expectedTp, testedBinder.info, matchOwner)) + (expectedTp.prefix ne NoPrefix) + && !expectedTp.prefix.typeSymbol.isPackageClass + && needsOuterTest(expectedTp, testedBinder.info, matchOwner) + ) // the logic to generate the run-time test that follows from the fact that // a `prevBinder` is expected to have type `expectedTp` @@ -406,44 +408,52 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging { def renderCondition(cs: TypeTestCondStrategy): cs.Result = { import cs._ - def default = - // do type test first to ensure we won't select outer on null - if (outerTestNeeded) and(typeTest(testedBinder, expectedTp), outerTest(testedBinder, expectedTp)) - else typeTest(testedBinder, expectedTp) - // propagate expected type def expTp(t: Tree): t.type = t setType expectedTp + def testedWide = testedBinder.info.widen + def expectedWide = expectedTp.widen + def isAnyRef = testedWide <:< AnyRefTpe + def isAsExpected = testedWide <:< expectedTp + def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp) + def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe) + def mkNullTest = nonNullTest(testedBinder) + def mkOuterTest = outerTest(testedBinder, expectedTp) + def mkTypeTest = typeTest(testedBinder, expectedWide) + + def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder) + def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder) + def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res + + // If we conform to expected primitive type: + // it cannot be null and cannot have an outer pointer. No further checking. + // If we conform to expected reference type: + // have to test outer and non-null + // If we do not conform to expected type: + // have to test type and outer (non-null is implied by successful type test) + def mkDefault = ( + if (isExpectedPrimitiveType) tru + else addOuterTest( + if (isExpectedReferenceType) mkNullTest + else mkTypeTest + ) + ) + // true when called to type-test the argument to an extractor // don't do any fancy equality checking, just test the type - if (extractorArgTypeTest) default + // TODO: verify that we don't need to special-case Array + // I think it's okay: + // - the isInstanceOf test includes a test for the element type + // - Scala's arrays are invariant (so we don't drop type tests unsoundly) + if (extractorArgTypeTest) mkDefault else expectedTp match { - // TODO: [SPEC] the spec requires `eq` instead of `==` for singleton types - // this implies sym.isStable - case SingleType(_, sym) => and(equalsTest(gen.mkAttributedQualifier(expectedTp), testedBinder), typeTest(testedBinder, expectedTp.widen)) - // must use == to support e.g. List() == Nil - case ThisType(sym) if sym.isModule => and(equalsTest(CODE.REF(sym), testedBinder), typeTest(testedBinder, expectedTp.widen)) - case ConstantType(Constant(null)) if testedBinder.info.widen <:< AnyRefTpe - => eqTest(expTp(CODE.NULL), testedBinder) - case ConstantType(const) => equalsTest(expTp(Literal(const)), testedBinder) - case ThisType(sym) => eqTest(expTp(This(sym)), testedBinder) - - // TODO: verify that we don't need to special-case Array - // I think it's okay: - // - the isInstanceOf test includes a test for the element type - // - Scala's arrays are invariant (so we don't drop type tests unsoundly) - case _ if testedBinder.info.widen <:< expectedTp => - // if the expected type is a primitive value type, it cannot be null and it cannot have an outer pointer - // since the types conform, no further checking is required - if (isPrimitiveValueType(expectedTp)) tru - // have to test outer and non-null only when it's a reference type - else if (expectedTp <:< AnyRefTpe) { - // do non-null check first to ensure we won't select outer on null - if (outerTestNeeded) and(nonNullTest(testedBinder), outerTest(testedBinder, expectedTp)) - else nonNullTest(testedBinder) - } else default - - case _ => default + // TODO: [SPEC] the spec requires `eq` instead of `==` for singleton types - this implies sym.isStable + case SingleType(_, sym) => and(mkEqualsTest(gen.mkAttributedQualifier(expectedTp)), mkTypeTest) + case ThisType(sym) if sym.isModule => and(mkEqualsTest(CODE.REF(sym)), mkTypeTest) // must use == to support e.g. List() == Nil + case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(CODE.NULL)) + case ConstantType(const) => mkEqualsTest(expTp(Literal(const))) + case ThisType(sym) => mkEqTest(expTp(This(sym))) + case _ => mkDefault } } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala new file mode 100644 index 0000000000..e84ccbf754 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala @@ -0,0 +1,155 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Paul Phillips + */ + +package scala +package tools +package nsc +package transform +package patmat + +/** An extractor returns: F1, F2, ..., Fi, opt[Seq[E] or E*] + * A case matches: P1, P2, ..., Pj, opt[Seq[E]] + * Put together: P1/F1, P2/F2, ... Pi/Fi, Pi+1/E, Pi+2/E, ... Pj/E, opt[Seq[E]] + * + * Here Pm/Fi is the last pattern to match the fixed arity section. + * + * productArity: the value of i, i.e. the number of non-sequence types in the extractor + * nonStarArity: the value of j, i.e. the number of non-star patterns in the case definition + * elementArity: j - i, i.e. the number of non-star patterns which must match sequence elements + * starArity: 1 or 0 based on whether there is a star (sequence-absorbing) pattern + * totalArity: nonStarArity + starArity, i.e. the number of patterns in the case definition + * + * Note that productArity is a function only of the extractor, and + * nonStar/star/totalArity are all functions of the patterns. The key + * value for aligning and typing the patterns is elementArity, as it + * is derived from both sets of information. + */ +trait PatternExpander[Pattern, Type] { + /** You'll note we're not inside the cake. "Pattern" and "Type" are + * arbitrary types here, and NoPattern and NoType arbitrary values. + */ + def NoPattern: Pattern + def NoType: Type + + /** It's not optimal that we're carrying both sequence and repeated + * type here, but the implementation requires more unraveling before + * it can be avoided. + * + * sequenceType is Seq[T], elementType is T, repeatedType is T*. + */ + sealed case class Repeated(sequenceType: Type, elementType: Type, repeatedType: Type) { + def exists = elementType != NoType + + def elementList = if (exists) elementType :: Nil else Nil + def sequenceList = if (exists) sequenceType :: Nil else Nil + def repeatedList = if (exists) repeatedType :: Nil else Nil + + override def toString = s"${elementType}*" + } + object NoRepeated extends Repeated(NoType, NoType, NoType) { + override def toString = "<none>" + } + + final case class Patterns(fixed: List[Pattern], star: Pattern) { + def hasStar = star != NoPattern + def starArity = if (hasStar) 1 else 0 + def nonStarArity = fixed.length + def totalArity = nonStarArity + starArity + def starPatterns = if (hasStar) star :: Nil else Nil + def all = fixed ::: starPatterns + + override def toString = all mkString ", " + } + + /** An 'extractor' can be a case class or an unapply or unapplySeq method. + * Decoding what it is that they extract takes place before we arrive here, + * so that this class can concentrate only on the relationship between + * patterns and types. + * + * In a case class, the class is the unextracted type and the fixed and + * repeated types are derived from its constructor parameters. + * + * In an unapply, this is reversed: the parameter to the unapply is the + * unextracted type, and the other types are derived based on the return + * type of the unapply method. + * + * In other words, this case class and unapply are encoded the same: + * + * case class Foo(x: Int, y: Int, zs: Char*) + * def unapplySeq(x: Foo): Option[(Int, Int, Seq[Char])] + * + * Both are Extractor(Foo, Int :: Int :: Nil, Repeated(Seq[Char], Char, Char*)) + * + * @param whole The type in its unextracted form + * @param fixed The non-sequence types which are extracted + * @param repeated The sequence type which is extracted + */ + final case class Extractor(whole: Type, fixed: List[Type], repeated: Repeated) { + require(whole != NoType, s"expandTypes($whole, $fixed, $repeated)") + + def productArity = fixed.length + def hasSeq = repeated.exists + def elementType = repeated.elementType + def sequenceType = repeated.sequenceType + def allTypes = fixed ::: repeated.sequenceList + def varargsTypes = fixed ::: repeated.repeatedList + def isErroneous = allTypes contains NoType + + private def typeStrings = fixed.map("" + _) ::: ( if (hasSeq) List("" + repeated) else Nil ) + + def offeringString = if (isErroneous) "<error>" else typeStrings match { + case Nil => "Boolean" + case tp :: Nil => tp + case tps => tps.mkString("(", ", ", ")") + } + override def toString = "%s => %s".format(whole, offeringString) + } + + final case class TypedPat(pat: Pattern, tpe: Type) { + override def toString = s"$pat: $tpe" + } + + /** If elementArity is... + * 0: A perfect match between extractor and the fixed patterns. + * If there is a star pattern it will match any sequence. + * > 0: There are more patterns than products. There will have to be a + * sequence which can populate at least <elementArity> patterns. + * < 0: There are more products than patterns: compile time error. + */ + final case class Aligned(patterns: Patterns, extractor: Extractor) { + def elementArity = patterns.nonStarArity - productArity + def productArity = extractor.productArity + def starArity = patterns.starArity + def totalArity = patterns.totalArity + + def wholeType = extractor.whole + def sequenceType = extractor.sequenceType + def productTypes = extractor.fixed + def extractedTypes = extractor.allTypes + def typedNonStarPatterns = products ::: elements + def typedPatterns = typedNonStarPatterns ::: stars + + def isBool = !isSeq && productArity == 0 + def isSingle = !isSeq && totalArity == 1 + def isStar = patterns.hasStar + def isSeq = extractor.hasSeq + + private def typedAsElement(pat: Pattern) = TypedPat(pat, extractor.elementType) + private def typedAsSequence(pat: Pattern) = TypedPat(pat, extractor.sequenceType) + private def productPats = patterns.fixed take productArity + private def elementPats = patterns.fixed drop productArity + private def products = (productPats, productTypes).zipped map TypedPat + private def elements = elementPats map typedAsElement + private def stars = patterns.starPatterns map typedAsSequence + + override def toString = s""" + |Aligned { + | patterns $patterns + | extractor $extractor + | arities $productArity/$elementArity/$starArity // product/element/star + | typed ${typedPatterns mkString ", "} + |}""".stripMargin.trim + } +} diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index 394ba98f17..f6c960d089 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -34,7 +34,8 @@ import scala.reflect.internal.util.Position * - recover GADT typing by locally inserting implicit witnesses to type equalities derived from the current case, and considering these witnesses during subtyping (?) * - recover exhaustivity/unreachability of user-defined extractors by partitioning the types they match on using an HList or similar type-level structure */ -trait PatternMatching extends Transform with TypingTransformers +trait PatternMatching extends Transform + with TypingTransformers with Debugging with Interface with MatchTranslation @@ -45,7 +46,8 @@ trait PatternMatching extends Transform with TypingTransformers with Solving with MatchAnalysis with MatchOptimization - with MatchWarnings { + with MatchWarnings + with ScalacPatternExpanders { import global._ val phaseName: String = "patmat" diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala new file mode 100644 index 0000000000..7858cb5586 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala @@ -0,0 +1,154 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Paul Phillips + */ + +package scala +package tools +package nsc +package transform +package patmat + +/** This is scalac-specific logic layered on top of the scalac-agnostic + * "matching products to patterns" logic defined in PatternExpander. + */ +trait ScalacPatternExpanders { + val global: Global + + import global._ + import definitions._ + import treeInfo._ + + type PatternAligned = ScalacPatternExpander#Aligned + + implicit class AlignedOps(val aligned: PatternAligned) { + import aligned._ + def expectedTypes = typedPatterns map (_.tpe) + def unexpandedFormals = extractor.varargsTypes + } + trait ScalacPatternExpander extends PatternExpander[Tree, Type] { + def NoPattern = EmptyTree + def NoType = global.NoType + + def newPatterns(patterns: List[Tree]): Patterns = patterns match { + case init :+ last if isStar(last) => Patterns(init, last) + case _ => Patterns(patterns, NoPattern) + } + def elementTypeOf(tpe: Type) = { + val seq = repeatedToSeq(tpe) + + ( typeOfMemberNamedHead(seq) + orElse typeOfMemberNamedApply(seq) + orElse definitions.elementType(ArrayClass, seq) + ) + } + def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor = + logResult(s"newExtractor($whole, $fixed, $repeated")(Extractor(whole, fixed, repeated)) + + // Turn Seq[A] into Repeated(Seq[A], A, A*) + def repeatedFromSeq(seqType: Type): Repeated = { + val elem = elementTypeOf(seqType) + val repeated = scalaRepeatedType(elem) + + Repeated(seqType, elem, repeated) + } + // Turn A* into Repeated(Seq[A], A, A*) + def repeatedFromVarargs(repeated: Type): Repeated = + Repeated(repeatedToSeq(repeated), repeatedToSingle(repeated), repeated) + + /** In this case we are basing the pattern expansion on a case class constructor. + * The argument is the MethodType carried by the primary constructor. + */ + def applyMethodTypes(method: Type): Extractor = { + val whole = method.finalResultType + + method.paramTypes match { + case init :+ last if isScalaRepeatedParamType(last) => newExtractor(whole, init, repeatedFromVarargs(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + + /** In this case, expansion is based on an unapply or unapplySeq method. + * Unfortunately the MethodType does not carry the information of whether + * it was unapplySeq, so we have to funnel that information in separately. + */ + def unapplyMethodTypes(method: Type, isSeq: Boolean): Extractor = { + val whole = firstParamType(method) + val result = method.finalResultType + val expanded = ( + if (result =:= BooleanTpe) Nil + else typeOfMemberNamedGet(result) match { + case rawGet if !hasSelectors(rawGet) => rawGet :: Nil + case rawGet => typesOfSelectors(rawGet) + } + ) + expanded match { + case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + } + object alignPatterns extends ScalacPatternExpander { + /** Converts a T => (A, B, C) extractor to a T => ((A, B, CC)) extractor. + */ + def tupleExtractor(extractor: Extractor): Extractor = + extractor.copy(fixed = tupleType(extractor.fixed) :: Nil) + + private def validateAligned(tree: Tree, aligned: Aligned): Aligned = { + import aligned._ + + def owner = tree.symbol.owner + def offering = extractor.offeringString + def symString = tree.symbol.fullLocationString + def offerString = if (extractor.isErroneous) "" else s" offering $offering" + def arityExpected = ( if (extractor.hasSeq) "at least " else "" ) + productArity + + def err(msg: String) = currentUnit.error(tree.pos, msg) + def warn(msg: String) = currentUnit.warning(tree.pos, msg) + def arityError(what: String) = err(s"$what patterns for $owner$offerString: expected $arityExpected, found $totalArity") + + if (isStar && !isSeq) + err("Star pattern must correspond with varargs or unapplySeq") + else if (elementArity < 0) + arityError("not enough") + else if (elementArity > 0 && !extractor.hasSeq) + arityError("too many") + + aligned + } + + def apply(sel: Tree, args: List[Tree]): Aligned = { + val fn = sel match { + case Unapplied(fn) => fn + case _ => sel + } + val patterns = newPatterns(args) + val isSeq = sel.symbol.name == nme.unapplySeq + val isUnapply = sel.symbol.name == nme.unapply + val extractor = sel.symbol.name match { + case nme.unapply => unapplyMethodTypes(fn.tpe, isSeq = false) + case nme.unapplySeq => unapplyMethodTypes(fn.tpe, isSeq = true) + case _ => applyMethodTypes(fn.tpe) + } + + /** Rather than let the error that is SI-6675 pollute the entire matching + * process, we will tuple the extractor before creation Aligned so that + * it contains known good values. + */ + def productArity = extractor.productArity + def acceptMessage = if (extractor.isErroneous) "" else s" to hold ${extractor.offeringString}" + val requiresTupling = isUnapply && patterns.totalArity == 1 && productArity > 1 + + if (settings.lint && requiresTupling && effectivePatternArity(args) == 1) + currentUnit.warning(sel.pos, s"${sel.symbol.owner} expects $productArity patterns$acceptMessage but crushing into $productArity-tuple to fit single pattern (SI-6675)") + + val normalizedExtractor = if (requiresTupling) tupleExtractor(extractor) else extractor + validateAligned(fn, Aligned(patterns, normalizedExtractor)) + } + + def apply(tree: Tree): Aligned = tree match { + case Apply(fn, args) => apply(fn, args) + case UnApply(fn, args) => apply(fn, args) + } + } +} diff --git a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala index 54e4fefc15..fa6e5399eb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala +++ b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala @@ -13,7 +13,6 @@ package typechecker trait AnalyzerPlugins { self: Analyzer => import global._ - trait AnalyzerPlugin { /** * Selectively activate this analyzer plugin, e.g. according to the compiler phase. @@ -156,6 +155,117 @@ trait AnalyzerPlugins { self: Analyzer => def pluginsTypedReturn(tpe: Type, typer: Typer, tree: Return, pt: Type): Type = tpe } + /** + * @define nonCumulativeReturnValueDoc Returns `None` if the plugin doesn't want to customize the default behavior + * or something else if the plugin knows better that the implementation provided in scala-compiler.jar. + * If multiple plugins return a non-empty result, it's going to be a compilation error. + */ + trait MacroPlugin { + /** + * Selectively activate this analyzer plugin, e.g. according to the compiler phase. + * + * Note that the current phase can differ from the global compiler phase (look for `enteringPhase` + * invocations in the compiler). For instance, lazy types created by the UnPickler are completed + * at the phase in which their symbol is created. Observations show that this can even be the + * parser phase. Since symbol completion can trigger subtyping, typing etc, your plugin might + * need to be active also in phases other than namer and typer. + * + * Typically, this method can be implemented as + * + * global.phase.id < global.currentRun.picklerPhase.id + */ + def isActive(): Boolean = true + + /** + * Typechecks the right-hand side of a macro definition (which typically features + * a mere reference to a macro implementation). + * + * Default implementation provided in `self.standardTypedMacroBody` makes sure that the rhs + * resolves to a reference to a method in either a static object or a macro bundle, + * verifies that the referred method is compatible with the macro def and upon success + * attaches a macro impl binding to the macro def's symbol. + * + * $nonCumulativeReturnValueDoc. + */ + def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Option[Tree] = None + + /** + * Expands an application of a def macro (i.e. of a symbol that has the MACRO flag set), + * possibly using the current typer mode and the provided prototype. + * + * Default implementation provided in `self.standardMacroExpand` figures out whether the `expandee` + * needs to be expanded right away or its expansion has to be delayed until all undetermined + * parameters are inferred, then loads the macro implementation using `self.pluginsMacroRuntime`, + * prepares the invocation arguments for the macro implementation using `self.pluginsMacroArgs`, + * and finally calls into the macro implementation. After the call returns, it typechecks + * the expansion and performs some bookkeeping. + * + * This method is typically implemented if your plugin requires significant changes to the macro engine. + * If you only need to customize the macro context, consider implementing `pluginsMacroArgs`. + * If you only need to customize how macro implementation are invoked, consider going for `pluginsMacroRuntime`. + * + * $nonCumulativeReturnValueDoc. + */ + def pluginsMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Option[Tree] = None + + /** + * Computes the arguments that need to be passed to the macro impl corresponding to a particular expandee. + * + * Default implementation provided in `self.standardMacroArgs` instantiates a `scala.reflect.macros.contexts.Context`, + * gathers type and value arguments of the macro application and throws them together into `MacroArgs`. + * + * $nonCumulativeReturnValueDoc. + */ + def pluginsMacroArgs(typer: Typer, expandee: Tree): Option[MacroArgs] = None + + /** + * Summons a function that encapsulates macro implementation invocations for a particular expandee. + * + * Default implementation provided in `self.standardMacroRuntime` returns a function that + * loads the macro implementation binding from the macro definition symbol, + * then uses either Java or Scala reflection to acquire the method that corresponds to the impl, + * and then reflectively calls into that method. + * + * $nonCumulativeReturnValueDoc. + */ + def pluginsMacroRuntime(expandee: Tree): Option[MacroRuntime] = None + + /** + * Creates a symbol for the given tree in lexical context encapsulated by the given namer. + * + * Default implementation provided in `namer.standardEnterSym` handles MemberDef's and Imports, + * doing nothing for other trees (DocDef's are seen through and rewrapped). Typical implementation + * of `enterSym` for a particular tree flavor creates a corresponding symbol, assigns it to the tree, + * enters the symbol into scope and then might even perform some code generation. + * + * $nonCumulativeReturnValueDoc. + */ + def pluginsEnterSym(namer: Namer, tree: Tree): Boolean = false + + /** + * Makes sure that for the given class definition, there exists a companion object definition. + * + * Default implementation provided in `namer.standardEnsureCompanionObject` looks up a companion symbol for the class definition + * and then checks whether the resulting symbol exists or not. If it exists, then nothing else is done. + * If not, a synthetic object definition is created using the provided factory, which is then entered into namer's scope. + * + * $nonCumulativeReturnValueDoc. + */ + def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Option[Symbol] = None + + /** + * Prepares a list of statements for being typechecked by performing domain-specific type-agnostic code synthesis. + * + * Trees passed into this method are going to be named, but not typed. + * In particular, you can rely on the compiler having called `enterSym` on every stat prior to passing calling this method. + * + * Default implementation does nothing. Current approaches to code syntheses (generation of underlying fields + * for getters/setters, creation of companion objects for case classes, etc) are too disparate and ad-hoc + * to be treated uniformly, so I'm leaving this for future work. + */ + def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = stats + } + /** A list of registered analyzer plugins */ @@ -167,59 +277,158 @@ trait AnalyzerPlugins { self: Analyzer => analyzerPlugins = plugin :: analyzerPlugins } + private abstract class CumulativeOp[T] { + def default: T + def accumulate: (T, AnalyzerPlugin) => T + } + + private def invoke[T](op: CumulativeOp[T]): T = { + if (analyzerPlugins.isEmpty) op.default + else analyzerPlugins.foldLeft(op.default)((current, plugin) => + if (!plugin.isActive()) current else op.accumulate(current, plugin)) + } /** @see AnalyzerPlugin.pluginsPt */ def pluginsPt(pt: Type, typer: Typer, tree: Tree, mode: Mode): Type = + // performance opt if (analyzerPlugins.isEmpty) pt - else analyzerPlugins.foldLeft(pt)((pt, plugin) => - if (!plugin.isActive()) pt else plugin.pluginsPt(pt, typer, tree, mode)) + else invoke(new CumulativeOp[Type] { + def default = pt + def accumulate = (pt, p) => p.pluginsPt(pt, typer, tree, mode) + }) /** @see AnalyzerPlugin.pluginsTyped */ - def pluginsTyped(tpe: Type, typer: Typer, tree: Tree, mode: Mode, pt: Type): Type = { - // support deprecated methods in annotation checkers - val annotCheckersTpe = addAnnotations(tree, tpe) - if (analyzerPlugins.isEmpty) annotCheckersTpe - else analyzerPlugins.foldLeft(annotCheckersTpe)((tpe, plugin) => - if (!plugin.isActive()) tpe else plugin.pluginsTyped(tpe, typer, tree, mode, pt)) - } + def pluginsTyped(tpe: Type, typer: Typer, tree: Tree, mode: Mode, pt: Type): Type = + // performance opt + if (analyzerPlugins.isEmpty) addAnnotations(tree, tpe) + else invoke(new CumulativeOp[Type] { + // support deprecated methods in annotation checkers + def default = addAnnotations(tree, tpe) + def accumulate = (tpe, p) => p.pluginsTyped(tpe, typer, tree, mode, pt) + }) /** @see AnalyzerPlugin.pluginsTypeSig */ - def pluginsTypeSig(tpe: Type, typer: Typer, defTree: Tree, pt: Type): Type = - if (analyzerPlugins.isEmpty) tpe - else analyzerPlugins.foldLeft(tpe)((tpe, plugin) => - if (!plugin.isActive()) tpe else plugin.pluginsTypeSig(tpe, typer, defTree, pt)) + def pluginsTypeSig(tpe: Type, typer: Typer, defTree: Tree, pt: Type): Type = invoke(new CumulativeOp[Type] { + def default = tpe + def accumulate = (tpe, p) => p.pluginsTypeSig(tpe, typer, defTree, pt) + }) /** @see AnalyzerPlugin.pluginsTypeSigAccessor */ - def pluginsTypeSigAccessor(tpe: Type, typer: Typer, tree: ValDef, sym: Symbol): Type = - if (analyzerPlugins.isEmpty) tpe - else analyzerPlugins.foldLeft(tpe)((tpe, plugin) => - if (!plugin.isActive()) tpe else plugin.pluginsTypeSigAccessor(tpe, typer, tree, sym)) + def pluginsTypeSigAccessor(tpe: Type, typer: Typer, tree: ValDef, sym: Symbol): Type = invoke(new CumulativeOp[Type] { + def default = tpe + def accumulate = (tpe, p) => p.pluginsTypeSigAccessor(tpe, typer, tree, sym) + }) /** @see AnalyzerPlugin.canAdaptAnnotations */ - def canAdaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Boolean = { + def canAdaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Boolean = invoke(new CumulativeOp[Boolean] { // support deprecated methods in annotation checkers - val annotCheckersExists = global.canAdaptAnnotations(tree, mode, pt) - annotCheckersExists || { - if (analyzerPlugins.isEmpty) false - else analyzerPlugins.exists(plugin => - plugin.isActive() && plugin.canAdaptAnnotations(tree, typer, mode, pt)) - } - } + def default = global.canAdaptAnnotations(tree, mode, pt) + def accumulate = (curr, p) => curr || p.canAdaptAnnotations(tree, typer, mode, pt) + }) /** @see AnalyzerPlugin.adaptAnnotations */ - def adaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Tree = { + def adaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Tree = invoke(new CumulativeOp[Tree] { // support deprecated methods in annotation checkers - val annotCheckersTree = global.adaptAnnotations(tree, mode, pt) - if (analyzerPlugins.isEmpty) annotCheckersTree - else analyzerPlugins.foldLeft(annotCheckersTree)((tree, plugin) => - if (!plugin.isActive()) tree else plugin.adaptAnnotations(tree, typer, mode, pt)) - } + def default = global.adaptAnnotations(tree, mode, pt) + def accumulate = (tree, p) => p.adaptAnnotations(tree, typer, mode, pt) + }) /** @see AnalyzerPlugin.pluginsTypedReturn */ - def pluginsTypedReturn(tpe: Type, typer: Typer, tree: Return, pt: Type): Type = { - val annotCheckersType = adaptTypeOfReturn(tree.expr, pt, tpe) - if (analyzerPlugins.isEmpty) annotCheckersType - else analyzerPlugins.foldLeft(annotCheckersType)((tpe, plugin) => - if (!plugin.isActive()) tpe else plugin.pluginsTypedReturn(tpe, typer, tree, pt)) + def pluginsTypedReturn(tpe: Type, typer: Typer, tree: Return, pt: Type): Type = invoke(new CumulativeOp[Type] { + def default = adaptTypeOfReturn(tree.expr, pt, tpe) + def accumulate = (tpe, p) => p.pluginsTypedReturn(tpe, typer, tree, pt) + }) + + /** A list of registered macro plugins */ + private var macroPlugins: List[MacroPlugin] = Nil + + /** Registers a new macro plugin */ + def addMacroPlugin(plugin: MacroPlugin) { + if (!macroPlugins.contains(plugin)) + macroPlugins = plugin :: macroPlugins + } + + private abstract class NonCumulativeOp[T] { + def position: Position + def description: String + def default: T + def custom(plugin: MacroPlugin): Option[T] + } + + private def invoke[T](op: NonCumulativeOp[T]): T = { + if (macroPlugins.isEmpty) op.default + else { + val results = macroPlugins.filter(_.isActive()).map(plugin => (plugin, op.custom(plugin))) + results.flatMap { case (p, Some(result)) => Some((p, result)); case _ => None } match { + case (p1, _) :: (p2, _) :: _ => typer.context.error(op.position, s"both $p1 and $p2 want to ${op.description}"); op.default + case (_, custom) :: Nil => custom + case Nil => op.default + } + } + } + + /** @see MacroPlugin.pluginsTypedMacroBody */ + def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Tree = invoke(new NonCumulativeOp[Tree] { + def position = ddef.pos + def description = "typecheck this macro definition" + def default = standardTypedMacroBody(typer, ddef) + def custom(plugin: MacroPlugin) = plugin.pluginsTypedMacroBody(typer, ddef) + }) + + /** @see MacroPlugin.pluginsMacroExpand */ + def pluginsMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = invoke(new NonCumulativeOp[Tree] { + def position = expandee.pos + def description = "expand this macro application" + def default = standardMacroExpand(typer, expandee, mode, pt) + def custom(plugin: MacroPlugin) = plugin.pluginsMacroExpand(typer, expandee, mode, pt) + }) + + /** @see MacroPlugin.pluginsMacroArgs */ + def pluginsMacroArgs(typer: Typer, expandee: Tree): MacroArgs = invoke(new NonCumulativeOp[MacroArgs] { + def position = expandee.pos + def description = "compute macro arguments for this macro application" + def default = standardMacroArgs(typer, expandee) + def custom(plugin: MacroPlugin) = plugin.pluginsMacroArgs(typer, expandee) + }) + + /** @see MacroPlugin.pluginsMacroRuntime */ + def pluginsMacroRuntime(expandee: Tree): MacroRuntime = invoke(new NonCumulativeOp[MacroRuntime] { + def position = expandee.pos + def description = "compute macro runtime for this macro application" + def default = standardMacroRuntime(expandee) + def custom(plugin: MacroPlugin) = plugin.pluginsMacroRuntime(expandee) + }) + + /** @see MacroPlugin.pluginsEnterSym */ + def pluginsEnterSym(namer: Namer, tree: Tree): Context = + if (macroPlugins.isEmpty) namer.standardEnterSym(tree) + else invoke(new NonCumulativeOp[Context] { + def position = tree.pos + def description = "enter a symbol for this tree" + def default = namer.standardEnterSym(tree) + def custom(plugin: MacroPlugin) = { + val hasExistingSym = tree.symbol != NoSymbol + val result = plugin.pluginsEnterSym(namer, tree) + if (result && hasExistingSym) Some(namer.context) + else if (result && tree.isInstanceOf[Import]) Some(namer.context.make(tree)) + else if (result) Some(namer.context) + else None + } + }) + + /** @see MacroPlugin.pluginsEnsureCompanionObject */ + def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = invoke(new NonCumulativeOp[Symbol] { + def position = cdef.pos + def description = "enter a companion symbol for this tree" + def default = namer.standardEnsureCompanionObject(cdef, creator) + def custom(plugin: MacroPlugin) = plugin.pluginsEnsureCompanionObject(namer, cdef, creator) + }) + + /** @see MacroPlugin.pluginsEnterStats */ + def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = { + // performance opt + if (macroPlugins.isEmpty) stats + else macroPlugins.foldLeft(stats)((current, plugin) => + if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats)) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 719d04a7f9..4d0eda2377 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -625,8 +625,7 @@ trait ContextErrors { setError(tree) } - def CaseClassConstructorError(tree: Tree) = { - val baseMessage = tree.symbol + " is not a case class constructor, nor does it have an unapply/unapplySeq method" + def CaseClassConstructorError(tree: Tree, baseMessage: String) = { val addendum = directUnapplyMember(tree.symbol.info) match { case sym if hasMultipleNonImplicitParamLists(sym) => s"\nNote: ${sym.defString} exists in ${tree.symbol}, but it cannot be used as an extractor due to its second non-implicit parameter list" case _ => "" @@ -726,8 +725,9 @@ trait ContextErrors { NormalTypeError(expandee, "too many argument lists for " + fun) } - def MacroInvalidExpansionError(expandee: Tree, role: String, allowedExpansions: String) = { - issueNormalTypeError(expandee, s"macro in $role role can only expand into $allowedExpansions") + def MacroIncompatibleEngineError(macroEngine: String) = { + val message = s"macro cannot be expanded, because it was compiled by an incompatible macro engine $macroEngine" + issueNormalTypeError(lastTreeToTyper, message) } case object MacroExpansionException extends Exception with scala.util.control.ControlThrowable diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index fdec1edcc0..19fba639e3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -1145,7 +1145,7 @@ trait Implicits { gen.mkAttributedThis(thisSym) case _ => // if `pre` is not a PDT, e.g. if someone wrote - // implicitly[scala.reflect.macros.BlackboxContext#TypeTag[Int]] + // implicitly[scala.reflect.macros.blackbox.Context#TypeTag[Int]] // then we need to fail, because we don't know the prefix to use during type reification // upd. we also need to fail silently, because this is a very common situation // e.g. quite often we're searching for BaseUniverse#TypeTag, e.g. for a type tag in any universe diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 0d46a96b81..cf82d6baac 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -29,7 +29,7 @@ import Fingerprint._ * Then fooBar needs to point to a static method of the following form: * * def fooBar[T: c.WeakTypeTag] // type tag annotation is optional - * (c: scala.reflect.macros.BlackboxContext) + * (c: scala.reflect.macros.blackbox.Context) * (xs: c.Expr[List[T]]) * : c.Expr[T] = { * ... @@ -67,7 +67,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * * This solution is very simple, but unfortunately it's also lacking. If we use it, then * signatures of macro defs become transitively dependent on scala-reflect.jar - * (because they refer to macro impls, and macro impls refer to scala.reflect.macros.BlackboxContext/WhiteboxContext defined in scala-reflect.jar). + * (because they refer to macro impls, and macro impls refer to *box.Context defined in scala-reflect.jar). * More details can be found in comments to https://issues.scala-lang.org/browse/SI-5940. * * Therefore we have to avoid putting macro impls into binding pickles and come up with our own serialization format. @@ -81,9 +81,9 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * and various accounting information necessary when composing an argument list for the reflective invocation. */ case class MacroImplBinding( - // Is this macro impl a bundle (a trait extending BlackboxMacro or WhiteboxMacro) or a vanilla def? + // Is this macro impl a bundle (a trait extending *box.Macro) or a vanilla def? val isBundle: Boolean, - // Is this macro impl blackbox (i.e. having BlackboxContext in its signature)? + // Is this macro impl blackbox (i.e. having blackbox.Context in its signature)? val isBlackbox: Boolean, // Java class name of the class that contains the macro implementation // is used to load the corresponding object with Java reflection @@ -97,8 +97,8 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { // * c.Expr[T] => LiftedTyped // * c.Tree => LiftedUntyped // * c.WeakTypeTag[T] => Tagged(index of the type parameter corresponding to that type tag) - // * everything else (e.g. scala.reflect.macros.BlackboxContext/WhiteboxContext) => Other - // f.ex. for: def impl[T: WeakTypeTag, U, V: WeakTypeTag](c: BlackboxContext)(x: c.Expr[T], y: c.Tree): (U, V) = ??? + // * everything else (e.g. *box.Context) => Other + // f.ex. for: def impl[T: WeakTypeTag, U, V: WeakTypeTag](c: blackbox.Context)(x: c.Expr[T], y: c.Tree): (U, V) = ??? // `signature` will be equal to List(List(Other), List(LiftedTyped, LiftedUntyped), List(Tagged(0), Tagged(2))) signature: List[List[Fingerprint]], // type arguments part of a macro impl ref (the right-hand side of a macro definition) @@ -116,23 +116,22 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * with synthetic content that carries the payload described in `MacroImplBinding`. * * For example, for a pair of macro definition and macro implementation: - * def impl(c: scala.reflect.macros.BlackboxContext): c.Expr[Unit] = ??? + * def impl(c: scala.reflect.macros.blackbox.Context): c.Expr[Unit] = ??? * def foo: Unit = macro impl * * We will have the following annotation added on the macro definition `foo`: * * @scala.reflect.macros.internal.macroImpl( * `macro`( + * "macroEngine" = <current macro engine>, * "isBundle" = false, * "isBlackbox" = true, * "signature" = List(Other), * "methodName" = "impl", - * "versionFormat" = <current version format>, * "className" = "Macros$")) */ + def macroEngine = "v7.0 (implemented in Scala 2.11.0-M8)" object MacroImplBinding { - val versionFormat = 6.0 - def pickleAtom(obj: Any): Tree = obj match { case list: List[_] => Apply(Ident(ListModule), list map pickleAtom) @@ -183,12 +182,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } val payload = List[(String, Any)]( - "versionFormat" -> versionFormat, - "isBundle" -> isBundle, - "isBlackbox" -> isBlackbox, - "className" -> className, - "methodName" -> macroImpl.name.toString, - "signature" -> signature + "macroEngine" -> macroEngine, + "isBundle" -> isBundle, + "isBlackbox" -> isBlackbox, + "className" -> className, + "methodName" -> macroImpl.name.toString, + "signature" -> signature ) // the shape of the nucleus is chosen arbitrarily. it doesn't carry any payload. @@ -237,8 +236,8 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { raw.asInstanceOf[T] } - val pickleVersionFormat = unpickle("versionFormat", classOf[Double]) - if (versionFormat != pickleVersionFormat) fail(s"expected version format $versionFormat, actual $pickleVersionFormat") + val macroEngine = unpickle("macroEngine", classOf[String]) + if (self.macroEngine != macroEngine) typer.TyperErrorGen.MacroIncompatibleEngineError(macroEngine) val isBundle = unpickle("isBundle", classOf[Boolean]) val isBlackbox = unpickle("isBlackbox", classOf[Boolean]) @@ -315,7 +314,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * @return Macro impl reference for the given macro definition if everything is okay. * EmptyTree if an error occurs. */ - def typedMacroBody(typer: Typer, macroDdef: DefDef): Tree = { + def typedMacroBody(typer: Typer, macroDdef: DefDef): Tree = pluginsTypedMacroBody(typer, macroDdef) + + /** Default implementation of `typedMacroBody`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsTypedMacroBody for more details) + */ + def standardTypedMacroBody(typer: Typer, macroDdef: DefDef): Tree = { val macroDef = macroDdef.symbol assert(macroDef.isMacro, macroDdef) @@ -350,7 +354,6 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { 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 macroRole = universe.analyzer.macroExpanderAttachment(expandeeTree).role } 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 */) @@ -360,8 +363,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { /** Calculate the arguments to pass to a macro implementation when expanding the provided tree. */ case class MacroArgs(c: MacroContext, others: List[Any]) + def macroArgs(typer: Typer, expandee: Tree): MacroArgs = pluginsMacroArgs(typer, expandee) - private def macroArgs(typer: Typer, expandee: Tree): MacroArgs = { + /** Default implementation of `macroArgs`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroArgs for more details) + */ + def standardMacroArgs(typer: Typer, expandee: Tree): MacroArgs = { val macroDef = expandee.symbol val paramss = macroDef.paramss val treeInfo.Applied(core, targs, argss) = expandee @@ -471,20 +478,14 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } /** Keeps track of macros in-flight. - * See more informations in comments to `openMacros` in `scala.reflect.macros.WhiteboxContext`. + * See more informations in comments to `openMacros` in `scala.reflect.macros.whitebox.Context`. */ - private var _openMacros = List[MacroContext]() + var _openMacros = List[MacroContext]() def openMacros = _openMacros - private def pushMacroContext(c: MacroContext) = _openMacros ::= c - private def popMacroContext() = _openMacros = _openMacros.tail + def pushMacroContext(c: MacroContext) = _openMacros ::= c + def popMacroContext() = _openMacros = _openMacros.tail def enclosingMacroPosition = openMacros map (_.macroApplication.pos) find (_ ne NoPosition) getOrElse NoPosition - /** Describes the role that the macro expandee is performing. - */ - type MacroRole = scala.tools.nsc.typechecker.MacroRole - final def APPLY_ROLE = MacroRole.Apply - final def UNAPPLY_ROLE = MacroRole.Unapply - /** Performs macro expansion: * * ========= Expandable trees ========= @@ -527,30 +528,24 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * the expandee with an error marker set if the expansion has been cancelled due malformed arguments or implementation * the expandee with an error marker set if there has been an error */ - private abstract class MacroExpander[Result: ClassTag](val role: MacroRole, val typer: Typer, val expandee: Tree) { - def allowExpandee(expandee: Tree): Boolean = true - def allowExpanded(expanded: Tree): Boolean = true - def allowedExpansions: String = "anything" - def allowResult(result: Result): Boolean = true - - def onSuccess(expanded: Tree): Result - def onFallback(expanded: Tree): Result - def onSuppressed(expandee: Tree): Result = expandee match { case expandee: Result => expandee } - def onDelayed(expanded: Tree): Result = expanded match { case expanded: Result => expanded } - def onSkipped(expanded: Tree): Result = expanded match { case expanded: Result => expanded } - def onFailure(expanded: Tree): Result = { typer.infer.setError(expandee); expandee match { case expandee: Result => expandee } } - - def apply(desugared: Tree): Result = { + abstract class MacroExpander(val typer: Typer, val expandee: Tree) { + def onSuccess(expanded: Tree): Tree + def onFallback(expanded: Tree): Tree + def onSuppressed(expandee: Tree): Tree = expandee + def onDelayed(expanded: Tree): Tree = expanded + def onSkipped(expanded: Tree): Tree = expanded + def onFailure(expanded: Tree): Tree = { typer.infer.setError(expandee); expandee } + + def apply(desugared: Tree): Tree = { if (isMacroExpansionSuppressed(desugared)) onSuppressed(expandee) else expand(desugared) } - protected def expand(desugared: Tree): Result = { + protected def expand(desugared: Tree): Tree = { def showDetailed(tree: Tree) = showRaw(tree, printIds = true, printTypes = true) def summary() = s"expander = $this, expandee = ${showDetailed(expandee)}, desugared = ${if (expandee == desugared) () else showDetailed(desugared)}" if (macroDebugVerbose) println(s"macroExpand: ${summary()}") - assert(allowExpandee(expandee), summary()) - linkExpandeeAndDesugared(expandee, desugared, role) + linkExpandeeAndDesugared(expandee, desugared) val start = if (Statistics.canEnable) Statistics.startTimer(macroExpandNanos) else null if (Statistics.canEnable) Statistics.incCounter(macroExpandCount) @@ -562,21 +557,17 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { onFailure(typer.infer.setError(expandee)) } else try { val expanded = { - val runtime = macroRuntime(expandee.symbol) + val runtime = macroRuntime(expandee) if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) else macroExpandWithoutRuntime(typer, expandee) } expanded match { case Success(expanded) => - if (allowExpanded(expanded)) { - // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc - val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() - if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) - if (allowResult(expanded1)) expanded1 else onFailure(expanded) - } else { - typer.TyperErrorGen.MacroInvalidExpansionError(expandee, role.name, allowedExpansions) - onFailure(expanded) - } + // also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc + val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext() + if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1) + if (settings.Ymacroexpand.value == settings.MacroExpand.Discard) expandee.setType(expanded1.tpe) + else expanded1 case Fallback(fallback) => onFallback(fallback) case Delayed(delayed) => onDelayed(delayed) case Skipped(skipped) => onSkipped(skipped) @@ -592,151 +583,136 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { } } - /** Expands a tree that carries a term, which happens to be a term macro. - * @see MacroExpander - */ - private abstract class TermMacroExpander(role: MacroRole, typer: Typer, expandee: Tree, mode: Mode, pt: Type) - extends MacroExpander[Tree](role, typer, expandee) { - override def allowedExpansions: String = "term trees" - override def allowExpandee(expandee: Tree) = expandee.isTerm - override def onSuccess(expanded: Tree) = typer.typed(expanded, mode, pt) - override def onFallback(fallback: Tree) = typer.typed(fallback, mode, pt) - } - /** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`. * @param outerPt Expected type that comes from enclosing context (something that's traditionally called `pt`). * @param innerPt Expected type that comes from the signature of a macro def, possibly wildcarded to help type inference. - * @see MacroExpander */ - def macroExpandApply(typer: Typer, expandee: Tree, mode: Mode, outerPt: Type): Tree = { - object expander extends TermMacroExpander(APPLY_ROLE, typer, expandee, mode, outerPt) { - lazy val innerPt = { - val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe - if (isBlackbox(expandee)) tp - else { - // approximation is necessary for whitebox macros to guide type inference - // read more in the comments for onDelayed below - val undetparams = tp collect { case tp if tp.typeSymbol.isTypeParameter => tp.typeSymbol } - deriveTypeWithWildcards(undetparams)(tp) - } + class DefMacroExpander(typer: Typer, expandee: Tree, mode: Mode, outerPt: Type) + extends MacroExpander(typer, expandee) { + lazy val innerPt = { + val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe + if (isBlackbox(expandee)) tp + else { + // approximation is necessary for whitebox macros to guide type inference + // read more in the comments for onDelayed below + val undetparams = tp collect { case tp if tp.typeSymbol.isTypeParameter => tp.typeSymbol } + deriveTypeWithWildcards(undetparams)(tp) } - 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 - linkExpandeeAndExpanded(expandee, expanded0) - - def typecheck(label: String, tree: Tree, pt: Type): Tree = { - if (tree.isErrorTyped) tree - else { - if (macroDebugVerbose) println(s"$label (against pt = $pt): $tree") - // `macroExpandApply` is called from `adapt`, where implicit conversions are disabled - // therefore we need to re-enable the conversions back temporarily - val result = typer.context.withImplicitsEnabled(typer.typed(tree, mode, pt)) - if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reportBuffer.errors}") - result - } - } + } + 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 + linkExpandeeAndExpanded(expandee, expanded0) - if (isBlackbox(expandee)) { - val expanded1 = atPos(enclosingMacroPosition.makeTransparent)(Typed(expanded0, TypeTree(innerPt))) - typecheck("blackbox typecheck", expanded1, outerPt) - } else { - val expanded1 = expanded0 - val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt) - typecheck("whitebox typecheck #2", expanded2, innerPt) + def typecheck(label: String, tree: Tree, pt: Type): Tree = { + if (tree.isErrorTyped) tree + else { + if (macroDebugVerbose) println(s"$label (against pt = $pt): $tree") + // `macroExpandApply` is called from `adapt`, where implicit conversions are disabled + // therefore we need to re-enable the conversions back temporarily + val result = typer.context.withImplicitsEnabled(typer.typed(tree, mode, pt)) + if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reportBuffer.errors}") + result } } - override def onDelayed(delayed: Tree) = { - // =========== THE SITUATION =========== - // - // If we've been delayed (i.e. bailed out of the expansion because of undetermined type params present in the expandee), - // then there are two possible situations we're in: - // 1) We're in POLYmode, when the typer tests the waters wrt type inference - // (e.g. as in typedArgToPoly in doTypedApply). - // 2) We're out of POLYmode, which means that the typer is out of tricks to infer our type - // (e.g. if we're an argument to a function call, then this means that no previous argument lists - // can determine our type variables for us). - // - // Situation #1 is okay for us, since there's no pressure. In POLYmode we're just verifying that - // there's nothing outrageously wrong with our undetermined type params (from what I understand!). - // - // Situation #2 requires measures to be taken. If we're in it, then noone's going to help us infer - // the undetermined type params. Therefore we need to do something ourselves or otherwise this - // expandee will forever remaing not expanded (see SI-5692). A traditional way out of this conundrum - // is to call `instantiate` and let the inferencer try to find the way out. It works for simple cases, - // but sometimes, if the inferencer lacks information, it will be forced to approximate. - // - // =========== THE PROBLEM =========== - // - // Consider the following example (thanks, Miles!): - // - // Iso represents an isomorphism between two datatypes: - // 1) An arbitrary one (e.g. a random case class) - // 2) A uniform representation for all datatypes (e.g. an HList) - // - // trait Iso[T, U] { - // def to(t : T) : U - // def from(u : U) : T - // } - // implicit def materializeIso[T, U]: Iso[T, U] = macro ??? - // - // case class Foo(i: Int, s: String, b: Boolean) - // def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c) - // foo(Foo(23, "foo", true)) - // - // In the snippet above, even though we know that there's a fundep going from T to U - // (in a sense that a datatype's uniform representation is unambiguously determined by the datatype, - // e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information - // to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want. - // - // =========== THE SOLUTION (ENABLED ONLY FOR WHITEBOX MACROS) =========== - // - // To give materializers a chance to say their word before vanilla inference kicks in, - // we infer as much as possible (e.g. in the example above even though L is hopeless, C still can be inferred to Foo) - // and then trigger macro expansion with the undetermined type parameters still there. - // 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) - else { - forced += delayed - typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false) - macroExpandApply(typer, delayed, mode, outerPt) - } - } else delayed + + if (isBlackbox(expandee)) { + val expanded1 = atPos(enclosingMacroPosition.makeTransparent)(Typed(expanded0, TypeTree(innerPt))) + typecheck("blackbox typecheck", expanded1, outerPt) + } else { + val expanded1 = expanded0 + val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt) + typecheck("whitebox typecheck #2", expanded2, innerPt) } } - expander(expandee) + override def onDelayed(delayed: Tree) = { + // =========== THE SITUATION =========== + // + // If we've been delayed (i.e. bailed out of the expansion because of undetermined type params present in the expandee), + // then there are two possible situations we're in: + // 1) We're in POLYmode, when the typer tests the waters wrt type inference + // (e.g. as in typedArgToPoly in doTypedApply). + // 2) We're out of POLYmode, which means that the typer is out of tricks to infer our type + // (e.g. if we're an argument to a function call, then this means that no previous argument lists + // can determine our type variables for us). + // + // Situation #1 is okay for us, since there's no pressure. In POLYmode we're just verifying that + // there's nothing outrageously wrong with our undetermined type params (from what I understand!). + // + // Situation #2 requires measures to be taken. If we're in it, then noone's going to help us infer + // the undetermined type params. Therefore we need to do something ourselves or otherwise this + // expandee will forever remaing not expanded (see SI-5692). A traditional way out of this conundrum + // is to call `instantiate` and let the inferencer try to find the way out. It works for simple cases, + // but sometimes, if the inferencer lacks information, it will be forced to approximate. + // + // =========== THE PROBLEM =========== + // + // Consider the following example (thanks, Miles!): + // + // Iso represents an isomorphism between two datatypes: + // 1) An arbitrary one (e.g. a random case class) + // 2) A uniform representation for all datatypes (e.g. an HList) + // + // trait Iso[T, U] { + // def to(t : T) : U + // def from(u : U) : T + // } + // implicit def materializeIso[T, U]: Iso[T, U] = macro ??? + // + // case class Foo(i: Int, s: String, b: Boolean) + // def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c) + // foo(Foo(23, "foo", true)) + // + // In the snippet above, even though we know that there's a fundep going from T to U + // (in a sense that a datatype's uniform representation is unambiguously determined by the datatype, + // e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information + // to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want. + // + // =========== THE SOLUTION (ENABLED ONLY FOR WHITEBOX MACROS) =========== + // + // To give materializers a chance to say their word before vanilla inference kicks in, + // we infer as much as possible (e.g. in the example above even though L is hopeless, C still can be inferred to Foo) + // and then trigger macro expansion with the undetermined type parameters still there. + // 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) + else { + forced += delayed + typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false) + macroExpand(typer, delayed, mode, outerPt) + } + } else delayed + } + override def onFallback(fallback: Tree) = typer.typed(fallback, mode, outerPt) } - /** Expands a term macro used in unapply role as `u.Quasiquote(StringContext("", "")).q.unapply(x)` in `case q"$x" => ...`. - * @see MacroExpander + /** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`. + * @see DefMacroExpander */ - def macroExpandUnapply(typer: Typer, original: Tree, fun: Tree, unapply: Symbol, args: List[Tree], mode: Mode, pt: Type) = { - val expandee = treeCopy.Apply(original, gen.mkAttributedSelect(fun, unapply), args) - object expander extends TermMacroExpander(UNAPPLY_ROLE, typer, expandee, mode, pt) { - override def allowedExpansions: String = "unapply trees" - override def allowExpandee(expandee: Tree) = expandee.isInstanceOf[Apply] - private def unsupported(what: String) = abort("unapply macros currently don't support " + what) - override def onFallback(fallback: Tree) = unsupported("fallback") - override def onDelayed(delayed: Tree) = unsupported("advanced interaction with type inference") - } - expander(original) + def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = pluginsMacroExpand(typer, expandee, mode, pt) + + /** Default implementation of `macroExpand`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroExpand for more details) + */ + def standardMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = { + val expander = new DefMacroExpander(typer, expandee, mode, pt) + expander(expandee) } - private sealed abstract class MacroStatus(val result: Tree) - private case class Success(expanded: Tree) extends MacroStatus(expanded) - private case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true } - private case class Delayed(delayed: Tree) extends MacroStatus(delayed) - private case class Skipped(skipped: Tree) extends MacroStatus(skipped) - private case class Failure(failure: Tree) extends MacroStatus(failure) - private def Delay(expanded: Tree) = Delayed(expanded) - private def Skip(expanded: Tree) = Skipped(expanded) + sealed abstract class MacroStatus(val result: Tree) + case class Success(expanded: Tree) extends MacroStatus(expanded) + case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true } + case class Delayed(delayed: Tree) extends MacroStatus(delayed) + case class Skipped(skipped: Tree) extends MacroStatus(skipped) + case class Failure(failure: Tree) extends MacroStatus(failure) + def Delay(expanded: Tree) = Delayed(expanded) + def Skip(expanded: Tree) = Skipped(expanded) /** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ - private def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroStatus = { + def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroStatus = { val wasDelayed = isDelayed(expandee) val undetparams = calculateUndetparams(expandee) val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty @@ -767,7 +743,31 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { macroLogLite("" + expanded + "\n" + showRaw(expanded)) val freeSyms = expanded.freeTerms ++ expanded.freeTypes freeSyms foreach (sym => MacroFreeSymbolError(expandee, sym)) - Success(atPos(enclosingMacroPosition.focus)(expanded)) + // Macros might have spliced arguments with range positions into non-compliant + // locations, notably, under a tree without a range position. Or, they might + // splice a tree that `resetAttrs` has assigned NoPosition. + // + // Here, we just convert all positions in the tree to offset positions, and + // convert NoPositions to something sensible. + // + // Given that the IDE now sees the expandee (by using -Ymacro-expand:discard), + // this loss of position fidelity shouldn't cause any real problems. + // + // Alternatively, we could pursue a way to exclude macro expansions from position + // invariant checking, or find a way not to touch expansions that happen to validate. + // + // This would be useful for cases like: + // + // macro1 { macro2 { "foo" } } + // + // to allow `macro1` to see the range position of the "foo". + val expandedPos = enclosingMacroPosition.focus + def fixPosition(pos: Position) = + if (pos == NoPosition) expandedPos else pos.focus + expanded.foreach(t => t.pos = fixPosition(t.pos)) + + val result = atPos(enclosingMacroPosition.focus)(expanded) + Success(result) } expanded match { case expanded: Expr[_] if expandee.symbol.isTermMacro => validateResultingTree(expanded.tree) @@ -793,7 +793,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { /** Expands a macro when a runtime (i.e. the macro implementation) cannot be loaded * Meant for internal use within the macro infrastructure, don't use it elsewhere. */ - private def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroStatus = { + def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroStatus = { import typer.TyperErrorGen._ val fallbackSym = expandee.symbol.nextOverriddenSymbol orElse MacroImplementationNotFoundError(expandee) macroLogLite(s"falling back to: $fallbackSym") @@ -871,7 +871,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { context.implicitsEnabled = typer.context.implicitsEnabled context.enrichmentEnabled = typer.context.enrichmentEnabled context.macrosEnabled = typer.context.macrosEnabled - macroExpandApply(newTyper(context), tree, EXPRmode, WildcardType) + macroExpand(newTyper(context), tree, EXPRmode, WildcardType) case _ => tree }) @@ -902,12 +902,3 @@ object Fingerprint { val LiftedTyped = new Fingerprint(-2) val LiftedUntyped = new Fingerprint(-3) } - -class MacroRole private[MacroRole](val name: String) extends AnyVal { - override def toString = name -} - -object MacroRole { - val Apply = new MacroRole("apply") - val Unapply = new MacroRole("unapply") -} diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 86bb99e7fa..27e8698676 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -22,7 +22,7 @@ trait Namers extends MethodSynthesis { import global._ import definitions._ - private var _lockedCount = 0 + var _lockedCount = 0 def lockedCount = this._lockedCount /** Replaces any Idents for which cond is true with fresh TypeTrees(). @@ -107,8 +107,8 @@ trait Namers extends MethodSynthesis { } protected def owner = context.owner - private def contextFile = context.unit.source.file - private def typeErrorHandler[T](tree: Tree, alt: T): PartialFunction[Throwable, T] = { + def contextFile = context.unit.source.file + def typeErrorHandler[T](tree: Tree, alt: T): PartialFunction[Throwable, T] = { case ex: TypeError => // H@ need to ensure that we handle only cyclic references TypeSigError(tree, ex) @@ -122,10 +122,31 @@ trait Namers extends MethodSynthesis { || (vd.mods.isPrivateLocal && !vd.mods.isCaseAccessor) || (vd.name startsWith nme.OUTER) || (context.unit.isJava) + || isEnumConstant(vd) ) + def noFinishGetterSetter(vd: ValDef) = ( (vd.mods.isPrivateLocal && !vd.mods.isLazy) // all lazy vals need accessors, even private[this] - || vd.symbol.isModuleVar) + || vd.symbol.isModuleVar + || isEnumConstant(vd)) + + /** Determines whether this field holds an enum constant. + * To qualify, the following conditions must be met: + * - The field's class has the ENUM flag set + * - The field's class extends java.lang.Enum + * - The field has the ENUM flag set + * - The field is static + * - The field is stable + */ + def isEnumConstant(vd: ValDef) = { + val ownerHasEnumFlag = + // Necessary to check because scalac puts Java's static members into the companion object + // while Scala's enum constants live directly in the class. + // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal + // cyclic reference error. See the commit message for details. + if (context.unit.isJava) owner.companionClass.hasEnumFlag else owner.hasEnumFlag + vd.mods.hasAllFlags(ENUM | STABLE | STATIC) && ownerHasEnumFlag + } def setPrivateWithin[T <: Symbol](tree: Tree, sym: T, mods: Modifiers): T = if (sym.isPrivateLocal || !mods.hasAccessBoundary) sym @@ -243,7 +264,12 @@ trait Namers extends MethodSynthesis { validate(sym2.companionClass) } - def enterSym(tree: Tree): Context = { + def enterSym(tree: Tree): Context = pluginsEnterSym(this, tree) + + /** Default implementation of `enterSym`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsEnterSym for more details) + */ + def standardEnterSym(tree: Tree): Context = { def dispatch() = { var returnContext = this.context tree match { @@ -309,7 +335,7 @@ trait Namers extends MethodSynthesis { * be transferred to the symbol as they are, supply a mask containing * the flags to keep. */ - private def createMemberSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = { + def createMemberSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = { val pos = tree.pos val isParameter = tree.mods.isParameter val flags = tree.mods.flags & mask @@ -327,14 +353,14 @@ trait Namers extends MethodSynthesis { else owner.newValue(name.toTermName, pos, flags) } } - private def createFieldSymbol(tree: ValDef): TermSymbol = + def createFieldSymbol(tree: ValDef): TermSymbol = owner.newValue(tree.localName, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal) - private def createImportSymbol(tree: Tree) = + def createImportSymbol(tree: Tree) = NoSymbol.newImport(tree.pos) setInfo completerOf(tree) /** All PackageClassInfoTypes come from here. */ - private def createPackageSymbol(pos: Position, pid: RefTree): Symbol = { + def createPackageSymbol(pos: Position, pid: RefTree): Symbol = { val pkgOwner = pid match { case Ident(_) => if (owner.isEmptyPackageClass) rootMirror.RootClass else owner case Select(qual: RefTree, _) => createPackageSymbol(pos, qual).moduleClass @@ -393,7 +419,7 @@ trait Namers extends MethodSynthesis { /** Given a ClassDef or ModuleDef, verifies there isn't a companion which * has been defined in a separate file. */ - private def validateCompanionDefs(tree: ImplDef) { + def validateCompanionDefs(tree: ImplDef) { val sym = tree.symbol orElse { return } val ctx = if (context.owner.isPackageObjectClass) context.outer else context val module = if (sym.isModule) sym else ctx.scope lookupModule tree.name @@ -466,7 +492,13 @@ trait Namers extends MethodSynthesis { * class definition tree. * @return the companion object symbol. */ - def ensureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = { + def ensureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = + pluginsEnsureCompanionObject(this, cdef, creator) + + /** Default implementation of `ensureCompanionObject`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsEnsureCompanionObject for more details) + */ + def standardEnsureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = { val m = companionSymbolOf(cdef.symbol, context) // @luc: not sure why "currentRun.compiles(m)" is needed, things breaks // otherwise. documentation welcome. @@ -609,11 +641,7 @@ trait Namers extends MethodSynthesis { else enterGetterSetter(tree) - // When java enums are read from bytecode, they are known to have - // constant types by the jvm flag and assigned accordingly. When - // they are read from source, the java parser marks them with the - // STABLE flag, and now we receive that signal. - if (tree.symbol hasAllFlags STABLE | JAVA) + if (isEnumConstant(tree)) tree.symbol setInfo ConstantType(Constant(tree.symbol)) } @@ -828,9 +856,10 @@ trait Namers extends MethodSynthesis { * assigns the type to the tpt's node. Returns the type. */ private def assignTypeToTree(tree: ValOrDefDef, defnTyper: Typer, pt: Type): Type = { - val rhsTpe = - if (tree.symbol.isTermMacro) defnTyper.computeMacroDefType(tree, pt) - else defnTyper.computeType(tree.rhs, pt) + val rhsTpe = tree match { + case ddef: DefDef if tree.symbol.isTermMacro => defnTyper.computeMacroDefType(ddef, pt) + case _ => defnTyper.computeType(tree.rhs, pt) + } val defnTpe = widenIfNecessary(tree.symbol, rhsTpe, pt) tree.tpt defineType defnTpe setPos tree.pos.focus @@ -1620,7 +1649,7 @@ trait Namers extends MethodSynthesis { val tree: Tree } - def mkTypeCompleter(t: Tree)(c: Symbol => Unit) = new LockingTypeCompleter { + def mkTypeCompleter(t: Tree)(c: Symbol => Unit) = new LockingTypeCompleter with FlagAgnosticCompleter { val tree = t def completeImpl(sym: Symbol) = c(sym) } diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 069d6d5fb2..41c656f8ce 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -78,26 +78,34 @@ trait PatternTypers { // Do some ad-hoc overloading resolution and update the tree's symbol and type // do not update the symbol if the tree's symbol's type does not define an unapply member // (e.g. since it's some method that returns an object with an unapply member) - val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember) - def caseClass = fun.tpe.typeSymbol.linkedClassOfClass + val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember) + val caseClass = fun.tpe.typeSymbol.linkedClassOfClass + val member = unapplyMember(fun.tpe) + def resultType = (fun.tpe memberType member).finalResultType + def isEmptyType = resultOfMatchingMethod(resultType, nme.isEmpty)() + def isOkay = ( + resultType.isErroneous + || (resultType <:< BooleanTpe) + || (isEmptyType <:< BooleanTpe) + || member.isMacro + || member.isOverloaded // the whole overloading situation is over the rails + ) // Dueling test cases: pos/overloaded-unapply.scala, run/case-class-23.scala, pos/t5022.scala // A case class with 23+ params has no unapply method. - // A case class constructor be overloaded with unapply methods in the companion. - if (caseClass.isCase && !unapplyMember(fun.tpe).isOverloaded) + // A case class constructor may be overloaded with unapply methods in the companion. + if (caseClass.isCase && !member.isOverloaded) logResult(s"convertToCaseConstructor($fun, $caseClass, pt=$pt)")(convertToCaseConstructor(fun, caseClass, pt)) - else if (hasUnapplyMember(fun)) + else if (!reallyExists(member)) + CaseClassConstructorError(fun, s"${fun.symbol} is not a case class, nor does it have an unapply/unapplySeq member") + else if (isOkay) fun + else if (isEmptyType == NoType) + CaseClassConstructorError(fun, s"an unapply result must have a member `def isEmpty: Boolean") else - CaseClassConstructorError(fun) + CaseClassConstructorError(fun, s"an unapply result must have a member `def isEmpty: Boolean (found: def isEmpty: $isEmptyType)") } - def expectedPatternTypes(fun: Tree, args: List[Tree]): List[Type] = - newExtractorShape(fun, args).expectedPatternTypes - - def typedPatternArgs(fun: Tree, args: List[Tree], mode: Mode): List[Tree] = - typedArgsForFormals(args, newExtractorShape(fun, args).formals, mode) - def typedArgsForFormals(args: List[Tree], formals: List[Type], mode: Mode): List[Tree] = { def typedArgWithFormal(arg: Tree, pt: Type) = { val newMode = if (isByNameParamType(pt)) mode.onlySticky else mode.onlySticky | BYVALmode @@ -158,109 +166,6 @@ trait PatternTypers { case _ => wrapClassTagUnapply(treeTyped, extractor, tpe) } } - - def newExtractorShape(fun: Tree, args: List[Tree]): ExtractorShape = ExtractorShape(fun, args) - - case class CaseClassInfo(clazz: Symbol, classType: Type) { - def constructor = clazz.primaryConstructor - def constructorType = classType.prefix memberType clazz memberType constructor - def accessors = clazz.caseFieldAccessors - } - object NoCaseClassInfo extends CaseClassInfo(NoSymbol, NoType) { - override def toString = "NoCaseClassInfo" - } - - case class UnapplyMethodInfo(unapply: Symbol, tpe: Type) { - def name = unapply.name - def isUnapplySeq = name == nme.unapplySeq - def unapplyType = tpe memberType method - def resultType = tpe.finalResultType - def method = unapplyMember(tpe) - def paramType = firstParamType(unapplyType) - def rawGet = if (isBool) UnitTpe else typeOfMemberNamedGetOrSelf(resultType) - def rawTypes = if (isBool) Nil else typesOfSelectorsOrSelf(rawGet) - def isBool = resultType =:= BooleanTpe // aka "Tuple0" or "Option[Unit]" - } - - object NoUnapplyMethodInfo extends UnapplyMethodInfo(NoSymbol, NoType) { - override def toString = "NoUnapplyMethodInfo" - } - - case class ExtractorShape(fun: Tree, args: List[Tree]) { - def pos = fun.pos - private def symbol = fun.symbol - private def tpe = fun.tpe - - val ccInfo = tpe.typeSymbol.linkedClassOfClass match { - case clazz if clazz.isCase => CaseClassInfo(clazz, tpe) - case _ => NoCaseClassInfo - } - val exInfo = UnapplyMethodInfo(symbol, tpe) - import exInfo.{ rawGet, rawTypes, isUnapplySeq } - - override def toString = s"ExtractorShape($fun, $args)" - - def unapplyMethod = exInfo.method - def unapplyType = exInfo.unapplyType - def unapplyParamType = exInfo.paramType - def enclClass = symbol.enclClass - - // TODO - merge these. The difference between these two methods is that expectedPatternTypes - // expands the list of types so it is the same length as the number of patterns, whereas formals - // leaves the varargs type unexpanded. - def formals = ( - if (isUnapplySeq) productTypes :+ varargsType - else if (elementArity == 0) productTypes - else if (isSingle) squishIntoOne() - else wrongArity(patternFixedArity) - ) - def expectedPatternTypes = elementArity match { - case 0 => productTypes - case _ if elementArity > 0 && isUnapplySeq => productTypes ::: elementTypes - case _ if productArity > 1 && patternFixedArity == 1 => squishIntoOne() - case _ => wrongArity(patternFixedArity) - } - - def elementType = elementTypeOfLastSelectorOrSelf(rawGet) - - private def hasBogusExtractor = directUnapplyMember(tpe).exists && !unapplyMethod.exists - private def expectedArity = "" + productArity + ( if (isUnapplySeq) "+" else "") - private def wrongArityMsg(n: Int) = ( - if (hasBogusExtractor) s"$enclClass does not define a valid extractor method" - else s"wrong number of patterns for $enclClass offering $rawTypes_s: expected $expectedArity, found $n" - ) - private def rawTypes_s = rawTypes match { - case Nil => "()" - case tp :: Nil => "" + tp - case tps => tps.mkString("(", ", ", ")") - } - - private def err(msg: String) = { unit.error(pos, msg) ; throw new TypeError(msg) } - private def wrongArity(n: Int) = err(wrongArityMsg(n)) - - def squishIntoOne() = { - if (settings.lint) - unit.warning(pos, s"$enclClass expects $expectedArity patterns to hold $rawGet but crushing into $productArity-tuple to fit single pattern (SI-6675)") - - rawGet :: Nil - } - // elementArity is the number of non-sequence patterns minus the - // the number of non-sequence product elements returned by the extractor. - // If it is zero, there is a perfect match between those parts, and - // if there is a wildcard star it will match any sequence. - // If it is positive, there are more patterns than products, - // so a sequence will have to fill in the elements. If it is negative, - // there are more products than patterns, which is a compile time error. - def elementArity = patternFixedArity - productArity - def patternFixedArity = treeInfo effectivePatternArity args - def productArity = productTypes.size - def isSingle = !isUnapplySeq && (patternFixedArity == 1) - - def productTypes = if (isUnapplySeq) rawTypes dropRight 1 else rawTypes - def elementTypes = List.fill(elementArity)(elementType) - def varargsType = scalaRepeatedType(elementType) - } - private class VariantToSkolemMap extends TypeMap(trackVariance = true) { private val skolemBuffer = mutable.ListBuffer[TypeSymbol]() @@ -365,10 +270,12 @@ trait PatternTypers { case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType case _ => UnapplyWithSingleArgError(fun) ; ErrorType } - val shape = newExtractorShape(fun, args) - import shape.{ unapplyParamType, unapplyType, unapplyMethod } + val unapplyMethod = unapplyMember(fun.tpe) + val unapplyType = fun.tpe memberType unapplyMethod + val unapplyParamType = firstParamType(unapplyType) + def isSeq = unapplyMethod.name == nme.unapplySeq - def extractor = extractorForUncheckedType(shape.pos, unapplyParamType) + def extractor = extractorForUncheckedType(fun.pos, unapplyParamType) def canRemedy = unapplyParamType match { case RefinedType(_, decls) if !decls.isEmpty => false case RefinedType(parents, _) if parents exists isUncheckable => false @@ -400,7 +307,8 @@ trait PatternTypers { // the union of the expected type and the inferred type of the argument to unapply val glbType = glb(ensureFullyDefined(pt) :: unapplyArg.tpe_* :: Nil) val wrapInTypeTest = canRemedy && !(fun1.symbol.owner isNonBottomSubClass ClassTagClass) - val args1 = typedPatternArgs(fun1, args, mode) + val formals = patmat.alignPatterns(fun1, args).unexpandedFormals + val args1 = typedArgsForFormals(args, formals, mode) val result = UnApply(fun1, args1) setPos tree.pos setType glbType if (wrapInTypeTest) diff --git a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala index 14f47a00fd..995f98cc2c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala +++ b/src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala @@ -16,7 +16,7 @@ trait StdAttachments { /** Scratchpad for the macro expander, which is used to store all intermediate data except the details about the runtime. */ - case class MacroExpanderAttachment(original: Tree, desugared: Tree, role: MacroRole) + case class MacroExpanderAttachment(original: Tree, desugared: Tree) /** Loads underlying MacroExpanderAttachment from a macro expandee or returns a default value for that attachment. */ @@ -24,15 +24,15 @@ trait StdAttachments { tree.attachments.get[MacroExpanderAttachment] getOrElse { tree match { case Apply(fn, _) if tree.isInstanceOf[ApplyToImplicitArgs] => macroExpanderAttachment(fn) - case _ => MacroExpanderAttachment(tree, EmptyTree, APPLY_ROLE) + case _ => MacroExpanderAttachment(tree, EmptyTree) } } /** After macro expansion is completed, links the expandee and the expansion result * by annotating them both with a `MacroExpansionAttachment`. */ - def linkExpandeeAndDesugared(expandee: Tree, desugared: Tree, role: MacroRole): Unit = { - val metadata = MacroExpanderAttachment(expandee, desugared, role) + def linkExpandeeAndDesugared(expandee: Tree, desugared: Tree): Unit = { + val metadata = MacroExpanderAttachment(expandee, desugared) expandee updateAttachment metadata desugared updateAttachment metadata } @@ -95,7 +95,7 @@ trait StdAttachments { /** Determines whether a tree should not be expanded, because someone has put SuppressMacroExpansionAttachment on it or one of its children. */ def isMacroExpansionSuppressed(tree: Tree): Boolean = - ( settings.Ymacronoexpand.value // SI-6812 + ( settings.Ymacroexpand.value == settings.MacroExpand.None // SI-6812 || tree.attachments.get[SuppressMacroExpansionAttachment.type].isDefined || (tree match { // we have to account for the fact that during typechecking an expandee might become wrapped, diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index f0252251f7..9516f94135 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -380,7 +380,7 @@ trait SyntheticMethods extends ast.TreeDSL { val original = ddef.symbol val newAcc = deriveMethod(ddef.symbol, name => context.unit.freshTermName(name + "$")) { newAcc => newAcc.makePublic - newAcc resetFlag (ACCESSOR | PARAMACCESSOR) + newAcc resetFlag (ACCESSOR | PARAMACCESSOR | OVERRIDE) ddef.rhs.duplicate } // TODO: shouldn't the next line be: `original resetFlag CASEACCESSOR`? diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index dbe85f4f5a..6b5afce993 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1102,7 +1102,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (tree.isType) adaptType() else if (mode.typingExprNotFun && treeInfo.isMacroApplication(tree) && !isMacroExpansionSuppressed(tree)) - macroExpandApply(this, tree, mode, pt) + macroExpand(this, tree, mode, pt) else if (mode.typingConstructorPattern) typedConstructorPattern(tree, pt) else if (shouldInsertApply(tree)) @@ -1853,12 +1853,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } protected def enterSym(txt: Context, tree: Tree): Context = - if (txt eq context) namer.enterSym(tree) - else newNamer(txt).enterSym(tree) + if (txt eq context) namer enterSym tree + else newNamer(txt) enterSym tree /** <!-- 2 --> Check that inner classes do not inherit from Annotation */ - def typedTemplate(templ: Template, parents1: List[Tree]): Template = { + def typedTemplate(templ0: Template, parents1: List[Tree]): Template = { + val templ = templ0 + // please FIXME: uncommenting this line breaks everything + // val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents) val clazz = context.owner clazz.annotations.map(_.completeInfo()) if (templ.symbol == NoSymbol) @@ -1886,7 +1889,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ) // the following is necessary for templates generated later assert(clazz.info.decls != EmptyScope, clazz) - enterSyms(context.outer.make(templ, clazz, clazz.info.decls), templ.body) + val body1 = pluginsEnterStats(this, templ.body) + enterSyms(context.outer.make(templ, clazz, clazz.info.decls), body1) if (!templ.isErrorTyped) // if `parentTypes` has invalidated the template, don't validate it anymore validateParentClasses(parents1, selfType) if (clazz.isCase) @@ -1900,26 +1904,26 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType]) - val body = { - val body = - if (isPastTyper || reporter.hasErrors) templ.body - else templ.body flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _)) - val primaryCtor = treeInfo.firstConstructor(body) + val body2 = { + val body2 = + if (isPastTyper || reporter.hasErrors) body1 + else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _)) + val primaryCtor = treeInfo.firstConstructor(body2) val primaryCtor1 = primaryCtor match { case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) => val argss = superArgs(parents1.head) getOrElse Nil - val pos = wrappingPos(parents1.head.pos, argss.flatten) + val pos = wrappingPos(parents1.head.pos, primaryCtor :: argss.flatten).makeTransparent val superCall = atPos(pos)(PrimarySuperCall(argss)) deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos case _ => primaryCtor } - body mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } + body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } } - val body1 = typedStats(body, templ.symbol) + val body3 = typedStats(body2, templ.symbol) if (clazz.info.firstParent.typeSymbol == AnyValClass) - validateDerivedValueClass(clazz, body1) + validateDerivedValueClass(clazz, body3) if (clazz.isTrait) { for (decl <- clazz.info.decls if decl.isTerm && decl.isEarlyInitialized) { @@ -1927,7 +1931,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - treeCopy.Template(templ, parents1, self1, body1) setType clazz.tpe_* + treeCopy.Template(templ, parents1, self1, body3) setType clazz.tpe_* } /** Remove definition annotations from modifiers (they have been saved @@ -2309,10 +2313,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - def typedBlock(block: Block, mode: Mode, pt: Type): Block = { + def typedBlock(block0: Block, mode: Mode, pt: Type): Block = { val syntheticPrivates = new ListBuffer[Symbol] try { - namer.enterSyms(block.stats) + namer.enterSyms(block0.stats) + val block = treeCopy.Block(block0, pluginsEnterStats(this, block0.stats), block0.expr) for (stat <- block.stats) enterLabelDef(stat) if (phaseId(currentPeriod) <= currentRun.typerPhase.id) { @@ -3797,7 +3802,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper protected def typedExistentialTypeTree(tree: ExistentialTypeTree, mode: Mode): Tree = { for (wc <- tree.whereClauses) - if (wc.symbol == NoSymbol) { namer.enterSym(wc); wc.symbol setFlag EXISTENTIAL } + if (wc.symbol == NoSymbol) { namer enterSym wc; wc.symbol setFlag EXISTENTIAL } else context.scope enter wc.symbol val whereClauses1 = typedStats(tree.whereClauses, context.owner) for (vd @ ValDef(_, _, _, _) <- whereClauses1) @@ -4948,7 +4953,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val sym: Symbol = tree.symbol if ((sym ne null) && (sym ne NoSymbol)) sym.initialize - def typedPackageDef(pdef: PackageDef) = { + def typedPackageDef(pdef0: PackageDef) = { + val pdef = treeCopy.PackageDef(pdef0, pdef0.pid, pluginsEnterStats(this, pdef0.stats)) val pid1 = typedQualifier(pdef.pid).asInstanceOf[RefTree] assert(sym.moduleClass ne NoSymbol, sym) val stats1 = newTyper(context.make(tree, sym.moduleClass, sym.info.decls)) @@ -5494,25 +5500,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper tpe } - def computeMacroDefType(tree: Tree, pt: Type): Type = { + def computeMacroDefType(ddef: DefDef, pt: Type): Type = { assert(context.owner.isMacro, context.owner) - assert(tree.symbol.isMacro, tree.symbol) - assert(tree.isInstanceOf[DefDef], tree.getClass) - val ddef = tree.asInstanceOf[DefDef] + assert(ddef.symbol.isMacro, ddef.symbol) - val tree1 = + val rhs1 = if (transformed contains ddef.rhs) { // macro defs are typechecked in `methodSig` (by calling this method) in order to establish their link to macro implementation asap // if a macro def doesn't have explicitly specified return type, this method will be called again by `assignTypeToTree` // here we guard against this case transformed(ddef.rhs) } else { - val tree1 = typedMacroBody(this, ddef) - transformed(ddef.rhs) = tree1 - tree1 + val rhs1 = typedMacroBody(this, ddef) + transformed(ddef.rhs) = rhs1 + rhs1 } - val isMacroBodyOkay = !tree.symbol.isErroneous && !(tree1 exists (_.isErroneous)) && tree1 != EmptyTree + val isMacroBodyOkay = !ddef.symbol.isErroneous && !(rhs1 exists (_.isErroneous)) && rhs1 != EmptyTree val shouldInheritMacroImplReturnType = ddef.tpt.isEmpty if (isMacroBodyOkay && shouldInheritMacroImplReturnType) { val commonMessage = "macro defs must have explicitly specified return types" @@ -5524,7 +5528,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val explanation = s"inference of $inferredType from macro impl's c.Expr[$inferredType] is deprecated and is going to stop working in 2.12" unit.deprecationWarning(ddef.pos, s"$commonMessage ($explanation)") } - computeMacroDefTypeFromMacroImplRef(ddef, tree1) match { + computeMacroDefTypeFromMacroImplRef(ddef, rhs1) match { case ErrorType => ErrorType case NothingTpe => NothingTpe case NoType => reportFailure(); AnyTpe |