From a481860c64304d93b1627a5e089fa490c7b11fc4 Mon Sep 17 00:00:00 2001 From: mihaylov Date: Thu, 7 Jul 2005 16:22:23 +0000 Subject: Added delegate support Credits: Most of the work was done by Martin Rubli as a semester project --- .../scala/tools/scalac/typechecker/Analyzer.scala | 27 +++++ sources/scala/tools/scalac/typechecker/Infer.scala | 11 +- sources/scalac/backend/msil/GenMSIL.java | 73 ++++++++++- sources/scalac/backend/msil/TypeCreator.java | 80 ++++++++++++ sources/scalac/symtab/Definitions.java | 8 ++ .../scalac/symtab/classfile/CLRClassParser.java | 134 +++++++++++++++------ sources/scalac/symtab/classfile/CLRTypes.java | 17 +++ sources/scalac/util/Names.java | 2 + 8 files changed, 313 insertions(+), 39 deletions(-) diff --git a/sources/scala/tools/scalac/typechecker/Analyzer.scala b/sources/scala/tools/scalac/typechecker/Analyzer.scala index 54236a4a18..790febf143 100644 --- a/sources/scala/tools/scalac/typechecker/Analyzer.scala +++ b/sources/scala/tools/scalac/typechecker/Analyzer.scala @@ -616,6 +616,20 @@ class Analyzer(global: scalac_Global, descr: AnalyzerPhase) extends Transformer( sym.getType().isInstanceOf[Type$PolyType] && sym.typeParams().length == 0; + /** + * Returns true if the adaption 'from => to' corresponds to a delegate + * forward view. + * + * TODO: The check should probably be deeper than just for a function type + * because the user might introduce a view from a delegate type to a + * function type himself, which is okay as long as it doesn't collide with + * the forward view. + */ + private def isDelegateForwardView(from: Type, to: Type): boolean = + definitions.DELEGATE_TYPE() != null && + from.isSubType(definitions.DELEGATE_TYPE()) && + to.isFunctionType(); + // Views ----------------------------------------------------------------------- private def applyView(v: View, tree: Tree, mode: int, pt: Type): Tree = { @@ -1646,6 +1660,13 @@ class Analyzer(global: scalac_Global, descr: AnalyzerPhase) extends Transformer( case _ => } val v = infer.bestView(tree.getType(), pt, Names.EMPTY); + // Convert views of delegate types to closures wrapped around + // the expression's apply method. + if(global.target == scalac_Global.TARGET_MSIL && + v != null && isDelegateForwardView(tree.getType(), pt)) { + val meth: Symbol = tree.symbol().lookup(Names.apply); + return adapt(gen.Select(tree, meth), mode, pt); + } if (v != null) return applyView(v, tree, mode, pt); // todo: remove val coerceMeth: Symbol = tree.getType().lookup(Names.coerce); @@ -1814,6 +1835,12 @@ class Analyzer(global: scalac_Global, descr: AnalyzerPhase) extends Transformer( } } } + // MSIL: Forbid chaining of non-variable delegate objects using += and -= + if(global.target == scalac_Global.TARGET_MSIL && + qual.getType().isSubType(definitions.DELEGATE_TYPE()) && + (sym.name == Names.PLUSEQ || sym.name == Names.MINUSEQ) && + !qual.symbol().isVariable()) + error(tree.pos, "illegal modification of non-variable delegate"); val qualtype = if (qual.isInstanceOf[Tree.Super]) context.enclClass.owner.thisType() else qual.getType(); diff --git a/sources/scala/tools/scalac/typechecker/Infer.scala b/sources/scala/tools/scalac/typechecker/Infer.scala index 0890e3ed52..69b74f8adc 100644 --- a/sources/scala/tools/scalac/typechecker/Infer.scala +++ b/sources/scala/tools/scalac/typechecker/Infer.scala @@ -1395,7 +1395,16 @@ class Infer(global: scalac_Global, gen: TreeGen, make: TreeFactory) extends scal if (best != null) { viewMeths = availableViews(tp); while (!viewMeths.isEmpty) { - if (viewMeths.head != best && + // Ugly hack necessary for MSIL delegate support + // Used to be just: + // if (viewMeths.head != best && + // isApplicable(viewMeths.head.symtype, argtypes, pt, name, false) && + // !(specializesView(best.symtype, viewMeths.head.symtype) && + // !specializesView(viewMeths.head.symtype, best.symtype))) { + if ((if (global.target == scalac_Global.TARGET_MSIL) + viewMeths.head.sym != best.sym + else + viewMeths.head != best) && isApplicable(viewMeths.head.symtype, argtypes, pt, name, false) && !(specializesView(best.symtype, viewMeths.head.symtype) && !specializesView(viewMeths.head.symtype, best.symtype))) { diff --git a/sources/scalac/backend/msil/GenMSIL.java b/sources/scalac/backend/msil/GenMSIL.java index 8aab417000..73535cd3fb 100644 --- a/sources/scalac/backend/msil/GenMSIL.java +++ b/sources/scalac/backend/msil/GenMSIL.java @@ -850,10 +850,50 @@ public final class GenMSIL { genLoad(args[0], convTo); return items.StackItem(convTo); } + // Generate delegate's reverse view + if (sym.name == Names.view && + CLRTypes.instance().isDelegateType(resType.toType())) + { + assert args.length == 1 : Debug.show(sym); + return createDelegateFromFunction(args[0], resType); + } return check(invokeMethod(sym, args, resType, true)); case Select(Tree qualifier, _): + // Treat delegate chaining methods += and -= in a special way + if(CLRTypes.instance().isDelegateType(tc.getType(qualifier.type())) && + (sym.name == Names.PLUSEQ || sym.name == Names.MINUSEQ)) { + Type delegate = tc.getType(qualifier.type()); + MSILType delegateType = msilType(delegate); + MethodBase chainer = tc.getMethod(sym); + + switch(qualifier) { + case Apply(Tree f, _): + Symbol setterSym = f.symbol().owner().lookup( + Name.fromString(f.symbol().name.toString() + + Names._EQ)); + assert !setterSym.isNone() : "Setter method not found"; + MethodInfo setter = (MethodInfo)tc.getMethod(setterSym); + + // Generate object and argument for the setter call + emitThis(); // FIXME: doesn't work if the variable is + // is a member of another class + genLoad(qualifier, delegateType); + invokeMethod(sym, args, delegateType, false); + code.Emit(OpCodes.Castclass, delegate); + code.Emit(OpCodes.Callvirt, setter); + return items.VoidItem(); + + case Ident(_): + // Prepare the left-hand side for the store + Item var = gen(qualifier, delegateType); + load(var); + invokeMethod(sym, args, delegateType, false); + code.Emit(OpCodes.Castclass, delegate); + return check(store(var)); + } + } // scala.Any.== if (sym == defs.ANY_EQEQ) { return genEq(qualifier, args[0]); @@ -1328,12 +1368,39 @@ public final class GenMSIL { (MethodInfo)method); res = returnsVoid(method) ? items.VoidItem() : items.StackItem(resType); } - if (returnsVoid(fun) && !returnsVoid(method)) { - res = drop(res); - } + CLRTypes clrt = CLRTypes.instance(); + if (returnsVoid(fun) && !returnsVoid(method) + && method != clrt.DELEGATE_COMBINE + && method != clrt.DELEGATE_REMOVE) + { + res = drop(res); + } return res; } + private Item createDelegateFromFunction(Tree fun, MSILType type) { + Item ifun = genLoad(fun, msilType(fun.type)); + // Get the apply function of the delegate object + Symbol funSym = tc.getSymbol(ifun.type.toType()); + Symbol applySym = funSym.lookup(Names.apply); + MethodInfo apply = (MethodInfo) tc.getDelegateApplyMethod(applySym); + + // Get the (object, native int) constructor of the delegate type + Type delegateType = type.toType(); + ConstructorInfo delegCtor = delegateType.GetConstructor( + new Type[]{tc.OBJECT, Type.GetType("System.IntPtr")}); + + // Duplicate the object on the stack; we need its apply method + // and Ldvirtftn uses one element. + code.Emit(OpCodes.Dup); + code.Emit(OpCodes.Ldvirtftn, apply); + + // Create a new delegate; this uses up the element duplicated above. + code.Emit(OpCodes.Newobj, delegCtor); + + return items.StackItem(type); + } + /* * Returns the MSILType that corresponds to the given scala.symtab.Type */ diff --git a/sources/scalac/backend/msil/TypeCreator.java b/sources/scalac/backend/msil/TypeCreator.java index f5f036b225..7efa5645b1 100644 --- a/sources/scalac/backend/msil/TypeCreator.java +++ b/sources/scalac/backend/msil/TypeCreator.java @@ -714,6 +714,10 @@ final class TypeCreator { } } + public Symbol getSymbol(Type type) { + return (Symbol) types2symbols.get(type); + } + public Type createType(Symbol clazz) { try { return createType0(clazz); } catch (Error e) { @@ -963,6 +967,82 @@ final class TypeCreator { return method; } + /** + * Returns all MethodBase objects corresponding to the symbol. + */ + private MethodBase[] getMethods(Symbol sym) { + MethodBase method = (MethodBase) symbols2methods.get(sym); + if (method != null) + return new MethodBase[]{method}; + MemberInfo m = ti.getMember(sym); + if (m != null && m instanceof MethodBase) { + method = (MethodBase) m; + } else { + // force the creation of the declaring type + Type owner = getType(sym.owner()); + assert owner != null : Debug.show(sym); + method = (MethodBase) symbols2methods.get(sym); + if (method != null) + return new MethodBase[]{method}; + switch (sym.info()) { + case MethodType(Symbol[] vparams, scalac.symtab.Type result): + Type[] params = new Type[vparams.length]; + Type resType = getType(result); + for (int i = 0; i < params.length; i++) + params[i] = getType(vparams[i]); + if (sym.isInitializer()) { + // The owner of a constructor is the outer class + // so get the result type of the constructor + method = owner.GetConstructor(params); + assert method != null : "cannot find " + owner + + "::.ctor" + methodSignature(params); + } else { + method = owner instanceof TypeBuilder + ? findMethod(sym.owner(), sym) + : owner.GetMethod + (getMethodName(sym.name, params), params, resType); + } + break; + case OverloadedType(Symbol[] alts, scalac.symtab.Type[] alttypes): + MethodBase[] methods = new MethodBase[alts.length]; + for (int i = 0; i < alts.length; i++) + methods[i] = getMethod(alts[i]); + return methods; + + default: + throw Debug.abort("Symbol doesn't have a method type", sym); + } + assert method != null + : Debug.show(owner) + " => Cannot find method: " + methodSignature(sym); + } + symbols2methods.put(sym, method); + return new MethodBase[]{method}; + } + + /** + * Returns the exactest apply method. + * The exactest method is taken to be the first method that doesn't have + * an 'object' return type and all 'object' parameter types. + */ + public MethodBase getDelegateApplyMethod(Symbol sym) { + MethodBase methods[] = getMethods(sym); + if(methods.length > 1) { + assert methods.length == 2; + for (int i = 0; i < methods.length; i++) { + if(((MethodInfo)methods[i]).ReturnType != OBJECT) + return methods[i]; + ParameterInfo pi[] = methods[i].GetParameters(); + for (int j = 0; j < pi.length; j++) { + if(pi[j].ParameterType != OBJECT) + return methods[i]; + } + } + throw Debug.abort("Expected apply method not found", sym); + } else { + return methods[0]; + } + } + private String getMethodName(Name name, Type[] params) { if (name == Names.finalize && params.length == 0) return "Finalize"; diff --git a/sources/scalac/symtab/Definitions.java b/sources/scalac/symtab/Definitions.java index 70386c9d1a..bf2fa6fc34 100644 --- a/sources/scalac/symtab/Definitions.java +++ b/sources/scalac/symtab/Definitions.java @@ -742,6 +742,12 @@ public class Definitions { return JAVAREFARRAYTYPE_JAVAREFARRAYTYPE; } + // MSIL delegate types + public final Symbol DELEGATE_CLASS; + public final Type DELEGATE_TYPE() { + return DELEGATE_CLASS != null ? DELEGATE_CLASS.staticType() : null; + } + //######################################################################## // Public Fields - Global values @@ -794,6 +800,8 @@ public class Definitions { STRING_CLASS = getClass(forMSIL ? "System.String" : "java.lang.String"); THROWABLE_CLASS = getClass(forMSIL ? "System.Exception" : "java.lang.Throwable"); + // .NET delegate class + DELEGATE_CLASS = forMSIL ? getClass("System.MulticastDelegate") : null; // the scala value classes UNIT_CLASS = getClass("scala.Unit"); diff --git a/sources/scalac/symtab/classfile/CLRClassParser.java b/sources/scalac/symtab/classfile/CLRClassParser.java index 92b9409492..498237edbe 100644 --- a/sources/scalac/symtab/classfile/CLRClassParser.java +++ b/sources/scalac/symtab/classfile/CLRClassParser.java @@ -133,7 +133,6 @@ public class CLRClassParser extends SymbolLoader { continue; assert props[i].PropertyType == getter.ReturnType; Name n; - Symbol method; scalac.symtab.Type mtype; ParameterInfo[] gparams = getter.GetParameters(); @@ -145,13 +144,8 @@ public class CLRClassParser extends SymbolLoader { n = Names.apply; mtype = methodType(getter, getter.ReturnType); } - Symbol owner = getter.IsStatic() ? staticsClass : clazz; int mods = translateAttributes(getter); - method = owner.newMethod(Position.NOPOS, mods, n); - setParamOwners(mtype, method); - method.setInfo(mtype); - (getter.IsStatic() ? statics : members).enterOrOverload(method); - clrTypes.map(method, getter); + createMethod(n, mods, mtype, getter, getter.IsStatic()); assert methodsSet.contains(getter) : "" + getter; methodsSet.remove(getter); @@ -168,12 +162,8 @@ public class CLRClassParser extends SymbolLoader { else n = Names.update; mods = translateAttributes(setter); - method = owner.newMethod(Position.NOPOS, mods, n); mtype = methodType(setter, global.definitions.UNIT_TYPE()); - setParamOwners(mtype, method); - method.setInfo(mtype); - (setter.IsStatic() ? statics : members).enterOrOverload(method); - clrTypes.map(method, setter); + createMethod(n, mods, mtype, setter, setter.IsStatic()); assert methodsSet.contains(setter) : "" + setter; methodsSet.remove(setter); } @@ -186,6 +176,12 @@ public class CLRClassParser extends SymbolLoader { createMethod(method); } + // Create symbols related to delegate types + if(clrTypes.isDelegateType(type)) { + createDelegateView(); + createDelegateChainers(); + } + // for enumerations introduce comparison and bitwise logical operations; // the backend should recognize and replace them with comparison or // bitwise logical operations on the primitive underlying type @@ -197,20 +193,13 @@ public class CLRClassParser extends SymbolLoader { make.methodType(argTypes, global.definitions.boolean_TYPE(), scalac.symtab.Type.EMPTY_ARRAY); - Symbol enumCmp = clazz.newMethod - (Position.NOPOS, mods, ENUM_CMP_NAMES[i]); - setParamOwners(enumCmpType, enumCmp); - enumCmp.setInfo(enumCmpType); - members.enterOrOverload(enumCmp); + createMethod(ENUM_CMP_NAMES[i], mods, enumCmpType, null, false); } for (int i = 0; i < ENUM_BIT_LOG_NAMES.length; i++) { scalac.symtab.Type enumBitLogType = make.methodType (argTypes, clazzType, scalac.symtab.Type.EMPTY_ARRAY); - Symbol enumBitLog = clazz.newMethod - (Position.NOPOS, mods, ENUM_BIT_LOG_NAMES[i]); - setParamOwners(enumBitLogType, enumBitLog); - enumBitLog.setInfo(enumBitLogType); - members.enterOrOverload(enumBitLog); + createMethod + (ENUM_BIT_LOG_NAMES[i], mods, enumBitLogType, null, false); } } @@ -239,20 +228,17 @@ public class CLRClassParser extends SymbolLoader { ICustomAttributeProvider member, scalac.symtab.Type defaultType) { - scalac.symtab.Type symbolType = null; - if (!member.IsDefined(clrTypes.PICO_META_ATTR, false)) { - symbolType = defaultType; - } else { + if (member !=null && member.IsDefined(clrTypes.PICO_META_ATTR, false)) { Object[] attrs = member.GetCustomAttributes(clrTypes.PICO_META_ATTR, false); assert attrs.length == 1 : "attrs.length = " + attrs.length; String meta = (String)((Attribute)attrs[0]).getConstructorArguments()[0]; - symbolType = new MetaParser + defaultType = new MetaParser (meta, tvars, sym, defaultType, clazz, clazzType, make).parse(); } - sym.setInfo(symbolType); - return symbolType; + sym.setInfo(defaultType); + return defaultType; } private void createConstructor(ConstructorInfo constr) { @@ -278,13 +264,85 @@ public class CLRClassParser extends SymbolLoader { if (mtype == null) return; int mods = translateAttributes(method); - Symbol owner = method.IsStatic() ? staticsClass : clazz; - Symbol methodSym = - owner.newMethod(Position.NOPOS, mods, getName(method)); + createMethod(getName(method), mods, mtype, method, method.IsStatic()); + } + + // Create static view methods within the delegate and the function type + // with the following signatures: + // def MyDelegate.view(MyDelegate): FunctionX[InvokeArgs..., InvokeRet]; + // def FunctionX.view(FunctionX[InvokeArgs..., InvokeRet]): MyDelegate; + private void createDelegateView() { + // Extract the parameter and return types of the Invoke method + MethodInfo invoke = (MethodInfo)type.GetMember("Invoke")[0]; + scalac.symtab.Type invokeRetType = getCLRType(invoke.ReturnType); + scalac.symtab.Type invokeParamTypes[] = + new scalac.symtab.Type[invoke.GetParameters().length]; + for(int j = 0; j < invoke.GetParameters().length; j++) + invokeParamTypes[j] = + getCLRType(invoke.GetParameters()[j].ParameterType); + scalac.symtab.Type funType = + global.definitions.FUNCTION_TYPE(invokeParamTypes, invokeRetType); + + // FORWARD MAPPING (Delegate => Function) + scalac.symtab.Type viewParamTypes[] = { getCLRType(type) }; + scalac.symtab.Type viewRetType = funType; + scalac.symtab.Type viewMethodType = make.methodType( + viewParamTypes, + viewRetType, + scalac.symtab.Type.EMPTY_ARRAY); + + createMethod(Names.view, Modifiers.JAVA, viewMethodType, null, true); + + // REVERSE MAPPING (Function => Delegate) + viewParamTypes = new scalac.symtab.Type[]{ funType }; + viewRetType = getCLRType(type); + viewMethodType = make.methodType( + viewParamTypes, + viewRetType, + scalac.symtab.Type.EMPTY_ARRAY); + + createMethod(Names.view, Modifiers.JAVA, viewMethodType, null, true); + } + + private void createDelegateChainers() { + int mods = Modifiers.JAVA | Modifiers.FINAL; + Type[] args = new Type[]{type}; + + createMethod(Names.PLUSEQ, mods, args, clrTypes.VOID, + clrTypes.DELEGATE_COMBINE, false); + createMethod(Names.MINUSEQ, mods, args, clrTypes.VOID, + clrTypes.DELEGATE_REMOVE, false); + createMethod + (Names.PLUS, mods, args, type, clrTypes.DELEGATE_COMBINE, false); + createMethod + (Names.MINUS, mods, args, type, clrTypes.DELEGATE_REMOVE, false); + } + + private Symbol createMethod(Name name, int mods, Type[] args, + Type retType, MethodInfo method, boolean statik) + { + return createMethod(name, mods, args, getCLSType(retType), method, statik); + } + private Symbol createMethod(Name name, int mods, Type[] args, + scalac.symtab.Type retType, + MethodInfo method, + boolean statik) + { + scalac.symtab.Type mtype = methodType(args, retType); + assert mtype != null : name; + return createMethod(name, mods, mtype, method, statik); + } + private Symbol createMethod(Name name, int mods, scalac.symtab.Type mtype, + MethodInfo method, boolean statik) + { + Symbol methodSym = (statik ? staticsClass: clazz) + .newMethod(Position.NOPOS, mods, name); setParamOwners(mtype, methodSym); - parseMeta(methodSym, method, mtype); - (method.IsStatic() ? statics : members).enterOrOverload(methodSym); - clrTypes.map(methodSym, method); + parseMeta(methodSym, method, mtype); // sets the type to mtype if no meta + (statik ? statics : members).enterOrOverload(methodSym); + if (method != null) + clrTypes.map(methodSym, method); + return methodSym; } private Name getName(MethodInfo method) { @@ -303,6 +361,12 @@ public class CLRClassParser extends SymbolLoader { // TODO: check if the type implements ICloneable? if (name.equals("Clone") && params.length == 0) return Names.clone; + // Pretend that delegates have a 'apply' method instead of the 'Invoke' + // method. This is harmless because the latter one can't be called + // directly anyway. + if (name.equals("Invoke") + && clrTypes.isDelegateType(method.DeclaringType)) + return Names.apply; return Name.fromString(name); } diff --git a/sources/scalac/symtab/classfile/CLRTypes.java b/sources/scalac/symtab/classfile/CLRTypes.java index ab1e8e54c4..d14ba470ed 100644 --- a/sources/scalac/symtab/classfile/CLRTypes.java +++ b/sources/scalac/symtab/classfile/CLRTypes.java @@ -65,6 +65,7 @@ public final class CLRTypes { public final Type BOOLEAN; public final Type VOID; public final Type ENUM; + public final Type DELEGATE; public final Type OBJECT; public final Type STRING; @@ -78,6 +79,9 @@ public final class CLRTypes { public final ConstructorInfo SYMTAB_CONSTR; public final ConstructorInfo SYMTAB_DEFAULT_CONSTR; + public final MethodInfo DELEGATE_COMBINE; + public final MethodInfo DELEGATE_REMOVE; + private final SymbolNameWriter snw = new SymbolNameWriter(); private Type[] types; @@ -109,6 +113,7 @@ public final class CLRTypes { BOOLEAN = getType("System.Boolean"); VOID = getType("System.Void"); ENUM = getType("System.Enum"); + DELEGATE = getType("System.MulticastDelegate"); OBJECT = getType("System.Object"); STRING = getType("System.String"); @@ -123,8 +128,15 @@ public final class CLRTypes { SYMTAB_DEFAULT_CONSTR = SCALA_SYMTAB_ATTR.GetConstructor(Type.EmptyTypes); + Type delegate = Type.GetType("System.Delegate"); + Type[] dargs = new Type[]{delegate, delegate}; + DELEGATE_COMBINE = delegate.GetMethod("Combine", dargs); + DELEGATE_REMOVE = delegate.GetMethod("Remove", dargs); + assert PICO_META_ATTR != null; assert SCALA_SYMTAB_ATTR != null; + assert DELEGATE_COMBINE != null; + assert DELEGATE_REMOVE != null; Type[] types = Type.EmptyTypes; Iterator as = assemblies.iterator(); @@ -183,6 +195,11 @@ public final class CLRTypes { return getType(elemType.FullName + "[]"); } + // Returns true if the given type is a delegate type. + public boolean isDelegateType(Type t) { + return t.BaseType() == DELEGATE; + } + //########################################################################## // assembly loading methods diff --git a/sources/scalac/util/Names.java b/sources/scalac/util/Names.java index 9bde7b9b5c..66a67f819c 100644 --- a/sources/scalac/util/Names.java +++ b/sources/scalac/util/Names.java @@ -118,6 +118,8 @@ public class Names { public static final Name AMPAMP = encode("&&"); public static final Name COLONCOLON = encode("::"); public static final Name PERCENT = encode("%"); + public static final Name PLUSEQ = encode("+="); + public static final Name MINUSEQ = encode("-="); public static final Name All = Name.fromString("All"); public static final Name AllRef = Name.fromString("AllRef"); -- cgit v1.2.3