diff options
author | Paul Phillips <paulp@improving.org> | 2012-09-27 16:12:50 -0700 |
---|---|---|
committer | Eugene Burmako <xeno.by@gmail.com> | 2012-09-28 14:22:05 +0200 |
commit | b5b614444cefe84861b06a09fbaf10d100556556 (patch) | |
tree | 1b7b0e8f8410daa2cd41b76257d09e97ad4a0958 | |
parent | a6b81ac12a45866e97d30133c12dee775b93ea39 (diff) | |
download | scala-b5b614444cefe84861b06a09fbaf10d100556556.tar.gz scala-b5b614444cefe84861b06a09fbaf10d100556556.tar.bz2 scala-b5b614444cefe84861b06a09fbaf10d100556556.zip |
Implementations of isValueType and isNonValueType.
Restrictions regarding how non-value types can be used have
generally not been enforced explicitly, depending instead on
the fact that the compiler wouldn't attempt to use them in
strange ways like offering a method type as a type argument.
Since users can now create most types from scratch, it has
become important to enforce the restrictions in a more
direct fashion.
This was a lot harder than it probably should have been
because there are so many types which go unmentioned by the
specification. Hopefully a useful exercise in any case.
-rw-r--r-- | src/compiler/scala/reflect/reify/utils/Extractors.scala | 13 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Types.scala | 116 | ||||
-rw-r--r-- | test/files/run/fail-non-value-types.check | 2 | ||||
-rw-r--r-- | test/files/run/fail-non-value-types.scala | 30 | ||||
-rw-r--r-- | test/files/run/macro-reify-type/Macros_1.scala | 2 |
5 files changed, 150 insertions, 13 deletions
diff --git a/src/compiler/scala/reflect/reify/utils/Extractors.scala b/src/compiler/scala/reflect/reify/utils/Extractors.scala index 523520749b..b60d15c1d4 100644 --- a/src/compiler/scala/reflect/reify/utils/Extractors.scala +++ b/src/compiler/scala/reflect/reify/utils/Extractors.scala @@ -92,13 +92,12 @@ trait Extractors { Block(List(universeAlias, mirrorAlias), wrappee) } - private def mkTarg(tpe: Type): Tree = { - // if we're reifying a MethodType, we can't use it as a type argument for TypeTag ctor - // http://groups.google.com/group/scala-internals/browse_thread/thread/2d7bb85bfcdb2e2 - val guineaPig = Apply(TypeApply(Select(Select(gen.mkRuntimeUniverseRef, nme.TypeTag), nme.apply), List(TypeTree(tpe))), List(Literal(Constant(null)), Literal(Constant(null)))) - val isGoodTpe = typer.silent(_.typed(guineaPig)) match { case analyzer.SilentResultValue(_) => true; case _ => false } - TypeTree(if (isGoodTpe) tpe else AnyTpe) - } + // if we're reifying a MethodType, we can't use it as a type argument for TypeTag ctor + // http://groups.google.com/group/scala-internals/browse_thread/thread/2d7bb85bfcdb2e2 + private def mkTarg(tpe: Type): Tree = ( + if ((tpe eq null) || !isUseableAsTypeArg(tpe)) TypeTree(AnyTpe) + else TypeTree(tpe) + ) object ReifiedTree { def apply(universe: Tree, mirror: Tree, symtab: SymbolTable, rtree: Tree, tpe: Type, rtpe: Tree, concrete: Boolean): Tree = { diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 6b274467fc..0e8665ee84 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -66,9 +66,9 @@ import util.ThreeValues._ // a type variable // Replace occurrences of type parameters with type vars, where // inst is the instantiation and constr is a list of bounds. - case DeBruijnIndex(level, index) + case DeBruijnIndex(level, index, args) // for dependent method types: a type referring to a method parameter. - case ErasedValueType(clazz, underlying) + case ErasedValueType(tref) // only used during erasure of derived value classes. */ @@ -3623,9 +3623,20 @@ trait Types extends api.Types { self: SymbolTable => */ /** A creator for type applications */ - def appliedType(tycon: Type, args: List[Type]): Type = - if (args.isEmpty) tycon //@M! `if (args.isEmpty) tycon' is crucial (otherwise we create new types in phases after typer and then they don't get adapted (??)) - else tycon match { + def appliedType(tycon: Type, args: List[Type]): Type = { + if (args.isEmpty) + return tycon //@M! `if (args.isEmpty) tycon' is crucial (otherwise we create new types in phases after typer and then they don't get adapted (??)) + + /** Disabled - causes cycles in tcpoly tests. */ + if (false && isDefinitionsInitialized) { + assert(isUseableAsTypeArgs(args), { + val tapp_s = s"""$tycon[${args mkString ", "}]""" + val arg_s = args filterNot isUseableAsTypeArg map (t => t + "/" + t.getClass) mkString ", " + s"$tapp_s includes illegal type argument $arg_s" + }) + } + + tycon match { case TypeRef(pre, sym @ (NothingClass|AnyClass), _) => copyTypeRef(tycon, pre, sym, Nil) //@M drop type args to Any/Nothing case TypeRef(pre, sym, _) => copyTypeRef(tycon, pre, sym, args) case PolyType(tparams, restpe) => restpe.instantiateTypeParams(tparams, args) @@ -3639,6 +3650,7 @@ trait Types extends api.Types { self: SymbolTable => case WildcardType => tycon // needed for neg/t0226 case _ => abort(debugString(tycon)) } + } /** Very convenient. */ def appliedType(tyconSym: Symbol, args: Type*): Type = @@ -5732,6 +5744,100 @@ trait Types extends api.Types { self: SymbolTable => case _ => false } + /** This is defined and named as it is because the goal is to exclude source + * level types which are not value types (e.g. MethodType) without excluding + * necessary internal types such as WildcardType. There are also non-value + * types which can be used as type arguments (e.g. type constructors.) + */ + def isUseableAsTypeArg(tp: Type) = ( + isInternalTypeUsedAsTypeArg(tp) // the subset of internal types which can be type args + || isHKTypeRef(tp) // not a value type, but ok as a type arg + || isValueElseNonValue(tp) // otherwise only value types + ) + + private def isHKTypeRef(tp: Type) = tp match { + case TypeRef(_, sym, Nil) => tp.isHigherKinded + case _ => false + } + @tailrec final def isUseableAsTypeArgs(tps: List[Type]): Boolean = tps match { + case Nil => true + case x :: xs => isUseableAsTypeArg(x) && isUseableAsTypeArgs(xs) + } + + /** The "third way", types which are neither value types nor + * non-value types as defined in the SLS, further divided into + * types which are used internally in type applications and + * types which are not. + */ + private def isInternalTypeNotUsedAsTypeArg(tp: Type): Boolean = tp match { + case AntiPolyType(pre, targs) => true + case ClassInfoType(parents, defs, clazz) => true + case DeBruijnIndex(level, index, args) => true + case ErasedValueType(tref) => true + case NoPrefix => true + case NoType => true + case SuperType(thistpe, supertpe) => true + case TypeBounds(lo, hi) => true + case _ => false + } + private def isInternalTypeUsedAsTypeArg(tp: Type): Boolean = tp match { + case WildcardType => true + case BoundedWildcardType(_) => true + case ErrorType => true + case _: TypeVar => true + case _ => false + } + private def isAlwaysValueType(tp: Type) = tp match { + case RefinedType(_, _) => true + case ExistentialType(_, _) => true + case ConstantType(_) => true + case _ => false + } + private def isAlwaysNonValueType(tp: Type) = tp match { + case OverloadedType(_, _) => true + case NullaryMethodType(_) => true + case MethodType(_, _) => true + case PolyType(_, MethodType(_, _)) => true + case _ => false + } + /** Should be called only with types for which a clear true/false answer + * can be given: true == value type, false == non-value type. Otherwise, + * an exception is thrown. + */ + private def isValueElseNonValue(tp: Type): Boolean = tp match { + case tp if isAlwaysValueType(tp) => true + case tp if isAlwaysNonValueType(tp) => false + case AnnotatedType(_, underlying, _) => isValueElseNonValue(underlying) + case SingleType(_, sym) => sym.isValue // excludes packages and statics + case TypeRef(_, _, _) if tp.isHigherKinded => false // excludes type constructors + case ThisType(sym) => !sym.isPackageClass // excludes packages + case TypeRef(_, sym, _) => !sym.isPackageClass // excludes packages + case PolyType(_, _) => true // poly-methods excluded earlier + case tp => sys.error("isValueElseNonValue called with third-way type " + tp) + } + + /** SLS 3.2, Value Types + * Is the given type definitely a value type? A true result means + * it verifiably is, but a false result does not mean it is not, + * only that it cannot be assured. To avoid false positives, this + * defaults to false, but since Type is not sealed, one should take + * a false answer with a grain of salt. This method may be primarily + * useful as documentation; it is likely that !isNonValueType(tp) + * will serve better than isValueType(tp). + */ + def isValueType(tp: Type) = isValueElseNonValue(tp) + + /** SLS 3.3, Non-Value Types + * Is the given type definitely a non-value type, as defined in SLS 3.3? + * The specification-enumerated non-value types are method types, polymorphic + * method types, and type constructors. Supplements to the specified set of + * non-value types include: types which wrap non-value symbols (packages + * abd statics), overloaded types. Varargs and by-name types T* and (=>T) are + * not designated non-value types because there is code which depends on using + * them as type arguments, but their precise status is unclear. + */ + def isNonValueType(tp: Type) = !isValueElseNonValue(tp) + def isNonRefinementClassType(tpe: Type) = tpe match { case SingleType(_, sym) => sym.isModuleClass case TypeRef(_, sym, _) => sym.isClass && !sym.isRefinementClass diff --git a/test/files/run/fail-non-value-types.check b/test/files/run/fail-non-value-types.check new file mode 100644 index 0000000000..1c2d7dcf1b --- /dev/null +++ b/test/files/run/fail-non-value-types.check @@ -0,0 +1,2 @@ +[B, That](f: A => B)(implicit cbf: ImaginaryCanBuildFrom[CompletelyIndependentList.this.Repr,B,That])That +()CompletelyIndependentList[A] diff --git a/test/files/run/fail-non-value-types.scala b/test/files/run/fail-non-value-types.scala new file mode 100644 index 0000000000..d9aa5c01ca --- /dev/null +++ b/test/files/run/fail-non-value-types.scala @@ -0,0 +1,30 @@ +import scala.reflect.runtime.universe._ + +class ImaginaryCanBuildFrom[-From, -Elem, +To] +class CompletelyIndependentList[+A] { + type Repr <: CompletelyIndependentList[A] + def map[B, That](f: A => B)(implicit cbf: ImaginaryCanBuildFrom[Repr, B, That]): That = ??? + def distinct(): CompletelyIndependentList[A] = ??? +} + +object Test { + var failed = false + def expectFailure[T](body: => T): Boolean = { + try { val res = body ; failed = true ; println(res + " failed to fail.") ; false } + catch { case _: AssertionError => true } + } + + /** Attempt to use a method type as a type argument - expect failure. */ + def tcon[T: TypeTag](args: Type*) = appliedType(typeOf[T].typeConstructor, args.toList) + + val map = typeOf[CompletelyIndependentList[Int]].member("map": TermName).typeSignature + val distinct = typeOf[CompletelyIndependentList[Int]].member("distinct": TermName).typeSignature + + def main(args: Array[String]): Unit = { + expectFailure(println(tcon[CompletelyIndependentList[Int]](map))) + expectFailure(tcon[CompletelyIndependentList[Int]](distinct)) + println(map) + println(distinct) + if (failed) sys.exit(1) + } +} diff --git a/test/files/run/macro-reify-type/Macros_1.scala b/test/files/run/macro-reify-type/Macros_1.scala index aeec9fc97c..06de05735d 100644 --- a/test/files/run/macro-reify-type/Macros_1.scala +++ b/test/files/run/macro-reify-type/Macros_1.scala @@ -17,7 +17,7 @@ object StaticReflect { case NoSymbol => c.error(c.enclosingPosition, s"No member called $nameName in $clazz.") ; reify(ru.NoType) case member => val mtpe = member typeSignatureIn clazz - val mtag = c.reifyType(c.runtimeUniverse, Select(c.runtimeUniverse, newTermName("rootMirror")), mtpe) + val mtag = c.reifyType(treeBuild.mkRuntimeUniverseRef, Select(treeBuild.mkRuntimeUniverseRef, newTermName("rootMirror")), mtpe) val mtree = Select(mtag, newTermName("tpe")) c.Expr[ru.Type](mtree) |