From 2064372659156e2637eac4d092321818a30abb41 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 2 Jan 2012 11:48:22 -0800 Subject: Better error reporting regarding main methods. Now notices most things which look like main methods and says something useful if they aren't usable as entry points. Closes SI-4749. --- .../scala/reflect/internal/Definitions.scala | 17 +++--- .../scala/reflect/internal/transform/Erasure.scala | 2 +- .../scala/tools/nsc/backend/jvm/GenJVM.scala | 68 ++++++++++++++-------- 3 files changed, 54 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala index d3af8e2623..cc6ba32f9c 100644 --- a/src/compiler/scala/reflect/internal/Definitions.scala +++ b/src/compiler/scala/reflect/internal/Definitions.scala @@ -443,15 +443,11 @@ trait Definitions extends reflect.api.StandardDefinitions { def isStringAddition(sym: Symbol) = sym == String_+ || sym == StringAdd_+ def isArrowAssoc(sym: Symbol) = ArrowAssocClass.tpe.decls.toList contains sym - // The given symbol is a method with the right signature to be a runnable java program. - def isJavaMainMethod(sym: Symbol) = sym.tpe match { - case MethodType(param :: Nil, restpe) if restpe.typeSymbol == UnitClass => - param.tpe match { - case TypeRef(_, ArrayClass, arg :: Nil) => arg.typeSymbol == StringClass - case _ => false - } - case _ => false - } + // The given symbol is a method with the right name and signature to be a runnable java program. + def isJavaMainMethod(sym: Symbol) = (sym.name == nme.main) && (sym.info match { + case MethodType(p :: Nil, restpe) => isArrayOfSymbol(p.tpe, StringClass) && restpe.typeSymbol == UnitClass + case _ => false + }) // The given class has a main method. def hasJavaMainMethod(sym: Symbol): Boolean = (sym.tpe member nme.main).alternatives exists isJavaMainMethod @@ -595,6 +591,9 @@ trait Definitions extends reflect.api.StandardDefinitions { def byNameType(arg: Type) = appliedType(ByNameParamClass.typeConstructor, List(arg)) def iteratorOfType(tp: Type) = appliedType(IteratorClass.typeConstructor, List(tp)) + lazy val StringArray = arrayType(StringClass.tpe) + lazy val ObjectArray = arrayType(ObjectClass.tpe) + def ClassType(arg: Type) = if (phase.erasedTypes || forMSIL) ClassClass.tpe else appliedType(ClassClass.typeConstructor, List(arg)) diff --git a/src/compiler/scala/reflect/internal/transform/Erasure.scala b/src/compiler/scala/reflect/internal/transform/Erasure.scala index c8cb6febfa..1e0ba17e15 100644 --- a/src/compiler/scala/reflect/internal/transform/Erasure.scala +++ b/src/compiler/scala/reflect/internal/transform/Erasure.scala @@ -75,7 +75,7 @@ trait Erasure { case TypeRef(pre, sym, args) => if (sym == ArrayClass) if (unboundedGenericArrayLevel(tp) == 1) ObjectClass.tpe - else if (args.head.typeSymbol.isBottomClass) arrayType(ObjectClass.tpe) + else if (args.head.typeSymbol.isBottomClass) ObjectArray else typeRef(apply(pre), sym, args map this) else if (sym == AnyClass || sym == AnyValClass || sym == SingletonClass || sym == NotNullClass) erasedTypeRef(ObjectClass) else if (sym == UnitClass) erasedTypeRef(BoxedUnitClass) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 03d1bc3ad2..4ec3ef5ae6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -30,13 +30,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with import global._ import icodes._ import icodes.opcodes._ - import definitions.{ - AnyClass, ObjectClass, ThrowsClass, ThrowableClass, ClassfileAnnotationClass, - SerializableClass, StringClass, ClassClass, FunctionClass, - DeprecatedAttr, SerializableAttr, SerialVersionUIDAttr, VolatileAttr, - TransientAttr, CloneableAttr, RemoteAttr, JavaCloneableClass, - RemoteInterfaceClass, RemoteExceptionClass, hasJavaMainMethod - } + import definitions._ val phaseName = "jvm" @@ -68,10 +62,10 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with def isJavaEntryPoint(clasz: IClass) = { val sym = clasz.symbol - def fail(msg: String) = { + def fail(msg: String, pos: Position = sym.pos) = { clasz.cunit.warning(sym.pos, - sym.name + " has a main method, but " + sym.fullName('.') + " will not be a runnable program.\n" + - " " + msg + ", which means no static forwarder can be generated.\n" + sym.name + " has a main method with parameter type Array[String], but " + sym.fullName('.') + " will not be a runnable program.\n" + + " Reason: " + msg // TODO: make this next claim true, if possible // by generating valid main methods as static in module classes // not sure what the jvm allows here @@ -79,19 +73,47 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with ) false } - sym.hasModuleFlag && hasJavaMainMethod(sym) && { - // At this point we've seen a module with a main method, so if this - // doesn't turn out to be a valid entry point, issue a warning. - val companion = sym.linkedClassOfClass - if (companion.isTrait) - fail("Its companion is a trait") - else if (hasJavaMainMethod(companion) && !(sym isSubClass companion)) - fail("Its companion contains its own main method") - // this is only because forwarders aren't smart enough yet - else if (companion.tpe.member(nme.main) != NoSymbol) - fail("Its companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") - else - true + def failNoForwarder(msg: String) = { + fail(msg + ", which means no static forwarder can be generated.\n") + } + val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { m => + m.info match { + case MethodType(p :: Nil, _) => p.tpe.typeSymbol == ArrayClass + case _ => false + } + } + // At this point it's a module with a main-looking method, so either + // succeed or warn that it isn't. + hasApproximate && { + // Before erasure so we can identify generic mains. + atPhase(currentRun.erasurePhase) { + val companion = sym.linkedClassOfClass + val companionMain = companion.tpe.member(nme.main) + + if (hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (companion.tpe.member(nme.main) != NoSymbol) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.isTrait) + failNoForwarder("companion is a trait") + // Now either succeeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else possibles exists { m => + m.info match { + case PolyType(_, _) => + fail("main methods cannot be generic.") + case MethodType(params, res) => + if (res.typeSymbol :: params exists (_.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.pos) + else + isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) + case tp => + fail("don't know what this is: " + tp, m.pos) + } + } + } } } -- cgit v1.2.3