From c39f26382dddaa7e000022704792c813f8bf7f2b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 17 Oct 2010 21:01:31 +0000 Subject: Closes #3871. --- src/compiler/scala/tools/nsc/symtab/Symbols.scala | 3 +- .../scala/tools/nsc/typechecker/Contexts.scala | 48 +++++++++++++++++++--- .../scala/tools/nsc/typechecker/Infer.scala | 25 ++++++----- .../scala/tools/nsc/typechecker/Typers.scala | 23 ++++++++--- 4 files changed, 75 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index 1933893373..810c0d4f83 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -217,7 +217,6 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => sym setFlag (STABLE | SYNTHETIC) if (isTrait) sym setFlag DEFERRED sym.expandName(this) - // todo: stop depending on compiler bug (ticket #3871) to set referenced. sym.referenced = this sym } @@ -1663,7 +1662,7 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => privateWithin = NoSymbol - protected var referenced: Symbol = NoSymbol + var referenced: Symbol = NoSymbol def cloneSymbolImpl(owner: Symbol): Symbol = new TermSymbol(owner, pos, name).copyAttrsFrom(this) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 4906838e36..3e7f755b8e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -32,6 +32,8 @@ trait Contexts { self: Analyzer => global.definitions.RootClass.info.decls) } + var lastAccessCheckDetails: String = "" + /** List of objects and packages to import from in * a root context. This list is sensitive to the * compiler settings. @@ -324,6 +326,12 @@ trait Contexts { self: Analyzer => " " + scope.toList + "\n:: " + outer.toString() } + /** Is `sub' a subclass of `base' or a companion object of such a subclass? + */ + def isSubClassOrCompanion(sub: Symbol, base: Symbol) = + sub.isNonBottomSubClass(base) || + sub.isModuleClass && sub.linkedClassOfClass.isNonBottomSubClass(base) + /** Return closest enclosing context that defines a superclass of `clazz', or a * companion module of a superclass of `clazz', or NoContext if none exists */ def enclosingSuperClassContext(clazz: Symbol): Context = { @@ -335,11 +343,12 @@ trait Contexts { self: Analyzer => c } - /** Return closest enclosing context that defines a subclass of `clazz', or NoContext - * if none exists */ + /** Return closest enclosing context that defines a subclass of `clazz' or a companion + * object thereof, or NoContext if no such context exists + */ def enclosingSubClassContext(clazz: Symbol): Context = { var c = this.enclClass - while (c != NoContext && !c.owner.isNonBottomSubClass(clazz)) + while (c != NoContext && !isSubClassOrCompanion(c.owner, clazz)) c = c.outer.enclClass c } @@ -353,6 +362,8 @@ trait Contexts { self: Analyzer => * @return ... */ def isAccessible(sym: Symbol, pre: Type, superAccess: Boolean): Boolean = { + lastAccessCheckDetails = "" + @inline def accessWithinLinked(ab: Symbol) = { val linked = ab.linkedClassOfClass // don't have access if there is no linked class @@ -364,7 +375,7 @@ trait Contexts { self: Analyzer => /** Are we inside definition of `ab'? */ def accessWithin(ab: Symbol) = { // #3663: we must disregard package nesting if sym isJavaDefined - if(sym.isJavaDefined) { + if (sym.isJavaDefined) { // is `o` or one of its transitive owners equal to `ab`? // stops at first package, since further owners can only be surrounding packages @tailrec def abEnclosesStopAtPkg(o: Symbol): Boolean = @@ -392,6 +403,28 @@ trait Contexts { self: Analyzer => case _ => false } + /** Is protected access to target symbol permitted */ + def isProtectedAccessOK(target: Symbol) = { + val c = enclosingSubClassContext(sym.owner) + if (c == NoContext) + lastAccessCheckDetails = + "\n Access to protected "+target+" not permitted because"+ + "\n "+"enclosing class "+this.enclClass.owner+this.enclClass.owner.locationString+" is not a subclass of "+ + "\n "+sym.owner+sym.owner.locationString+" where target is defined" + c != NoContext && { + val res = + isSubClassOrCompanion(pre.widen.typeSymbol, c.owner) || + c.owner.isModuleClass && + isSubClassOrCompanion(pre.widen.typeSymbol, c.owner.linkedClassOfClass) + if (!res) + lastAccessCheckDetails = + "\n Access to protected "+target+" not permitted because"+ + "\n prefix type "+pre.widen+" does not conform to" + "\n "+c.owner+c.owner.locationString+" where the access take place" + res + } + } + (pre == NoPrefix) || { val ab = sym.accessBoundary(sym.owner) ( (ab.isTerm || ab == definitions.RootClass) @@ -403,9 +436,12 @@ trait Contexts { self: Analyzer => ) || (sym hasFlag PROTECTED) && ( superAccess + || pre.isInstanceOf[ThisType] || sym.isConstructor - || (pre.widen.typeSymbol.isNonBottomSubClass(sym.owner) && - (isSubClassOfEnclosing(pre.widen.typeSymbol) || phase.erasedTypes)) + || phase.erasedTypes + || isProtectedAccessOK(sym) + || (sym.allOverriddenSymbols exists isProtectedAccessOK) + // that last condition makes protected access via self types work. ) ) // note: phase.erasedTypes disables last test, because after addinterfaces diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index dc143666a1..fdcb692e45 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -235,6 +235,14 @@ trait Infer { def explainTypes(tp1: Type, tp2: Type) = withDisambiguation(tp1, tp2)(global.explainTypes(tp1, tp2)) + def accessError(tree: Tree, sym: Symbol, pre: Type, explanation: String): Tree = { + val realsym = underlying(sym) + + errorTree(tree, realsym + realsym.locationString + " cannot be accessed in " + + (if (sym.isClassConstructor) context.enclClass.owner else pre.widen) + + explanation) + } + /* -- Tests & Checks---------------------------------------------------- */ /** Check that sym is defined and accessible as a member of @@ -247,14 +255,6 @@ trait Infer { if (sym.isError) { tree setSymbol sym setType ErrorType } else { - def accessError(explanation: String): Tree = { - val realsym = underlying(sym) - - errorTree(tree, realsym + realsym.locationString + " cannot be accessed in " + - (if (sym.isClassConstructor) context.enclClass.owner else pre.widen) + - explanation) - } - val topClass = context.owner.toplevelClass if (context.unit != null) context.unit.depends += sym.toplevelClass @@ -268,8 +268,10 @@ trait Infer { Console.println(tree) Console.println("" + pre + " " + sym.owner + " " + context.owner + " " + context.outer.enclClass.owner + " " + sym.owner.thisType + (pre =:= sym.owner.thisType)) } - accessError( - if (settings.check.isDefault) "" else { + accessError(tree, sym, pre, + if (settings.check.isDefault) { + analyzer.lastAccessCheckDetails + } else { "\n because of an internal error (no accessible symbol):" + "\nsym = " + sym + "\nunderlying(sym) = " + underlying(sym) + @@ -294,7 +296,8 @@ trait Infer { if (settings.debug.value) ex.printStackTrace val sym2 = underlying(sym1) val itype = pre.memberType(sym2) - accessError("\n because its instance type "+itype+ + accessError(tree, sym, pre, + "\n because its instance type "+itype+ (if ("malformed type: "+itype.toString==ex.msg) " is malformed" else " contains a "+ex.msg)) ErrorType diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index fa62e24d96..2c8d1c845f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3664,9 +3664,12 @@ trait Typers { self: Analyzer => def ambiguousError(msg: String) = error(tree.pos, "reference to " + name + " is ambiguous;\n" + msg) - var defSym: Symbol = tree.symbol // the directly found symbol - var pre: Type = NoPrefix // the prefix type of defSym, if a class member - var qual: Tree = EmptyTree // the qualifier tree if transformed tree is a select + var defSym: Symbol = tree.symbol // the directly found symbol + var pre: Type = NoPrefix // the prefix type of defSym, if a class member + var qual: Tree = EmptyTree // the qualifier tree if transformed tree is a select + var inaccessibleSym: Symbol = NoSymbol // the first symbol that was found but that was discarded + // for being inaccessible; used for error reporting + var inaccessibleExplanation: String = "" // A symbol qualifies if it exists and is not stale. Stale symbols // are made to disappear here. In addition, @@ -3699,7 +3702,13 @@ trait Typers { self: Analyzer => cx = cx.enclClass defSym = pre.member(name) filter ( sym => qualifies(sym) && context.isAccessible(sym, pre, false)) - if (defSym == NoSymbol) cx = cx.outer + if (defSym == NoSymbol) { + if (inaccessibleSym eq NoSymbol) { + inaccessibleSym = pre.member(name) filter qualifies + inaccessibleExplanation = analyzer.lastAccessCheckDetails + } + cx = cx.outer + } } } @@ -3769,7 +3778,11 @@ trait Typers { self: Analyzer => if (settings.debug.value) { log(context.imports)//debug } - error(tree.pos, "not found: "+decodeWithNamespace(name)) + if (inaccessibleSym eq NoSymbol) { + error(tree.pos, "not found: "+decodeWithNamespace(name)) + } else accessError( + tree, inaccessibleSym, context.enclClass.owner.thisType, + inaccessibleExplanation) defSym = context.owner.newErrorSymbol(name) } } -- cgit v1.2.3