# HG changeset patch # User mcimadamore # Date 1297770586 0 # Node ID 351027202f60898a18d75a9abd5b55cb93f180d3 # Parent ef6c66215a930ce74967bb88ac8312b5c79d1e86 7017664: Add listeners infrastracture to javac scopes Summary: Add listeners to javac scopes, added CompoundScope and correct invalidation logic for ImplementationCache Reviewed-by: jjg diff -r ef6c66215a93 -r 351027202f60 src/share/classes/com/sun/tools/javac/code/Scope.java --- a/src/share/classes/com/sun/tools/javac/code/Scope.java Mon Feb 14 14:27:47 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/code/Scope.java Tue Feb 15 11:49:46 2011 +0000 @@ -74,7 +74,7 @@ /** A list of scopes to be notified if items are to be removed from this scope. */ - List listeners = List.nil(); + List listeners = List.nil(); /** Use as a "not-found" result for lookup. * Also used to mark deleted entries in the table. @@ -219,12 +219,27 @@ Entry e = makeEntry(sym, old, elems, s, origin); table[hash] = e; elems = e; + + //notify listeners + for (List l = listeners; l.nonEmpty(); l = l.tail) { + l.head.symbolAdded(sym, this); + } } Entry makeEntry(Symbol sym, Entry shadowed, Entry sibling, Scope scope, Scope origin) { return new Entry(sym, shadowed, sibling, scope); } + + public interface ScopeListener { + public void symbolAdded(Symbol sym, Scope s); + public void symbolRemoved(Symbol sym, Scope s); + } + + public void addScopeListener(ScopeListener sl) { + listeners = listeners.prepend(sl); + } + /** Remove symbol from this scope. Used when an inner class * attribute tells us that the class isn't a package member. */ @@ -258,9 +273,9 @@ te = te.sibling; } - // remove items from scopes that have done importAll - for (List l = listeners; l.nonEmpty(); l = l.tail) { - l.head.remove(sym); + //notify listeners + for (List l = listeners; l.nonEmpty(); l = l.tail) { + l.head.symbolRemoved(sym, this); } } @@ -393,7 +408,32 @@ }; } }; + } + public Iterable getElementsByName(Name name) { + return getElementsByName(name, noFilter); + } + + public Iterable getElementsByName(final Name name, final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + Scope.Entry currentEntry = lookup(name, sf); + + public boolean hasNext() { + return currentEntry.scope != null; + } + public Symbol next() { + Scope.Entry prevEntry = currentEntry; + currentEntry = currentEntry.next(sf); + return prevEntry.sym; + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; } public String toString() { @@ -488,7 +528,7 @@ } } - public static class StarImportScope extends ImportScope { + public static class StarImportScope extends ImportScope implements ScopeListener { public StarImportScope(Symbol owner) { super(owner); @@ -500,8 +540,13 @@ enter(e.sym, fromScope); } // Register to be notified when imported items are removed - fromScope.listeners = fromScope.listeners.prepend(this); + fromScope.addScopeListener(this); } + + public void symbolRemoved(Symbol sym, Scope s) { + remove(sym); + } + public void symbolAdded(Symbol sym, Scope s) { } } /** An empty scope, into which you can't place anything. Used for @@ -538,6 +583,151 @@ } } + /** A class scope adds capabilities to keep track of changes in related + * class scopes - this allows client to realize whether a class scope + * has changed, either directly (because a new member has been added/removed + * to this scope) or indirectly (i.e. because a new member has been + * added/removed into a supertype scope) + */ + public static class CompoundScope extends Scope implements ScopeListener { + + public static final Entry[] emptyTable = new Entry[0]; + + private List subScopes = List.nil(); + private int mark = 0; + + public CompoundScope(Symbol owner) { + super(null, owner, emptyTable); + } + + public void addSubScope(Scope that) { + if (that != null) { + subScopes = subScopes.prepend(that); + that.addScopeListener(this); + mark++; + for (ScopeListener sl : listeners) { + sl.symbolAdded(null, this); //propagate upwards in case of nested CompoundScopes + } + } + } + + public void symbolAdded(Symbol sym, Scope s) { + mark++; + for (ScopeListener sl : listeners) { + sl.symbolAdded(sym, s); + } + } + + public void symbolRemoved(Symbol sym, Scope s) { + mark++; + for (ScopeListener sl : listeners) { + sl.symbolRemoved(sym, s); + } + } + + public int getMark() { + return mark; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("CompoundScope{"); + String sep = ""; + for (Scope s : subScopes) { + buf.append(sep); + buf.append(s); + sep = ","; + } + buf.append("}"); + return buf.toString(); + } + + @Override + public Iterable getElements(final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new CompoundScopeIterator(subScopes) { + Iterator nextIterator(Scope s) { + return s.getElements().iterator(); + } + }; + } + }; + } + + @Override + public Iterable getElementsByName(final Name name, final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new CompoundScopeIterator(subScopes) { + Iterator nextIterator(Scope s) { + return s.getElementsByName(name, sf).iterator(); + } + }; + } + }; + } + + abstract class CompoundScopeIterator implements Iterator { + + private Iterator currentIterator; + private List scopesToScan; + + public CompoundScopeIterator(List scopesToScan) { + this.scopesToScan = scopesToScan; + update(); + } + + abstract Iterator nextIterator(Scope s); + + public boolean hasNext() { + return currentIterator != null; + } + + public Symbol next() { + Symbol sym = currentIterator.next(); + if (!currentIterator.hasNext()) { + update(); + } + return sym; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private void update() { + while (scopesToScan.nonEmpty()) { + currentIterator = nextIterator(scopesToScan.head); + scopesToScan = scopesToScan.tail; + if (currentIterator.hasNext()) return; + } + currentIterator = null; + } + } + + @Override + public Entry lookup(Name name, Filter sf) { + throw new UnsupportedOperationException(); + } + + @Override + public Scope dup(Symbol newOwner) { + throw new UnsupportedOperationException(); + } + + @Override + public void enter(Symbol sym, Scope s, Scope origin) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove(Symbol sym) { + throw new UnsupportedOperationException(); + } + } + /** An error scope, for which the owner should be an error symbol. */ public static class ErrorScope extends Scope { ErrorScope(Scope next, Symbol errSymbol, Entry[] table) { diff -r ef6c66215a93 -r 351027202f60 src/share/classes/com/sun/tools/javac/code/Symbol.java --- a/src/share/classes/com/sun/tools/javac/code/Symbol.java Mon Feb 14 14:27:47 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/code/Symbol.java Tue Feb 15 11:49:46 2011 +0000 @@ -731,7 +731,7 @@ /** members closure cache (set by Types.membersClosure) */ - Scope membersClosure; + Scope.CompoundScope membersClosure; public ClassSymbol(long flags, Name name, Type type, Symbol owner) { super(flags, name, type, owner); diff -r ef6c66215a93 -r 351027202f60 src/share/classes/com/sun/tools/javac/code/Types.java --- a/src/share/classes/com/sun/tools/javac/code/Types.java Mon Feb 14 14:27:47 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/code/Types.java Tue Feb 15 11:49:46 2011 +0000 @@ -2023,18 +2023,22 @@ final MethodSymbol cachedImpl; final Filter implFilter; final boolean checkResult; + final int prevMark; public Entry(MethodSymbol cachedImpl, Filter scopeFilter, - boolean checkResult) { + boolean checkResult, + int prevMark) { this.cachedImpl = cachedImpl; this.implFilter = scopeFilter; this.checkResult = checkResult; + this.prevMark = prevMark; } - boolean matches(Filter scopeFilter, boolean checkResult) { + boolean matches(Filter scopeFilter, boolean checkResult, int mark) { return this.implFilter == scopeFilter && - this.checkResult == checkResult; + this.checkResult == checkResult && + this.prevMark == mark; } } @@ -2046,10 +2050,11 @@ _map.put(ms, new SoftReference>(cache)); } Entry e = cache.get(origin); + CompoundScope members = membersClosure(origin.type); if (e == null || - !e.matches(implFilter, checkResult)) { - MethodSymbol impl = implementationInternal(ms, origin, Types.this, checkResult, implFilter); - cache.put(origin, new Entry(impl, implFilter, checkResult)); + !e.matches(implFilter, checkResult, members.getMark())) { + MethodSymbol impl = implementationInternal(ms, origin, checkResult, implFilter); + cache.put(origin, new Entry(impl, implFilter, checkResult, members.getMark())); return impl; } else { @@ -2057,8 +2062,8 @@ } } - private MethodSymbol implementationInternal(MethodSymbol ms, TypeSymbol origin, Types types, boolean checkResult, Filter implFilter) { - for (Type t = origin.type; t.tag == CLASS || t.tag == TYPEVAR; t = types.supertype(t)) { + private MethodSymbol implementationInternal(MethodSymbol ms, TypeSymbol origin, boolean checkResult, Filter implFilter) { + for (Type t = origin.type; t.tag == CLASS || t.tag == TYPEVAR; t = supertype(t)) { while (t.tag == TYPEVAR) t = t.getUpperBound(); TypeSymbol c = t.tsym; @@ -2066,7 +2071,7 @@ e.scope != null; e = e.next(implFilter)) { if (e.sym != null && - e.sym.overrides(ms, origin, types, checkResult)) + e.sym.overrides(ms, origin, Types.this, checkResult)) return (MethodSymbol)e.sym; } } @@ -2082,46 +2087,35 @@ // // - public Scope membersClosure(Type site) { + public CompoundScope membersClosure(Type site) { return membersClosure.visit(site); } - UnaryVisitor membersClosure = new UnaryVisitor() { - - public Scope visitType(Type t, Void s) { + UnaryVisitor membersClosure = new UnaryVisitor() { + + public CompoundScope visitType(Type t, Void s) { return null; } @Override - public Scope visitClassType(ClassType t, Void s) { + public CompoundScope visitClassType(ClassType t, Void s) { ClassSymbol csym = (ClassSymbol)t.tsym; if (csym.membersClosure == null) { - Scope membersClosure = new Scope(csym); + CompoundScope membersClosure = new CompoundScope(csym); for (Type i : interfaces(t)) { - enterAll(visit(i), membersClosure); + membersClosure.addSubScope(visit(i)); } - enterAll(visit(supertype(t)), membersClosure); - enterAll(csym.members(), membersClosure); + membersClosure.addSubScope(visit(supertype(t))); + membersClosure.addSubScope(csym.members()); csym.membersClosure = membersClosure; } return csym.membersClosure; } @Override - public Scope visitTypeVar(TypeVar t, Void s) { + public CompoundScope visitTypeVar(TypeVar t, Void s) { return visit(t.getUpperBound()); } - - public void enterAll(Scope s, Scope to) { - if (s == null) return; - List syms = List.nil(); - for (Scope.Entry e = s.elems ; e != null ; e = e.sibling) { - syms = syms.prepend(e.sym); - } - for (Symbol sym : syms) { - to.enter(sym); - } - } }; // diff -r ef6c66215a93 -r 351027202f60 src/share/classes/com/sun/tools/javac/comp/Check.java --- a/src/share/classes/com/sun/tools/javac/comp/Check.java Mon Feb 14 14:27:47 2011 -0800 +++ b/src/share/classes/com/sun/tools/javac/comp/Check.java Tue Feb 15 11:49:46 2011 +0000 @@ -2106,32 +2106,32 @@ void checkOverrideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) { ClashFilter cf = new ClashFilter(site); //for each method m1 that is a member of 'site'... - for (Scope.Entry e1 = types.membersClosure(site).lookup(sym.name, cf) ; - e1.scope != null ; e1 = e1.next(cf)) { + for (Symbol s1 : types.membersClosure(site).getElementsByName(sym.name, cf)) { //...find another method m2 that is overridden (directly or indirectly) //by method 'sym' in 'site' - for (Scope.Entry e2 = types.membersClosure(site).lookup(sym.name, cf) ; - e2.scope != null ; e2 = e2.next(cf)) { - if (e1.sym == e2.sym || !sym.overrides(e2.sym, site.tsym, types, false)) continue; + for (Symbol s2 : types.membersClosure(site).getElementsByName(sym.name, cf)) { + if (s1 == s2 || !sym.overrides(s2, site.tsym, types, false)) continue; //if (i) the signature of 'sym' is not a subsignature of m1 (seen as //a member of 'site') and (ii) m1 has the same erasure as m2, issue an error - if (!types.isSubSignature(sym.type, types.memberType(site, e1.sym)) && - types.hasSameArgs(e1.sym.erasure(types), e2.sym.erasure(types))) { + if (!types.isSubSignature(sym.type, types.memberType(site, s1)) && + types.hasSameArgs(s1.erasure(types), s2.erasure(types))) { sym.flags_field |= CLASH; - String key = e2.sym == sym ? + String key = s2 == sym ? "name.clash.same.erasure.no.override" : "name.clash.same.erasure.no.override.1"; log.error(pos, key, sym, sym.location(), - e1.sym, e1.sym.location(), - e2.sym, e2.sym.location()); + s1, s1.location(), + s2, s2.location()); return; } } } } + + /** Check that all static methods accessible from 'site' are * mutually compatible (JLS 8.4.8). * @@ -2142,16 +2142,15 @@ void checkHideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) { ClashFilter cf = new ClashFilter(site); //for each method m1 that is a member of 'site'... - for (Scope.Entry e = types.membersClosure(site).lookup(sym.name, cf) ; - e.scope != null ; e = e.next(cf)) { + for (Symbol s : types.membersClosure(site).getElementsByName(sym.name, cf)) { //if (i) the signature of 'sym' is not a subsignature of m1 (seen as //a member of 'site') and (ii) 'sym' has the same erasure as m1, issue an error - if (!types.isSubSignature(sym.type, types.memberType(site, e.sym)) && - types.hasSameArgs(e.sym.erasure(types), sym.erasure(types))) { + if (!types.isSubSignature(sym.type, types.memberType(site, s)) && + types.hasSameArgs(s.erasure(types), sym.erasure(types))) { log.error(pos, "name.clash.same.erasure.no.hide", sym, sym.location(), - e.sym, e.sym.location()); + s, s.location()); return; } } diff -r ef6c66215a93 -r 351027202f60 test/tools/javac/scope/7017664/CompoundScopeTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/scope/7017664/CompoundScopeTest.java Tue Feb 15 11:49:46 2011 +0000 @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 7017664 + * @summary Basher for CompoundScopes + */ + +import java.util.Random; +import java.util.Map; +import java.util.HashMap; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Scope.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.file.JavacFileManager; + +public class CompoundScopeTest { + public static void main(String... args) throws Exception { + new CompoundScopeTest().run(args); + } + + static final int MAX_SYMBOLS_COUNT = 20; + static final int PASSES = 10; + + void run(String... args) throws Exception { + int count = PASSES; + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-seed") && (i + 1 < args.length)) + seed = Long.parseLong(args[++i]); + else if(arg.equals("-tests") && (i + 1 < args.length)) + count = Integer.parseInt(args[++i]); + else + throw new Exception("unknown arg: " + arg); + } + + rgen = new Random(seed); + + for (int i = 0; i < count; i++) { + Test t = new Test(); + t.run(); + } + + if (errors > 0) + throw new Exception(errors + " errors found"); + } + + /** + * Write a message to stderr. + */ + void log(String msg) { + System.err.println(msg); + } + + /** + * Write an error message to stderr. + */ + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + Random rgen; + long seed = 0; + + int errors; + + /** Class to encapsulate a test run. */ + class Test { + + List elems = List.nil(); + Map> shadowedMap = new HashMap>(); + + /** Run the test. */ + void run() throws Exception { + log ("starting test"); + setup(); + Scope[] scopes = { createScope(rgen.nextInt(MAX_SYMBOLS_COUNT)), + createScope(rgen.nextInt(MAX_SYMBOLS_COUNT)), + createScope(rgen.nextInt(MAX_SYMBOLS_COUNT)) }; + boolean[][] scopeNesting = { {false, true, false, true}, + {false, true, true, true}, + {false, false, true, true} }; + /** + * We want to generate (and check) the following compound scopes: + * C1 = C(S1, S2, S3) + * C2 = C((S1, S2), S3) + * C3 = C(S1, (S2, S3)) + * C3 = C(C(S1, S2, S3)) + */ + for (int i = 0 ; i < 4 ; i ++) { + CompoundScope root = new CompoundScope(symtab.noSymbol); + CompoundScope sub = new CompoundScope(symtab.noSymbol); + boolean subAdded = false; + for (int sc = 0 ; sc < 3 ; sc ++) { + if (scopeNesting[sc][i]) { + sub.addSubScope(scopes[sc]); + if (!subAdded) { + root.addSubScope(sub); + subAdded = true; + } + } else { + root.addSubScope(scopes[sc]); + } + } + log("testing scope: " + root); + checkElems(root); + checkShadowed(root); + } + } + + /** + * Create a scope containing a given number of synthetic symbols + */ + Scope createScope(int nelems) { + Scope s = new Scope(symtab.noSymbol); + for (int i = 0 ; i < nelems ; i++) { + Symbol sym = new TypeSymbol(0, names.fromString("s" + i), null, null); + s.enter(sym); + elems = elems.prepend(sym); + List shadowed = shadowedMap.get(sym.name); + if (shadowed == null) { + shadowed = List.nil(); + } + shadowedMap.put(sym.name, shadowed.prepend(sym)); + } + return s; + } + + /** + * Setup compiler context + */ + void setup() { + log ("setup"); + context = new Context(); + JavacFileManager.preRegister(context); // required by ClassReader which is required by Symtab + names = Names.instance(context); // Name.Table impls tied to an instance of Names + symtab = Symtab.instance(context); + } + + /** + * Check that CompoundScope.getElements() correctly visits all symbols + * in all subscopes (in the correct order) + */ + void checkElems(CompoundScope cs) { + List allSymbols = elems; + int count = 0; + for (Symbol s : cs.getElements()) { + checkSameSymbols(s, allSymbols.head); + allSymbols = allSymbols.tail; + count++; + } + if (count != elems.size()) { + error("CompoundScope.getElements() did not returned enough symbols"); + } + } + + /** + * Check that CompoundScope.getElements() correctly visits all symbols + * with a given name in all subscopes (in the correct order) + */ + void checkShadowed(CompoundScope cs) { + for (Map.Entry> shadowedEntry : shadowedMap.entrySet()) { + int count = 0; + List shadowed = shadowedEntry.getValue(); + Name name = shadowedEntry.getKey(); + for (Symbol s : cs.getElementsByName(name)) { + checkSameSymbols(s, shadowed.head); + shadowed = shadowed.tail; + count++; + } + if (count != shadowedEntry.getValue().size()) { + error("CompoundScope.lookup() did not returned enough symbols for name " + name); + } + } + } + + void checkSameSymbols(Symbol found, Symbol req) { + if (found != req) { + error("Symbol mismatch - found : " + found + ":" + found.hashCode() + "\n" + + " required : " + req + ":" + req.hashCode()); + } + } + + Context context; + Symtab symtab; + Names names; + } +} diff -r ef6c66215a93 -r 351027202f60 test/tools/javac/scope/7017664/ImplementationCacheTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/scope/7017664/ImplementationCacheTest.java Tue Feb 15 11:49:46 2011 +0000 @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 7017664 + * @summary Basher for CompoundScopes + */ + +import com.sun.source.util.JavacTask; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.util.Context; + +import com.sun.tools.javac.code.Symbol.*; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import javax.lang.model.element.Element; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +import static javax.tools.JavaFileObject.Kind; + +public class ImplementationCacheTest { + + static class SourceFile extends SimpleJavaFileObject { + + final String source = "interface I { void m(); }\n" + + "class A implements I { public void m() {} }\n" + + "class B extends A { }\n"; + + public SourceFile() { + super(URI.create("test.java"), Kind.SOURCE); + } + + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + } + + public static void main(String[] args) throws IOException { + List files = Arrays.asList(new SourceFile()); + JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); + JavacTask ct = (JavacTask)tool.getTask(null, null, null, null, null, files); + Context ctx = new Context(); + JavacFileManager.preRegister(ctx); + checkImplementationCache(ct.analyze(), Types.instance(ctx)); + } + + static void checkImplementationCache(Iterable elements, Types types) { + if (types == null) { + throw new AssertionError("problems initializing Types"); + } + + Symbol a = null; + Symbol b = null; + Symbol i = null; + + for (Element e : elements) { + if (e.getSimpleName().contentEquals("A")) { + a = (Symbol)e; + } else if (e.getSimpleName().contentEquals("B")) { + b = (Symbol)e; + } else if (e.getSimpleName().contentEquals("I")) { + i = (Symbol)e; + } + } + + if (a == null || b == null || i == null) { + throw new AssertionError("missing class"); + } + + MethodSymbol I_m = null; + + for (Symbol sym : i.members().getElements()) { + if (sym.name.contentEquals("m")) { + I_m = (MethodSymbol)sym; + } + } + + if (I_m == null) { + throw new AssertionError("missing method m() in scope of interface I"); + } + + Symbol impl = I_m.implementation((TypeSymbol)b, types, true); + + if (impl == null || impl.owner != a) { + throw new AssertionError("wrong implementation for m() in B"); + } + + b.members().enter(I_m.clone(b)); + + Symbol newImpl = I_m.implementation((TypeSymbol)b, types, true); + + if (newImpl == impl) { + throw new AssertionError("stale implementation for m() in B"); + } + + if (newImpl == null || newImpl.owner != b) { + throw new AssertionError("wrong implementation for m() in B"); + } + } +}