When working on our GPCE Mint tutorial, Eddy and I realized that there is a problem when programmers use type variables inside brackets. A method like
1 2 3 4 | public separable <X> Code<X> fun(Code<X> c1, Code<X> c2) { return <| ( `(lfTest.eval(e,f).booleanCodeValue()) ? `c2 : `c1 ) |>; } |
causes Mint to generate code containing the free type variable X
. There is no way to get any type information from a type variable at runtime, e.g. by doing X.class
, so instead we decided to require the user to have a final instance of Class<X>
in scope somewhere.
This was a pragmatic solution that seems to work pretty well. The method above can be rewritten as
1 2 3 4 5 | public separable <X> Code<X> fun(Code<X> c1, Code<X> c2, final Class<X> xc) { return <| ( `(lfTest.eval(e,f).booleanCodeValue()) ? `c2 : `c1 ) |>; } |
To implement this, we had to find all variables of type Class<X>
that were in scope at the time a bracket was generated. This was surprisingly confusing to do with the javac environment and scope classes. In the end, I decided to adapt the Resolver.findVar
and Resolver.findField
methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | /** This method returns a list of all VarSymbols of type Class<X> in scope where X is a type variable. */ List<VarSymbol> getClassVarsInScope() { // System.out.println("env = "+env); List<VarSymbol> ret = List.nil(); Scope current = env.info.scope; // System.out.println("Scope: "+current); while(current != null) { // also go into the outer (enclosing) scopes // for(Symbol s: current.elems) { for (Scope.Entry e = current.elems; e != null; e = e.sibling) { ret = processPotentialClassVarInScope(e.sym, ret); } current = current.next; } // System.out.println("Asking for vars in "+env.enclClass.sym); ret = _getClassVarsInScope(env, ret); return ret.reverse(); } List<VarSymbol> processPotentialClassVarInScope(Symbol s, List<VarSymbol> ret) { // System.out.println("in scope: "+s+" : "+s.getClass().getSimpleName()); if (s instanceof VarSymbol) { VarSymbol vs = (VarSymbol)s; if ((vs.type.tsym == syms.classType.tsym) && ((vs.flags() & FINAL) != 0) && (vs.type.getTypeArguments().nonEmpty()) && (vs.type.getTypeArguments().head instanceof TypeVar)) { // vs is of type Class<X> where X is a type variable ret = ret.prepend(vs); } } return ret; } List<VarSymbol> _getClassVarsInScope(Env<AttrContext> env, List<VarSymbol> ret) { Env<AttrContext> env1 = env; boolean staticOnly = false; while (env1.outer != null) { if (rs.isStatic(env1)) staticOnly = true; Scope.Entry e = env1.info.scope.elems; while (e != null) { Symbol sym = e.sym; if (!(staticOnly && sym.kind == VAR && sym.owner.kind == TYP && (sym.flags() & STATIC) == 0)) { ret = processPotentialClassVarInScope(sym, ret); } ret = _getClassFieldsInScope(env1, env1.enclClass.sym.type, env1.enclClass.sym, ret); e = e.sibling; } ret = _getClassFieldsInScope(env1, env1.enclClass.sym.type, env1.enclClass.sym, ret); if ((env1.enclClass.sym.flags() & STATIC) != 0) staticOnly = true; env1 = env1.outer; } ret = _getClassFieldsInScope(env, syms.predefClass.type, syms.predefClass, ret); return ret; } List<VarSymbol> _getClassFieldsInScope(Env<AttrContext> env, Type site, TypeSymbol c, List<VarSymbol> ret) { while (c.type.tag == TYPEVAR) c = c.type.getUpperBound().tsym; Scope.Entry e = c.members().elems; while (e != null) { if (e.sym.kind == VAR && (e.sym.flags_field & SYNTHETIC) == 0) { if (rs.isAccessible(env, site, e.sym)) { ret = processPotentialClassVarInScope(e.sym, ret); } } e = e.sibling; } Type st = types.supertype(c.type); if (st != null && (st.tag == CLASS || st.tag == TYPEVAR)) { ret = _getClassFieldsInScope(env, site, st.tsym, ret); } for (Type it: types.interfaces(c.type)) { ret = _getClassFieldsInScope(env, site, it.tsym, ret); } return ret; } |
A new Mint release can be expected shortly.