Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ abstract class DefaultMacroCompiler extends Resolvers
case SilentResultValue(result) if looksLikeMacroBundleType(result.tpe) =>
val bundle = result.tpe.typeSymbol
if (!isMacroBundleType(bundle.tpe)) MacroBundleWrongShapeError()
if (!bundle.owner.isStaticOwner) MacroBundleNonStaticError()
if (!bundle.owner.isStaticOwner) {
val isReplClassBased = settings.Yreplclassbased.value && bundle.owner.enclosingTopLevelClass.isInterpreterWrapper
MacroBundleNonStaticError(isReplClassBased)
}
bundleResult.get
case _ =>
vanillaResult.get
Expand Down
14 changes: 11 additions & 3 deletions src/compiler/scala/reflect/macros/compiler/Errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ trait Errors extends Traces {
"macro implementation reference is ambiguous: makes sense both as\n"+
"a macro bundle method reference and a vanilla object method reference")

def MacroBundleNonStaticError() = bundleRefError("macro bundles must be static")
private def replClassBasedMacroAddendum(isReplClassBased: Boolean): String =
if (isReplClassBased)
"\nnote: macro definition is not supported in the REPL when using -Yrepl-classbased."
else ""


def MacroBundleNonStaticError(isReplClassBased: Boolean) = {
bundleRefError("macro bundles must be static" + replClassBasedMacroAddendum(isReplClassBased))
}

def MacroBundleWrongShapeError() = bundleRefError("macro bundles must be concrete monomorphic classes having a single constructor with a `val c: Context` parameter")

