/* ____ ____ ____ ____ ______ *\ ** / __// __ \/ __// __ \/ ____/ SOcos COmpiles Scala ** ** __\_ \/ /_/ / /__/ /_/ /\_ \ (c) 2002, LAMP/EPFL ** ** /_____/\____/\___/\____/____/ ** \* */ // $Id$ // TODO try to use setInfo instead of updateInfo for cloned symbols, // to avoid the need to use nextPhase/nextInfo. package scalac.transformer; import scalac.*; import scalac.symtab.*; import scalac.checkers.*; import scalac.util.Name; import java.util.*; import scalac.util.Debug; public class AddInterfacesPhase extends PhaseDescriptor { public String name () { return "addinterfaces"; } public String description () { return "add one interface per class"; } public String taskDescription() { return "added interfaces"; } public void apply(Global global) { new AddInterfaces(global, this).apply(); } public void apply(Unit unit) { new AddInterfaces(unit.global, this).apply(unit); } public Type transformInfo(Symbol sym, Type tp) { if (sym.isPrimaryConstructor()) { Symbol clazz = sym.primaryConstructorClass(); if (!(clazz.isClass() && needInterface(clazz))) return tp; // The symbol is a constructor of a class which needs // an interface. All its value arguments have to be // removed. return removeValueParams(tp); } else if (sym.isClass() && !sym.isJava()) { Definitions definitions = Global.instance.definitions; Type[] oldParents = tp.parents(); assert oldParents.length > 0 : Debug.show(sym); for (int i = 1; i < oldParents.length; ++i) { Symbol oldSym = oldParents[i].symbol(); assert !oldSym.isJava() || oldSym.isInterface() : Debug.show(sym) + " <: " + Debug.show(oldSym); } Type[] newParents; Scope newMembers; if (needInterface(sym)) { // Before this phase, the symbol is a class, but after // it will be an interface. Its type has to be changed // so that: // // 1. Java classes are removed from its parents, // // 2. only members which will end up in the // interface are kept, and private ones are made // public and renamed. sym.flags |= Modifiers.INTERFACE; Scope.SymbolIterator oldMembersIt = new Scope.UnloadIterator(tp.members().iterator()); newMembers = new Scope(); while (oldMembersIt.hasNext()) { Symbol member = oldMembersIt.next(); if (!memberGoesInInterface(member)) continue; if (member.isPrivate()) { member.name = uniqueName(member); member.flags ^= Modifiers.PRIVATE; } else if (member.isProtected()) member.flags ^= Modifiers.PROTECTED; newMembers.enterOrOverload(member); } Symbol oldSym = oldParents[0].symbol(); if (oldSym.isJava() && !oldSym.isInterface() && oldSym != definitions.ANY_CLASS && oldSym != definitions.ANYREF_CLASS) { newParents = new Type[oldParents.length]; newParents[0] = definitions.ANYREF_TYPE; for (int i = 1; i < oldParents.length; ++i) newParents[i] = oldParents[i]; } else { newParents = oldParents; } } else { // The symbol is the one of a class which doesn't need // an interface. We need to fix its parents to use // class symbols instead of interface symbols. newMembers = tp.members(); newParents = new Type[oldParents.length]; for (int i = 0; i < oldParents.length; ++i) { switch (oldParents[i]) { case TypeRef(Type pre, Symbol oldSym, Type[] args): newParents[i] = !needInterface(oldSym) ? oldParents[i]: Type.typeRef(pre, getClassSymbol(oldSym), args); break; default: throw Debug.abort("illegal case", oldParents[i]); } } } return Type.compoundType(newParents, newMembers, sym); } else return tp; } public Checker[] postCheckers(Global global) { return new Checker[] { new CheckSymbols(global), new CheckTypes(global), new CheckOwners(global), new CheckNames(global) }; } protected boolean memberGoesInInterface(Symbol member) { return member.isType() || (member.isMethod() && !member.isPrimaryConstructor()); } protected Type removeValueParams(Type tp) { switch (tp) { case MethodType(Symbol[] vparams, Type result): return new Type.MethodType(Symbol.EMPTY_ARRAY, result); case PolyType(Symbol[] tps, Type result): return new Type.PolyType(tps, removeValueParams(result)); default: return tp; } } protected void uniqueName(Symbol sym, StringBuffer buf) { Symbol owner = sym.owner(); if (owner != Symbol.NONE) { uniqueName(owner, buf); buf.append('$'); } buf.append(sym.name.toString()); } protected Name uniqueName(Symbol sym) { StringBuffer buf = new StringBuffer(); uniqueName(sym, buf); Name newName = Name.fromString(buf.toString()); if (sym.name.isTypeName()) return newName.toTypeName(); else return newName; } // Terminology: in the following code, the symbol which was used // until this phase for the class, and will now be used for the // interface is called the "interface symbol". The symbol created // by this phase for the class is called the "class symbol". /** True iff the given class symbol needs an interface. */ protected boolean needInterface(Symbol classSym) { assert classSym.isClass() : Debug.toString(classSym) + " is not a class (kind " + classSym.kind + ")"; return !(classSym.isJava() || classSym.isModuleClass() || classSym.isAnonymousClass() || hasInterfaceSymbol(classSym) || classSym == Global.instance.definitions.ANY_CLASS || classSym == Global.instance.definitions.ANYREF_CLASS || classSym == Global.instance.definitions.ALL_CLASS || classSym == Global.instance.definitions.ALLREF_CLASS); } protected final static String CLASS_SUFFIX = "$class"; protected Name className(Name ifaceName) { Name className = Name.fromString(ifaceName.toString() + CLASS_SUFFIX); if (ifaceName.isTypeName()) return className.toTypeName(); else return className; } protected HashMap ifaceToClass = new HashMap(); protected HashMap classToIFace = new HashMap(); /** Return the class symbol corresponding to the given interface * symbol. If the class does not need an interface, return the * given symbol. */ protected Symbol getClassSymbol(Symbol ifaceSym) { if (ifaceSym.isPrimaryConstructor()) return getClassSymbol(ifaceSym.primaryConstructorClass()) .primaryConstructor(); if (!needInterface(ifaceSym)) return ifaceSym; Symbol classSym = (Symbol)ifaceToClass.get(ifaceSym); if (classSym == null) { classSym = ifaceSym.cloneSymbol(ifaceSym.owner()); classSym.name = className(ifaceSym.name); classSym.flags &= ~Modifiers.INTERFACE; Symbol ifaceConstrSym = ifaceSym.primaryConstructor(); Symbol classConstrSym = classSym.primaryConstructor(); classConstrSym.name = className(ifaceConstrSym.name); Scope ifaceOwnerMembers = ifaceSym.owner().members(); ifaceOwnerMembers.enter(classSym); // ifaceOwnerMembers.enter(classConstrSym); Type.SubstThisMap thisTypeMap = new Type.SubstThisMap(ifaceSym, classSym); // Create class substitution map. SymbolSubstTypeMap classSubst = newClassSubst(classSym); classSubst.insertSymbol( ifaceConstrSym.typeParams(), classConstrSym.typeParams()); classSubst.insertSymbol( ifaceConstrSym.valueParams(), classConstrSym.valueParams()); // Clone all members, entering them in the class scope. Map classMembersMap = newClassMemberMap(classSym); Scope classMembers = new Scope(); Scope.SymbolIterator ifaceMembersIt = new Scope.UnloadIterator(ifaceSym.members().iterator()); while (ifaceMembersIt.hasNext()) { Symbol ifaceMemberSym = ifaceMembersIt.next(); if (ifaceMemberSym.isType() || ifaceMemberSym.isDeferred() || ifaceMemberSym.isPrimaryConstructor()) continue; Symbol classMemberSym; if (memberGoesInInterface(ifaceMemberSym)) { if (ifaceMemberSym.isPrivate()) { ifaceMemberSym.name = uniqueName(ifaceMemberSym); ifaceMemberSym.flags ^= Modifiers.PRIVATE; } else if (ifaceMemberSym.isProtected()) ifaceMemberSym.flags ^= Modifiers.PROTECTED; classMemberSym = ifaceMemberSym.cloneSymbol(classSym); classMemberSym.setInfo( thisTypeMap.applyParams( classSubst.applyParams( classMemberSym.info().cloneType( ifaceMemberSym, classMemberSym)))); ifaceMemberSym.flags |= Modifiers.DEFERRED; } else { // Member doesn't go in interface, we just make it // owned by the class. classMemberSym = ifaceMemberSym; classMemberSym.setOwner(classSym); classMemberSym.updateInfo( thisTypeMap.apply( classSubst.apply( classMemberSym.info()))); } classMembersMap.put(ifaceMemberSym, classMemberSym); classMembers.enterOrOverload(classMemberSym); if (classMemberSym.isClass()) classMembers.enterOrOverload(classMemberSym.primaryConstructor()); } // Give correct type to the class symbol by using class // symbols for its parents, and by adding the interface // among them. Type[] oldClassParents = classSym.parents(); int oldParentsCount = oldClassParents.length; Type[] newClassParents = new Type[oldParentsCount + 1]; for (int i = 0; i < oldParentsCount; ++i) { switch (oldClassParents[i]) { case TypeRef(Type pre, Symbol sym, Type[] args): Type newTp = Type.typeRef(pre, getClassSymbol(sym), args); newClassParents[i] = classSubst.apply(newTp); break; default: throw Debug.abort("unexpected type for parent", oldClassParents[i]); } } newClassParents[oldParentsCount] = classSubst.apply(ifaceSym.type()); // TODO setInfo cannot be used here because the type then // goes through transformInfo. Maybe setInfo should behave // like updateInfo. classSym.updateInfo(Type.compoundType(newClassParents, classMembers, classSym)); ifaceToClass.put(ifaceSym, classSym); classToIFace.put(classSym, ifaceSym); } return classSym; } public boolean hasInterfaceSymbol(Symbol classSym) { return classToIFace.containsKey(classSym); } public Symbol getInterfaceSymbol(Symbol classSym) { return (Symbol)classToIFace.get(classSym); } HashMap/**/ classSubstitutions = new HashMap(); protected SymbolSubstTypeMap newClassSubst(Symbol classSym) { SymbolSubstTypeMap subst = new SymbolSubstTypeMap(); classSubstitutions.put(classSym, subst); return subst; } /** Return symbol substitution for the class (a mapping from the * interface's type and value parameters to the class' equivalent) */ public SymbolSubstTypeMap getClassSubst(Symbol classSym) { SymbolSubstTypeMap classSubst = (SymbolSubstTypeMap)classSubstitutions.get(classSym); assert classSubst != null; return classSubst; } HashMap/**/ classMemberMaps = new HashMap(); protected HashMap newClassMemberMap(Symbol classSym) { HashMap map = new HashMap(); classMemberMaps.put(classSym, map); return map; } /** Return symbol substitution for the class (a mapping from the * interface's type and value parameters to the class' equivalent) */ public Map getClassMemberMap(Symbol classSym) { return (Map)classMemberMaps.get(classSym); } }