From d7814a235963f40f4de1bf49efd4b22a8b0da5db Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 8 Jul 2010 15:58:47 +0000 Subject: closes #2416. this patch introduces a new subclass of TypeTree: TypeTreeWithDeferredRefCheck, which tracks the type args and type params of a type application when it was beta-reduced during typing without checking that the application was well-kinded -- that check must wait until refchecks, but was never performed since the application had been beta-reduced away caveat discovered while working on the fix: adapt turned all trees for which _.isType holds into TypeTree's review by odersky --- .../scala/tools/nsc/ast/TreePrinters.scala | 4 ++ src/compiler/scala/tools/nsc/ast/Trees.scala | 15 +++++ .../scala/tools/nsc/typechecker/Infer.scala | 3 + .../scala/tools/nsc/typechecker/RefChecks.scala | 31 +++++++--- .../scala/tools/nsc/typechecker/Typers.scala | 68 +++++++++++++++------- test/files/neg/t2416.check | 10 ++++ test/files/neg/t2416.scala | 14 +++++ 7 files changed, 118 insertions(+), 27 deletions(-) create mode 100644 test/files/neg/t2416.check create mode 100644 test/files/neg/t2416.scala diff --git a/src/compiler/scala/tools/nsc/ast/TreePrinters.scala b/src/compiler/scala/tools/nsc/ast/TreePrinters.scala index 08f764a76f..10b50db6d5 100644 --- a/src/compiler/scala/tools/nsc/ast/TreePrinters.scala +++ b/src/compiler/scala/tools/nsc/ast/TreePrinters.scala @@ -379,6 +379,9 @@ trait TreePrinters { trees: SymbolTable => case SelectFromArray(qualifier, name, _) => print(qualifier); print("."); print(symName(tree, name)) + case TypeTreeWithDeferredRefCheck() => + print("") + case tree => print("") } @@ -575,6 +578,7 @@ trait TreePrinters { trees: SymbolTable => // eliminated by refchecks case ModuleDef(mods, name, impl) => + case TypeTreeWithDeferredRefCheck() => // eliminated by erasure case TypeDef(mods, name, tparams, rhs) => diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 35db3c0984..dbe4a587ba 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -338,6 +338,9 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => case class Parens(args: List[Tree]) extends Tree // only used during parsing + /** emitted by typer, eliminated by refchecks **/ + case class TypeTreeWithDeferredRefCheck()(val check: () => TypeTree) extends AbsTypeTree + // ----- subconstructors -------------------------------------------- class ApplyToImplicitArgs(fun: Tree, args: List[Tree]) extends Apply(fun, args) @@ -383,6 +386,7 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => def Ident(tree: Tree, name: Name): Ident def Literal(tree: Tree, value: Constant): Literal def TypeTree(tree: Tree): TypeTree + def TypeTreeWithDeferredRefCheck(tree: Tree): TypeTreeWithDeferredRefCheck def Annotated(tree: Tree, annot: Tree, arg: Tree): Annotated def SingletonTypeTree(tree: Tree, ref: Tree): SingletonTypeTree def SelectFromTypeTree(tree: Tree, qualifier: Tree, selector: Name): SelectFromTypeTree @@ -470,6 +474,9 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => new Literal(value).copyAttrs(tree) def TypeTree(tree: Tree) = new TypeTree().copyAttrs(tree) + def TypeTreeWithDeferredRefCheck(tree: Tree) = tree match { + case dc@TypeTreeWithDeferredRefCheck() => new TypeTreeWithDeferredRefCheck()(dc.check).copyAttrs(tree) + } def Annotated(tree: Tree, annot: Tree, arg: Tree) = new Annotated(annot, arg).copyAttrs(tree) def SingletonTypeTree(tree: Tree, ref: Tree) = @@ -670,6 +677,10 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => case t @ TypeTree() => t case _ => treeCopy.TypeTree(tree) } + def TypeTreeWithDeferredRefCheck(tree: Tree) = tree match { + case t @ TypeTreeWithDeferredRefCheck() => t + case _ => treeCopy.TypeTreeWithDeferredRefCheck(tree) + } def Annotated(tree: Tree, annot: Tree, arg: Tree) = tree match { case t @ Annotated(annot0, arg0) if (annot0==annot) => t @@ -816,6 +827,8 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => treeCopy.Literal(tree, value) case TypeTree() => treeCopy.TypeTree(tree) + case TypeTreeWithDeferredRefCheck() => + treeCopy.TypeTreeWithDeferredRefCheck(tree) case Annotated(annot, arg) => treeCopy.Annotated(tree, transform(annot), transform(arg)) case SingletonTypeTree(ref) => @@ -878,6 +891,8 @@ trait Trees extends reflect.generic.Trees { self: SymbolTable => traverse(definition) case Parens(ts) => traverseTrees(ts) + case TypeTreeWithDeferredRefCheck() => // TODO: should we traverse the wrapped tree? + // (and rewrap the result? how to update the deferred check? would need to store wrapped tree instead of returning it from check) case _ => super.traverse(tree) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index a2594a060b..ba0b8317b5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -246,6 +246,9 @@ trait Infer { /** Check that sym is defined and accessible as a member of * tree site with type pre in current context. + * + * Note: pre is not refchecked -- moreover, refchecking the resulting tree may not refcheck pre, + * since pre may not occur in its type (callers should wrap the result in a TypeTreeWithDeferredRefCheck) */ def checkAccessible(tree: Tree, sym: Symbol, pre: Type, site: Tree): Tree = if (sym.isError) { diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 479b843931..9c0d11550f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1020,7 +1020,7 @@ abstract class RefChecks extends InfoTransform { private def checkAnnotations(tpes: List[Type], pos: Position) = tpes foreach (tp => checkTypeRef(tp, pos)) private def doTypeTraversal(tree: Tree)(f: Type => Unit) = if (!inPattern) tree.tpe foreach f - private def applyRefchecksToAnnotations(tree: Tree) = { + private def applyRefchecksToAnnotations(tree: Tree): Unit = { def applyChecks(annots: List[AnnotationInfo]) = { checkAnnotations(annots map (_.atp), tree.pos) transformTrees(annots flatMap (_.args)) @@ -1028,10 +1028,18 @@ abstract class RefChecks extends InfoTransform { tree match { case m: MemberDef => applyChecks(m.symbol.annotations) - case TypeTree() => doTypeTraversal(tree) { - case AnnotatedType(annots, _, _) => applyChecks(annots) - case _ => - } + case tpt@TypeTree() => + if(tpt.original != null) { + tpt.original foreach { + case dc@TypeTreeWithDeferredRefCheck() => applyRefchecksToAnnotations(dc.check()) // #2416 + case _ => + } + } + + doTypeTraversal(tree) { + case AnnotatedType(annots, _, _) => applyChecks(annots) + case _ => + } case _ => } } @@ -1141,7 +1149,6 @@ abstract class RefChecks extends InfoTransform { // type bounds (bug #935), issues deprecation warnings for symbols used // inside annotations. applyRefchecksToAnnotations(tree) - var result: Tree = tree match { case DefDef(mods, name, tparams, vparams, tpt, EmptyTree) if tree.symbol.hasAnnotation(NativeAttr) => tree.symbol.resetFlag(DEFERRED) @@ -1162,7 +1169,17 @@ abstract class RefChecks extends InfoTransform { if (bridges.nonEmpty) treeCopy.Template(tree, parents, self, body ::: bridges) else tree - case TypeTree() => + case dc@TypeTreeWithDeferredRefCheck() => assert(false, "adapt should have turned dc: TypeTreeWithDeferredRefCheck into tpt: TypeTree, with tpt.original == dc"); dc + case tpt@TypeTree() => + if(tpt.original != null) { + tpt.original foreach { + case dc@TypeTreeWithDeferredRefCheck() => + transform(dc.check()) // #2416 -- only call transform to do refchecks, but discard results + // tpt has the right type if the deferred checks are ok + case _ => + } + } + val existentialParams = new ListBuffer[Symbol] doTypeTraversal(tree) { // check all bounds, except those that are // existential type parameters diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index be4cfe6512..6a81f1897f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -555,6 +555,8 @@ trait Typers { self: Analyzer => * If symbol refers to package object, insert `.package` as second to last selector. * (exception for some symbols in scala package which are dealiased immediately) * Call checkAccessible, which sets tree's attributes. + * Also note that checkAccessible looks up sym on pre without checking that pre is well-formed + * (illegal type applications in pre will be skipped -- that's why typedSelect wraps the resulting tree in a TreeWithDeferredChecks) * @return modified tree and new prefix type */ private def makeAccessible(tree: Tree, sym: Symbol, pre: Type, site: Tree): (Tree, Type) = @@ -2933,6 +2935,11 @@ trait Typers { self: Analyzer => errorTree(tree, treeSymTypeMsg(fun)+" does not take type parameters.") } + private[this] var typingIndent: String = "" + @inline def deindentTyping() = if (printTypings) typingIndent = typingIndent.substring(0, typingIndent.length() - 2) + @inline def indentTyping() = if (printTypings) typingIndent += " " + @inline def printTyping(s: => String) = if (printTypings) println(typingIndent+s) + /** * @param tree ... * @param mode ... @@ -3250,7 +3257,7 @@ trait Typers { self: Analyzer => def errorInResult(tree: Tree) = treesInResult(tree) exists (_.pos == ex.pos) if (fun :: tree :: args exists errorInResult) { - if (printTypings) println("second try for: "+fun+" and "+args) + printTyping("second try for: "+fun+" and "+args) val Select(qual, name) = fun val args1 = tryTypedArgs(args, argMode(fun, mode), ex) val qual1 = @@ -3260,9 +3267,8 @@ trait Typers { self: Analyzer => val tree1 = Apply(Select(qual1, name) setPos fun.pos, args1) setPos tree.pos return typed1(tree1, mode | SNDTRYmode, pt) } - } else if (printTypings) { - println("no second try for "+fun+" and "+args+" because error not in result:"+ex.pos+"!="+tree.pos) - } + } else printTyping("no second try for "+fun+" and "+args+" because error not in result:"+ex.pos+"!="+tree.pos) + reportTypeError(tree.pos, ex) setError(tree) } @@ -3501,9 +3507,9 @@ trait Typers { self: Analyzer => case Select(_, _) => treeCopy.Select(tree, qual, name) case SelectFromTypeTree(_, _) => treeCopy.SelectFromTypeTree(tree, qual, name) } - //if (name.toString == "Elem") println("typedSelect "+qual+":"+qual.tpe+" "+sym+"/"+tree1+":"+tree1.tpe) val (tree2, pre2) = makeAccessible(tree1, sym, qual.tpe, qual) val result = stabilize(tree2, pre2, mode, pt) + def isPotentialNullDeference() = { phase.id <= currentRun.typerPhase.id && !sym.isConstructor && @@ -3514,7 +3520,20 @@ trait Typers { self: Analyzer => if (settings.Xchecknull.value && isPotentialNullDeference && unit != null) unit.warning(tree.pos, "potential null pointer dereference: "+tree) - result + result match { + // could checkAccessible (called by makeAccessible) potentially have skipped checking a type application in qual? + case SelectFromTypeTree(qual@TypeTree(), name) if qual.tpe.typeArgs nonEmpty => // TODO: somehow the new qual is not checked in refchecks + treeCopy.SelectFromTypeTree( + result, + (TypeTreeWithDeferredRefCheck(){ () => val tp = qual.tpe; val sym = tp.typeSymbolDirect + // will execute during refchecks -- TODO: make private checkTypeRef in refchecks public and call that one? + checkBounds(qual.pos, tp.prefix, sym.owner, sym.typeParams, tp.typeArgs, "") + qual // you only get to see the wrapped tree after running this check :-p + }) setType qual.tpe, + name) + case _ => + result + } } } @@ -3647,6 +3666,7 @@ trait Typers { self: Analyzer => else atPos(tree.pos)(Select(qual, name)) // atPos necessary because qualifier might come from startContext val (tree2, pre2) = makeAccessible(tree1, defSym, pre, qual) + // assert(pre.typeArgs isEmpty) // no need to add #2416-style check here, right? stabilize(tree2, pre2, mode, pt) } } @@ -3672,7 +3692,7 @@ trait Typers { self: Analyzer => } else { val tparams = tpt1.symbol.typeParams if (tparams.length == args.length) { - // @M: kind-arity checking is done here and in adapt, full kind-checking is in checkKindBounds (in Infer) + // @M: kind-arity checking is done here and in adapt, full kind-checking is in checkKindBounds (in Infer) val args1 = if(!tpt1.symbol.rawInfo.isComplete) args mapConserve (typedHigherKindedType(_, mode)) @@ -3683,11 +3703,8 @@ trait Typers { self: Analyzer => //@M! the polytype denotes the expected kind } val argtypes = args1 map (_.tpe) - val owntype = if (tpt1.symbol.isClass || tpt1.symbol.isNonClassType) - // @M! added the latter condition - appliedType(tpt1.tpe, argtypes) - else tpt1.tpe.instantiateTypeParams(tparams, argtypes) - (args, tparams).zipped map { (arg, tparam) => arg match { + + (args, tparams).zipped foreach { (arg, tparam) => arg match { // note: can't use args1 in selector, because Bind's got replaced case Bind(_, _) => if (arg.symbol.isAbstractType) @@ -3697,7 +3714,17 @@ trait Typers { self: Analyzer => glb(List(arg.symbol.info.bounds.hi, tparam.info.bounds.hi.subst(tparams, argtypes)))) case _ => }} - TypeTree(owntype) setOriginal(tree) // setPos tree.pos + + val result = TypeTree(appliedType(tpt1.tpe, argtypes)) setOriginal(tree) // setPos tree.pos (done by setOriginal) + if(tpt1.tpe.isInstanceOf[PolyType]) // did the type application (performed by appliedType) involve an unchecked beta-reduction? + (TypeTreeWithDeferredRefCheck(){ () => + // wrap the tree and include the bounds check -- refchecks will perform this check (that the beta reduction was indeed allowed) and unwrap + // we can't simply use original in refchecks because it does not contains types + // (and the only typed trees we have have been mangled so they're not quite the original tree anymore) + checkBounds(result.pos, tpt1.tpe.prefix, tpt1.symbol.owner, tpt1.symbol.typeParams, argtypes, "") + result // you only get to see the wrapped tree after running this check :-p + }).setType(result.tpe) + else result } else if (tparams.length == 0) { errorTree(tree, tpt1.tpe+" does not take type parameters") } else { @@ -4009,7 +4036,7 @@ trait Typers { self: Analyzer => case SelectFromTypeTree(qual, selector) => val qual1 = typedType(qual, mode) if (qual1.tpe.isVolatile) error(tree.pos, "illegal type selection from volatile type "+qual.tpe) - typedSelect(typedType(qual, mode), selector) + typedSelect(qual1, selector) case CompoundTypeTree(templ) => typedCompoundTypeTree(templ) @@ -4025,6 +4052,7 @@ trait Typers { self: Analyzer => case etpt @ ExistentialTypeTree(_, _) => newTyper(context.makeNewScope(tree, context.owner)).typedExistentialTypeTree(etpt, mode) + case dc@TypeTreeWithDeferredRefCheck() => dc // TODO: should we re-type the wrapped tree? then we need to change TypeTreeWithDeferredRefCheck's representation to include the wrapped tree explicitly (instead of in its closure) case tpt @ TypeTree() => if (tpt.original != null) tree setType typedType(tpt.original, mode).tpe @@ -4047,8 +4075,7 @@ trait Typers { self: Analyzer => * @param pt ... * @return ... */ - def typed(tree: Tree, mode: Int, pt: Type): Tree = { - + def typed(tree: Tree, mode: Int, pt: Type): Tree = { indentTyping() def dropExistential(tp: Type): Type = tp match { case ExistentialType(tparams, tpe) => if (settings.debug.value) println("drop ex "+tree+" "+tp) @@ -4074,15 +4101,15 @@ trait Typers { self: Analyzer => tree.tpe = null if (tree.hasSymbol) tree.symbol = NoSymbol } - if (printTypings) println("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors) //DEBUG + printTyping("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors) //DEBUG var tree1 = if (tree.tpe ne null) tree else typed1(tree, mode, dropExistential(pt)) - if (printTypings) println("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt) //DEBUG + printTyping("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt) //DEBUG tree1.tpe = addAnnotations(tree1, tree1.tpe) val result = if (tree1.isEmpty) tree1 else adapt(tree1, mode, pt, tree) - if (printTypings) println("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams) //DEBUG + printTyping("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams) //DEBUG // for (t <- tree1.tpe) assert(t != WildcardType) // if ((mode & TYPEmode) != 0) println("type: "+tree1+" has type "+tree1.tpe) if (phase.id <= currentRun.typerPhase.id) signalDone(context.asInstanceOf[analyzer.Context], tree, result) @@ -4090,7 +4117,7 @@ trait Typers { self: Analyzer => } catch { case ex: TypeError => tree.tpe = null - if (printTypings) println("caught "+ex+" in typed: "+tree) //DEBUG + printTyping("caught "+ex+" in typed: "+tree) //DEBUG reportTypeError(tree.pos, ex) setError(tree) case ex: Exception => @@ -4102,6 +4129,7 @@ trait Typers { self: Analyzer => throw ex } finally { + deindentTyping() if (Statistics.enabled) { val t = currentTime() microsByType(pendingTreeTypes.head) += ((t - typerTime) / 1000).toInt diff --git a/test/files/neg/t2416.check b/test/files/neg/t2416.check new file mode 100644 index 0000000000..0899ad09d5 --- /dev/null +++ b/test/files/neg/t2416.check @@ -0,0 +1,10 @@ +t2416.scala:3: error: type arguments [Int] do not conform to trait A's type parameter bounds [X <: Double] + def x : A[Int]#B = 10 // no you won't + ^ +t2416.scala:8: error: type arguments [Boolean] do not conform to type B's type parameter bounds [Y <: Double] + def x : A#B[Boolean] = 10 // seriously? + ^ +t2416.scala:13: error: type arguments [String] do not conform to type B's type parameter bounds [Z <: Double] + type C[Z <: A] = Z#B[String] // nuh-uh! + ^ +three errors found diff --git a/test/files/neg/t2416.scala b/test/files/neg/t2416.scala new file mode 100644 index 0000000000..6bb57a984b --- /dev/null +++ b/test/files/neg/t2416.scala @@ -0,0 +1,14 @@ +object t2416a { + trait A[X <: Double] { type B = X } + def x : A[Int]#B = 10 // no you won't +} + +object t2416b { + trait A{type B[Y <: Double] = Int} + def x : A#B[Boolean] = 10 // seriously? +} + +object t2416c { + trait A{type B[Z <: Double] = Int} + type C[Z <: A] = Z#B[String] // nuh-uh! +} \ No newline at end of file -- cgit v1.2.3