Expand All @@ -54,10 +62,10 @@ trait Errors extends Traces {

// sanity check errors

def MacroImplReferenceWrongShapeError() = implRefError(
def MacroImplReferenceWrongShapeError(isReplClassBased: Boolean = false) = implRefError(
"macro implementation reference has wrong shape. required:\n"+
"macro [<static object>].<method name>[[<type args>]] or\n" +
"macro [<macro bundle>].<method name>[[<type args>]]")
"macro [<macro bundle>].<method name>[[<type args>]]" + replClassBasedMacroAddendum(isReplClassBased))

def MacroImplWrongNumberOfTypeArgumentsError() = {
val diagnostic = if (macroImpl.typeParams.length > targs.length) "has too few type arguments" else "has too many arguments"
Expand Down
5 changes: 4 additions & 1 deletion src/compiler/scala/reflect/macros/compiler/Validators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ trait Validators {
val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner
val effectivelyStatic = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner
val correctBundleness = if (isImplMethod) macroImplOwner.isModuleClass else macroImplOwner.isClass && !macroImplOwner.isModuleClass
if (!effectivelyStatic || !correctBundleness) MacroImplReferenceWrongShapeError()
if (!effectivelyStatic || !correctBundleness) {
val isReplClassBased = settings.Yreplclassbased.value && effectiveOwner.enclosingTopLevelClass.isInterpreterWrapper
MacroImplReferenceWrongShapeError(isReplClassBased)
}
}

private def checkMacroDefMacroImplCorrespondence() = {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/scala/tools/nsc/settings/MutableSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ class MutableSettings(val errorFn: String => Unit, val pathFactory: PathFactory)
* Subclasses each define a `value` field of the appropriate type.
*/
abstract class Setting(val name: String, val helpDescription: String) extends AbsSetting with SettingValue with Mutable {
def withDefault(value: T): this.type = { v = value; this }

/** Will be called after this Setting is set for any extra work. */
private var _postSetHook: this.type => Unit = (x: this.type) => ()
override def postSetHook(): Unit = _postSetHook(this)
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ trait ScalaSettings extends AbsScalaSettings
val YmacroFresh = BooleanSetting ("-Ymacro-global-fresh-names", "Should fresh names in macros be unique across all compilation units")
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 YreplMagicImport = BooleanSetting ("-Yrepl-use-magic-imports", "In the code the wraps REPL snippes, use magic imports to rather than nesting wrapper object/classes")
val YreplMagicImport = BooleanSetting ("-Yrepl-use-magic-imports", "In the code that wraps REPL snippets, use magic imports rather than nesting wrapper object/classes")
val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "")
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overridden methods.")
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
Expand Down
267 changes: 136 additions & 131 deletions src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala
Original file line number Diff line number Diff line change
Expand Up @@ -489,147 +489,152 @@ trait TypeDiagnostics {
def inMode(context: Context, mode: Mode, tree: Tree): Tree = if (mode.typingMonoExprByValue) apply(context, tree) else tree
}

class checkUnused(typer: Typer) {
object UnusedPrivates {
val ignoreNames: Set[TermName] = Set(
"readResolve", "readObject", "writeObject", "writeReplace"
).map(TermName(_))
}

class UnusedPrivates extends Traverser {
val defnTrees = ListBuffer[MemberDef]()
val targets = mutable.Set[Symbol]()
val setVars = mutable.Set[Symbol]()
val treeTypes = mutable.Set[Type]()
val params = mutable.Set[Symbol]()
val patvars = mutable.Set[Symbol]()

def defnSymbols = defnTrees.toList map (_.symbol)
def localVars = defnSymbols filter (t => t.isLocalToBlock && t.isVar)

def qualifiesTerm(sym: Symbol) = (
(sym.isModule || sym.isMethod || sym.isPrivateLocal || sym.isLocalToBlock)
&& !nme.isLocalName(sym.name)
&& !sym.isParameter
&& !sym.isParamAccessor // could improve this, but it's a pain
&& !sym.isEarlyInitialized // lots of false positives in the way these are encoded
&& !(sym.isGetter && sym.accessed.isEarlyInitialized)
)
def qualifiesType(sym: Symbol) = !sym.isDefinedInPackage
def qualifies(sym: Symbol) = (
(sym ne null)
&& (sym.isTerm && qualifiesTerm(sym) || sym.isType && qualifiesType(sym))
)
def isExisting(sym: Symbol) = sym != null && sym.exists

override def traverse(t: Tree): Unit = {
val sym = t.symbol
t match {
case m: MemberDef if qualifies(sym) && !t.isErrorTyped =>
t match {
case ValDef(mods@_, name@_, tpt@_, rhs@_) if wasPatVarDef(t) =>
if (settings.warnUnusedPatVars && !atBounded(t)) patvars += sym
case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !sym.isAbstract && !sym.isDeprecated && !sym.isMacro =>
if (sym.isPrimaryConstructor)
for (cpa <- sym.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa
else if (sym.isSynthetic && sym.isImplicit) return
else if (!sym.isConstructor && rhs.symbol != Predef_???)
for (vs <- vparamss) params ++= vs.map(_.symbol)
defnTrees += m
case _ =>
defnTrees += m
}
case CaseDef(pat, guard@_, rhs@_) if settings.warnUnusedPatVars && !t.isErrorTyped =>
pat.foreach {
case b @ Bind(n, _) if !atBounded(b) && n != nme.DEFAULT_CASE => patvars += b.symbol
case _ =>
}
case _: RefTree if isExisting(sym) => targets += sym
case Assign(lhs, _) if isExisting(lhs.symbol) => setVars += lhs.symbol
case Function(ps, _) if settings.warnUnusedParams && !t.isErrorTyped => params ++=
ps.filterNot(p => atBounded(p) || p.symbol.isSynthetic).map(_.symbol)
case _ =>
}
class UnusedPrivates extends Traverser {
import UnusedPrivates.ignoreNames
def isEffectivelyPrivate(sym: Symbol): Boolean = false
val defnTrees = ListBuffer[MemberDef]()
val targets = mutable.Set[Symbol]()
val setVars = mutable.Set[Symbol]()
val treeTypes = mutable.Set[Type]()
val params = mutable.Set[Symbol]()
val patvars = mutable.Set[Symbol]()

def defnSymbols = defnTrees.toList map (_.symbol)
def localVars = defnSymbols filter (t => t.isLocalToBlock && t.isVar)

def qualifiesTerm(sym: Symbol) = (
(sym.isModule || sym.isMethod || sym.isPrivateLocal || sym.isLocalToBlock || isEffectivelyPrivate(sym))
&& !nme.isLocalName(sym.name)
&& !sym.isParameter
&& !sym.isParamAccessor // could improve this, but it's a pain
&& !sym.isEarlyInitialized // lots of false positives in the way these are encoded
&& !(sym.isGetter && sym.accessed.isEarlyInitialized)
)
def qualifiesType(sym: Symbol) = !sym.isDefinedInPackage
def qualifies(sym: Symbol) = (
(sym ne null)
&& (sym.isTerm && qualifiesTerm(sym) || sym.isType && qualifiesType(sym))
)
def isExisting(sym: Symbol) = sym != null && sym.exists

if (t.tpe ne null) {
for (tp <- t.tpe) if (!treeTypes(tp)) {
// Include references to private/local aliases (which might otherwise refer to an enclosing class)
val isAlias = {
val td = tp.typeSymbolDirect
td.isAliasType && (td.isLocal || td.isPrivate)
}
// Ignore type references to an enclosing class. A reference to C must be outside C to avoid warning.
if (isAlias || !currentOwner.hasTransOwner(tp.typeSymbol)) tp match {
case NoType | NoPrefix =>
case NullaryMethodType(_) =>
case MethodType(_, _) =>
case SingleType(_, _) =>
case ConstantType(Constant(k: Type)) =>
log(s"classOf $k referenced from $currentOwner")
treeTypes += k
case _ =>
log(s"${if (isAlias) "alias " else ""}$tp referenced from $currentOwner")
treeTypes += tp
}
override def traverse(t: Tree): Unit = {
val sym = t.symbol
t match {
case m: MemberDef if qualifies(sym) && !t.isErrorTyped =>
t match {
case ValDef(mods@_, name@_, tpt@_, rhs@_) if wasPatVarDef(t) =>
if (settings.warnUnusedPatVars && !atBounded(t)) patvars += sym
case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !sym.isAbstract && !sym.isDeprecated && !sym.isMacro =>
if (sym.isPrimaryConstructor)
for (cpa <- sym.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa
else if (sym.isSynthetic && sym.isImplicit) return
else if (!sym.isConstructor && rhs.symbol != Predef_???)
for (vs <- vparamss) params ++= vs.map(_.symbol)
defnTrees += m
case _ =>
defnTrees += m
}
// e.g. val a = new Foo ; new a.Bar ; don't let a be reported as unused.
for (p <- t.tpe.prefix) condOpt(p) {
case SingleType(_, sym) => targets += sym
case CaseDef(pat, guard@_, rhs@_) if settings.warnUnusedPatVars && !t.isErrorTyped =>
pat.foreach {
case b @ Bind(n, _) if !atBounded(b) && n != nme.DEFAULT_CASE => patvars += b.symbol
case _ =>
}
}
super.traverse(t)
case _: RefTree if isExisting(sym) => targets += sym
case Assign(lhs, _) if isExisting(lhs.symbol) => setVars += lhs.symbol
case Function(ps, _) if settings.warnUnusedParams && !t.isErrorTyped => params ++=
ps.filterNot(p => atBounded(p) || p.symbol.isSynthetic).map(_.symbol)
case _ =>
}
def isUnusedType(m: Symbol): Boolean = (
m.isType
&& !m.isTypeParameterOrSkolem // would be nice to improve this
&& (m.isPrivate || m.isLocalToBlock)
&& !(treeTypes.exists(_.exists(_.typeSymbolDirect == m)))
)
def isSyntheticWarnable(sym: Symbol) = (
sym.isDefaultGetter
)
def isUnusedTerm(m: Symbol): Boolean = (
m.isTerm
&& (!m.isSynthetic || isSyntheticWarnable(m))
&& ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract)) || m.isLocalToBlock)
&& !targets(m)
&& !(m.name == nme.WILDCARD) // e.g. val _ = foo
&& (m.isValueParameter || !ignoreNames(m.name.toTermName)) // serialization methods
&& !isConstantType(m.info.resultType) // subject to constant inlining
&& !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar
)
def isUnusedParam(m: Symbol): Boolean = (
isUnusedTerm(m)
&& !m.isDeprecated
&& !m.owner.isDefaultGetter
&& !(m.isParamAccessor && (
m.owner.isImplicit ||
targets.exists(s => s.isParameter
&& s.name == m.name && s.owner.isConstructor && s.owner.owner == m.owner) // exclude ctor params
))
)
def sympos(s: Symbol): Int =
if (s.pos.isDefined) s.pos.point else if (s.isTerm) s.asTerm.referenced.pos.point else -1
def treepos(t: Tree): Int =
if (t.pos.isDefined) t.pos.point else sympos(t.symbol)

def unusedTypes = defnTrees.toList.filter(t => isUnusedType(t.symbol)).sortBy(treepos)
def unusedTerms = {
val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol))

// is this a getter-setter pair? and why is this a difficult question for traits?
def sameReference(g: Symbol, s: Symbol) =
if (g.accessed.exists && s.accessed.exists) g.accessed == s.accessed
else g.owner == s.owner && g.setterName == s.name //sympos(g) == sympos(s)

// filter out setters if already warning for getter.
val clean = all.filterNot(v => v.symbol.isSetter && all.exists(g => g.symbol.isGetter && sameReference(g.symbol, v.symbol)))
clean.sortBy(treepos)

if (t.tpe ne null) {
for (tp <- t.tpe) if (!treeTypes(tp)) {
// Include references to private/local aliases (which might otherwise refer to an enclosing class)
val isAlias = {
val td = tp.typeSymbolDirect
td.isAliasType && (td.isLocal || td.isPrivate)
}
// Ignore type references to an enclosing class. A reference to C must be outside C to avoid warning.
if (isAlias || !currentOwner.hasTransOwner(tp.typeSymbol)) tp match {
case NoType | NoPrefix =>
case NullaryMethodType(_) =>
case MethodType(_, _) =>
case SingleType(_, _) =>
case ConstantType(Constant(k: Type)) =>
log(s"classOf $k referenced from $currentOwner")
treeTypes += k
case _ =>
log(s"${if (isAlias) "alias " else ""}$tp referenced from $currentOwner")
treeTypes += tp
}
}
// e.g. val a = new Foo ; new a.Bar ; don't let a be reported as unused.
for (p <- t.tpe.prefix) condOpt(p) {
case SingleType(_, sym) => targets += sym
}
}
// local vars which are never set, except those already returned in unused
def unsetVars = localVars.filter(v => !setVars(v) && !isUnusedTerm(v)).sortBy(sympos)
def unusedParams = params.toList.filter(isUnusedParam).sortBy(sympos)
def inDefinedAt(p: Symbol) = p.owner.isMethod && p.owner.name == nme.isDefinedAt && p.owner.owner.isAnonymousFunction
def unusedPatVars = patvars.toList.filter(p => isUnusedTerm(p) && !inDefinedAt(p)).sortBy(sympos)
super.traverse(t)
}
def isUnusedType(m: Symbol): Boolean = (
m.isType
&& !m.isTypeParameterOrSkolem // would be nice to improve this
&& (m.isPrivate || m.isLocalToBlock || isEffectivelyPrivate(m))
&& !(treeTypes.exists(_.exists(_.typeSymbolDirect == m)))
)
def isSyntheticWarnable(sym: Symbol) = (
sym.isDefaultGetter
)
def isUnusedTerm(m: Symbol): Boolean = (
m.isTerm
&& (!m.isSynthetic || isSyntheticWarnable(m))
&& ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract)) || m.isLocalToBlock || isEffectivelyPrivate(m))
&& !targets(m)
&& !(m.name == nme.WILDCARD) // e.g. val _ = foo
&& (m.isValueParameter || !ignoreNames(m.name.toTermName)) // serialization/repl methods
&& !isConstantType(m.info.resultType) // subject to constant inlining
&& !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar
)
def isUnusedParam(m: Symbol): Boolean = (
isUnusedTerm(m)
&& !m.isDeprecated
&& !m.owner.isDefaultGetter
&& !(m.isParamAccessor && (
m.owner.isImplicit ||
targets.exists(s => s.isParameter
&& s.name == m.name && s.owner.isConstructor && s.owner.owner == m.owner) // exclude ctor params
))
)
def sympos(s: Symbol): Int =
if (s.pos.isDefined) s.pos.point else if (s.isTerm) s.asTerm.referenced.pos.point else -1
def treepos(t: Tree): Int =
if (t.pos.isDefined) t.pos.point else sympos(t.symbol)

def unusedTypes = defnTrees.toList.filter(t => isUnusedType(t.symbol)).sortBy(treepos)
def unusedTerms = {
val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol))

// is this a getter-setter pair? and why is this a difficult question for traits?
def sameReference(g: Symbol, s: Symbol) =
if (g.accessed.exists && s.accessed.exists) g.accessed == s.accessed
else g.owner == s.owner && g.setterName == s.name //sympos(g) == sympos(s)

// filter out setters if already warning for getter.
val clean = all.filterNot(v => v.symbol.isSetter && all.exists(g => g.symbol.isGetter && sameReference(g.symbol, v.symbol)))
clean.sortBy(treepos)
}
// local vars which are never set, except those already returned in unused
def unsetVars = localVars.filter(v => !setVars(v) && !isUnusedTerm(v)).sortBy(sympos)
def unusedParams = params.toList.filter(isUnusedParam).sortBy(sympos)
def inDefinedAt(p: Symbol) = p.owner.isMethod && p.owner.name == nme.isDefinedAt && p.owner.owner.isAnonymousFunction
def unusedPatVars = patvars.toList.filter(p => isUnusedTerm(p) && !inDefinedAt(p)).sortBy(sympos)
}

class checkUnused(typer: Typer) {

object skipMacroCall extends UnusedPrivates {
override def qualifiesTerm(sym: Symbol): Boolean =
Expand Down
Loading