diff --git a/de.peeeq.wurstscript/parserspec/wurstscript.parseq b/de.peeeq.wurstscript/parserspec/wurstscript.parseq index 9e9e492b6..e2ab223b3 100644 --- a/de.peeeq.wurstscript/parserspec/wurstscript.parseq +++ b/de.peeeq.wurstscript/parserspec/wurstscript.parseq @@ -180,6 +180,7 @@ Expr = | ExprMember | ExprFunctionCall(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Identifier funcNameId, TypeExprList typeArgs, Arguments args) | ExprNewObject(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Identifier typeNameId, TypeExprList typeArgs, Arguments args) + | ExprTypeRef(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, TypeExpr typ) | ExprCast(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, TypeExpr typ, Expr expr) | ExprInstanceOf(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, TypeExpr typ, Expr expr) | ExprClosure(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, @ignoreForEquality de.peeeq.wurstscript.parser.WPos arrowSource, WShortParameters shortParameters, Expr implementation) diff --git a/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 b/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 index a2149d68b..4322fd435 100644 --- a/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 +++ b/de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4 @@ -350,7 +350,8 @@ indexes: expr: - exprPrimary + receiverType=genericTypeReceiver dotsTypeCall=('.'|'..') typeFuncName=ID? typeCallTypeArgs=typeArgs typeCallArgs=argumentList + | exprPrimary | left=expr 'castTo' castToType=typeExpr | left=expr 'instanceof' instaneofType=typeExpr | receiver=expr dotsCall=('.'|'..') funcName=ID? typeArgs argumentList @@ -443,6 +444,20 @@ stmtSkip:'skip'; typeArgs: ('<' (args+=typeExpr (',' args+=typeExpr)*)? '>')?; +typeArgsNonEmpty: '<' args+=typeExpr (',' args+=typeExpr)* '>'; + +genericTypeReceiver: + receiverTypePrefixes+=genericTypeReceiverPrefixPart* receiverTypeGenericPart=genericTypeReceiverGenericPart + ; + +genericTypeReceiverPrefixPart: + typeName=ID '.' + ; + +genericTypeReceiverGenericPart: + typeName=ID typeArgsNonEmpty + ; + exprList : exprs+=expr (',' exprs+=expr)*; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java index af453b3fd..7f5a8792a 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java @@ -688,6 +688,11 @@ public List> case_TypeExprSimple(TypeExprSimple t) return typeExpr(t); } + @Override + public List> case_ExprTypeRef(ExprTypeRef e) { + return typeExpr(e.getTyp()); + } + @Override public List> case_Modifiers(Modifiers modifiers) { return string("Modifiers for this declaration."); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java index 2ed203c66..ac3b08e64 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrExprType.java @@ -45,6 +45,10 @@ public static WurstType calculate(ExprFuncRef term) { return WurstTypeCode.instance(); } + public static WurstType calculate(ExprTypeRef term) { + return term.getTyp().attrTyp(); + } + public static WurstType calculate(ExprVarAccess term) { NameLink varDef = term.attrNameLink(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrPossibleFunctionSignatures.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrPossibleFunctionSignatures.java index 584dbcb8d..09813eb03 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrPossibleFunctionSignatures.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrPossibleFunctionSignatures.java @@ -37,7 +37,7 @@ public static ImmutableCollection calculate(FunctionCall fc) } } // TODO else check? - VariableBinding mapping = givenBinding(fc, sig.getDefinitionTypeVariables()); + VariableBinding mapping = sig.getMapping().union(givenBinding(fc, sig.getDefinitionTypeVariables())); sig = sig.setTypeArgs(fc, mapping); resultBuilder.add(sig); @@ -229,7 +229,7 @@ public static ImmutableCollection calculate(ExprMemberMethod } // Apply explicit type args from the call-site (e.g., c.foo(...)) - VariableBinding explicit = GenericsHelper.givenBinding(mm, sig.getDefinitionTypeVariables()); + VariableBinding explicit = sig.getMapping().union(GenericsHelper.givenBinding(mm, sig.getDefinitionTypeVariables())); sig = sig.setTypeArgs(mm, explicit); prepared.add(sig); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java index 66b1132a8..db1d39500 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java @@ -207,6 +207,10 @@ public static String description(ExprThis e) { return "this has type " + htmlType(e.attrTyp()); } + public static String description(ExprTypeRef e) { + return "Type reference " + htmlType(e.attrTyp()); + } + public static String description(ExprTypeId exprTypeId) { return "typeId: returns the typeId of an object or class. The typeId is " + "a unique number for each class in the same type hierarchy."; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java index 4125de5da..46c32add5 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ReadVariables.java @@ -112,6 +112,10 @@ public static ImmutableList calculate(ExprFuncRef exprFuncRef) { return ImmutableList.emptyList(); } + public static ImmutableList calculate(ExprTypeRef exprTypeRef) { + return ImmutableList.emptyList(); + } + public static ImmutableList calculate(ExprTypeId e) { return e.getLeft().attrReadVariables(); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java index b19f826e2..628f0de6f 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java @@ -487,6 +487,10 @@ public static void prettyPrint(ExprThis e, Spacer spacer, StringBuilder sb, int sb.append("this"); } + public static void prettyPrint(ExprTypeRef e, Spacer spacer, StringBuilder sb, int indent) { + e.getTyp().prettyPrint(spacer, sb, indent); + } + public static void prettyPrint(ExprTypeId e, Spacer spacer, StringBuilder sb, int indent) { e.getLeft().prettyPrint(spacer, sb, indent); sb.append(".typeId"); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ILInterpreter.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ILInterpreter.java index ba3eb07b0..b9072b91d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ILInterpreter.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ILInterpreter.java @@ -5,6 +5,8 @@ import de.peeeq.wurstio.jassinterpreter.VarargArray; import de.peeeq.wurstscript.WLogger; import de.peeeq.wurstscript.ast.Annotation; +import de.peeeq.wurstscript.ast.ClassOrInterface; +import de.peeeq.wurstscript.ast.FuncDef; import de.peeeq.wurstscript.ast.HasModifier; import de.peeeq.wurstscript.ast.Modifier; import de.peeeq.wurstscript.gui.WurstGui; @@ -172,6 +174,15 @@ public static LocalState runFunc(ProgramState globalState, ImFunction f, @Nullab subst.put(fvars.get(i2), targs.get(i2).getType()); } + ImClass sourceOwner = findSourceOwnerClass(f, globalState.getProg()); + if (sourceOwner != null) { + ImTypeVars cvars = sourceOwner.getTypeVariables(); + int n2 = Math.min(cvars.size(), targs.size()); + for (int i2 = 0; i2 < n2; i2++) { + subst.put(cvars.get(i2), targs.get(i2).getType()); + } + } + // 2) If the function is inside a class, also bind class type vars with the same call type args Element owner = f.getParent(); while (owner != null && !(owner instanceof ImClass)) { @@ -249,6 +260,26 @@ public static LocalState runFunc(ProgramState globalState, ImFunction f, @Nullab } } + private static @Nullable ImClass findSourceOwnerClass(ImFunction f, ImProg prog) { + if (!(f.getTrace() instanceof FuncDef)) { + return null; + } + FuncDef fd = (FuncDef) f.getTrace(); + if (!fd.attrIsStatic()) { + return null; + } + ClassOrInterface owner = fd.attrNearestClassOrInterface(); + if (owner == null) { + return null; + } + for (ImClass c : prog.getClasses()) { + if (c.getTrace() == owner) { + return c; + } + } + return null; + } + public static de.peeeq.wurstscript.ast.Element getTrace(ProgramState globalState, ImFunction f) { Element lastStatement = globalState.getLastStatement(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java index 9445df914..d01fda33a 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/antlr/AntlrWurstParseTreeTransformer.java @@ -1027,7 +1027,12 @@ private Arguments transformArgumentList(ArgumentListContext al) { private ExprMemberMethod transformMemberMethodCall2(WPos source, ExprContext receiver, Token dots, Token funcName, TypeArgsContext typeArgs, ArgumentListContext args) { - Expr left = transformExpr(receiver); + return transformMemberMethodCall2(source, transformExpr(receiver), dots, funcName, typeArgs, args); + } + + private ExprMemberMethod transformMemberMethodCall2(WPos source, + Expr left, Token dots, Token funcName, + TypeArgsContext typeArgs, ArgumentListContext args) { if (dots.getType() == WurstParser.DOT) { return Ast.ExprMemberMethodDot(source, left, text(funcName), transformTypeArgs(typeArgs), transformArgumentList(args)); @@ -1091,6 +1096,11 @@ private Expr transformExpr(ExprContext e) { } else if (e.castToType != null) { return Ast.ExprCast(source, transformTypeExpr(e.castToType), transformExpr(e.left)); + } else if (e.dotsTypeCall != null) { + TypeExpr receiverType = transformGenericTypeReceiver(e.receiverType); + Expr left = Ast.ExprTypeRef(receiverType.getSource(), receiverType); + return transformMemberMethodCall2(source, left, e.dotsTypeCall, + e.typeFuncName, e.typeCallTypeArgs, e.typeCallArgs); } else if (e.dotsVar != null) { return transformExprMemberVarAccess2(source, e.receiver, e.dotsVar, e.varName, e.indexes()); @@ -1120,6 +1130,23 @@ private Expr transformExpr(ExprContext e) { } + private TypeExpr transformGenericTypeReceiver(GenericTypeReceiverContext receiverType) { + OptTypeExpr scopeType = Ast.NoTypeExpr(); + TypeExpr result = null; + + for (GenericTypeReceiverPrefixPartContext part : receiverType.receiverTypePrefixes) { + result = Ast.TypeExprSimple(source(part.typeName), scopeType, + part.typeName.getText(), Ast.TypeExprList()); + scopeType = result; + } + + GenericTypeReceiverGenericPartContext genericPart = receiverType.receiverTypeGenericPart; + result = Ast.TypeExprSimple(source(genericPart.typeName), scopeType, + genericPart.typeName.getText(), transformTypeArgs(genericPart.typeArgsNonEmpty())); + + return result; + } + private int beginPos(ParseTree left) { if (left instanceof ParserRuleContext) { ParserRuleContext left2 = (ParserRuleContext) left; @@ -1379,6 +1406,14 @@ private TypeExprList transformTypeArgs(TypeArgsContext typeArgs) { return result; } + private TypeExprList transformTypeArgs(TypeArgsNonEmptyContext typeArgs) { + TypeExprList result = Ast.TypeExprList(); + for (TypeExprContext e : typeArgs.args) { + result.add(transformTypeExpr(e)); + } + return result; + } + private WParameters transformFormalParameters(FormalParametersContext ps, boolean makeConstant) { WParameters result = Ast.WParameters(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java index d0f9a25a5..066c4db93 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java @@ -86,6 +86,10 @@ public static ImExpr translate(ExprNewObject e, ImTranslator t, ImFunction f) { return wrapTranslation(e, t, translateIntern(e, t, f)); } + public static ImExpr translate(ExprTypeRef e, ImTranslator t, ImFunction f) { + throw new CompileError(e, "Type reference " + Utils.printTypeExpr(e.getTyp()) + " cannot be used as a value."); + } + public static ImExpr translate(ExprInstanceOf e, ImTranslator t, ImFunction f) { return wrapTranslation(e, t, translateIntern(e, t, f)); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java index 8641e645c..592a982f8 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java @@ -113,6 +113,36 @@ private static boolean hasTypeVarNamed(ImTypeVars vars, String name) { return false; } + private static String uniqueTypeVarName(ImTypeVars vars, TypeParamDef tp) { + String name = tp.getName(); + if (!hasTypeVarNamed(vars, name)) { + return name; + } + + String baseName = name + "$" + typeParamOwnerName(tp); + String candidate = baseName; + int suffix = 2; + while (hasTypeVarNamed(vars, candidate)) { + candidate = baseName + suffix; + suffix++; + } + return candidate; + } + + private static String typeParamOwnerName(TypeParamDef tp) { + de.peeeq.wurstscript.ast.Element e = tp.getParent(); + while (e != null) { + if (e instanceof FuncDef) { + return ((FuncDef) e).getName(); + } + if (e instanceof NamedScope) { + return ((NamedScope) e).getName(); + } + e = e.getParent(); + } + return "type"; + } + public Map getTypeVarOverridesForClass(ClassDef cd) { Map m = capturedOwnerTypeVarsByStaticClass.get(cd); return (m == null) ? Collections.emptyMap() : m; @@ -138,6 +168,17 @@ public void popContinueFlag() { return continueFlagStack.peek(); } + public Map getTypeVarOverridesForFunction(ImFunction f) { + Map result = new IdentityHashMap<>(); + for (ImTypeVar tv : f.getTypeVariables()) { + TypeParamDef tp = typeVariableReverse.get(tv); + if (tp != null) { + result.put(tp, tv); + } + } + return result; + } + public ImTranslator(WurstModel wurstProg, boolean isUnitTestMode, RunArgs runArgs) { this.wurstProg = wurstProg; @@ -930,7 +971,13 @@ public ImFunction getFuncFor(TranslatedToImFunction funcDef) { ImTypeVars typeVars = collectTypeVarsForFunction(funcDef); ImFunction f = ImFunction(funcDef, name, typeVars, ImVars(), ImVoid(), ImVars(), ImStmts(), flags); - funcDef.imCreateFuncSkeleton(this, f); + Map ov = getTypeVarOverridesForFunction(f); + pushTypeVarOverrides(ov); + try { + funcDef.imCreateFuncSkeleton(this, f); + } finally { + popTypeVarOverrides(ov); + } addFunction(f, funcDef); functionMap.put(funcDef, f); @@ -992,6 +1039,12 @@ private ImTypeVars collectTypeVarsForFunction(TranslatedToImFunction funcDef) { funcDef.match(new TranslatedToImFunction.MatcherVoid() { @Override public void case_FuncDef(FuncDef funcDef) { + if (funcDef.attrIsStatic()) { + ClassOrInterface owner = funcDef.attrNearestClassOrInterface(); + if (owner != null) { + handleTypeParameters(owner.getTypeParameters()); + } + } handleTypeParameters(funcDef.getTypeParameters()); } @@ -1004,7 +1057,9 @@ private void handleTypeParameters(TypeParamDefs tps) { private void handleTypeParameter(TypeParamDef tp) { if (tp.getTypeParamConstraints() instanceof TypeExprList) { - typeVars.add(typeVariable.getFor(tp)); + ImTypeVar v = JassIm.ImTypeVar(uniqueTypeVarName(typeVars, tp)); + typeVariableReverse.put(v, tp); + typeVars.add(v); } } @@ -1172,7 +1227,14 @@ public int getTupleIndex(TupleDef tupleDef, VarDef parameter) { public ImVar getVarFor(VarDef varDef) { ImVar v = varMap.get(varDef); if (v == null) { - ImType type = varDef.attrTyp().imTranslateType(this); + Map ov = getOwnerTypeVarOverridesForStaticClassVar(varDef); + pushTypeVarOverrides(ov); + ImType type; + try { + type = varDef.attrTyp().imTranslateType(this); + } finally { + popTypeVarOverrides(ov); + } String name = varDef.getName(); if (isNamedScopeVar(varDef)) { name = getNameFor(varDef.attrNearestNamedScope()) + "_" + name; @@ -1184,6 +1246,25 @@ public ImVar getVarFor(VarDef varDef) { return v; } + private Map getOwnerTypeVarOverridesForStaticClassVar(VarDef varDef) { + if (!(varDef instanceof GlobalVarDef) || !varDef.attrIsStatic()) { + return Collections.emptyMap(); + } + ClassOrInterface owner = varDef.attrNearestClassOrInterface(); + if (owner == null) { + return Collections.emptyMap(); + } + Map result = new IdentityHashMap<>(); + if (owner instanceof AstElementWithTypeParameters) { + for (TypeParamDef tp : ((AstElementWithTypeParameters) owner).getTypeParameters()) { + if (tp.getTypeParamConstraints() instanceof TypeExprList) { + result.put(tp, typeVariable.getFor(tp)); + } + } + } + return result; + } + private boolean isNamedScopeVar(VarDef varDef) { if (varDef.getParent() == null) { return false; @@ -1217,10 +1298,16 @@ public void setTranslated(ClassDef c) { public List translateStatements(ImFunction f, List statements) { List result = Lists.newArrayList(); - for (WStatement s : statements) { - lasttranslatedThing = s; - ImStmt translated = s.imTranslateStmt(this, f); - result.add(translated); + Map ov = getTypeVarOverridesForFunction(f); + pushTypeVarOverrides(ov); + try { + for (WStatement s : statements) { + lasttranslatedThing = s; + ImStmt translated = s.imTranslateStmt(this, f); + result.add(translated); + } + } finally { + popTypeVarOverrides(ov); } return result; } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java index 7dee9d9c0..e722118c5 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java @@ -1412,6 +1412,76 @@ public void genericStaticArray_instanceMethods_runtime() { ); } + @Test + public void genericStaticArray_staticMethods_specializedTypeReceiver_runtime() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class Box", + " private static T array store", + " static function put(int i, T v)", + " store[i] = v", + " static function get(int i) returns T", + " return store[i]", + " static function getFirst() returns T", + " return get(1)", + " static function same(T v) returns T", + " return v", + " init", + " Box.put(1, 42)", + " Box.put(2, 7)", + " Box.put(1, 1.5)", + " Box.put(2, 2.5)", + " if Box.get(1) == 42 and Box.get(2) == 7 and Box.getFirst() == 42 and Box.same(13) == 13 and Box.get(1) == 1.5 and Box.get(2) == 2.5 and Box.getFirst() == 1.5 and Box.same(3.5) == 3.5", + " testSuccess()", + "endpackage" + ); + } + + @Test + public void genericStaticArray_staticMethods_qualifiedSpecializedTypeReceiver_runtime() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class Outer", + " static class Box", + " private static T array store", + " static function put(int i, T v)", + " store[i] = v", + " static function get(int i) returns T", + " return store[i]", + " static function same(T v) returns T", + " return v", + " init", + " Outer.Box.put(1, 42)", + " Outer.Box.put(1, 2.5)", + " if Outer.Box.get(1) == 42 and Outer.Box.same(13) == 13 and Outer.Box.get(1) == 2.5 and Outer.Box.same(3.5) == 3.5", + " testSuccess()", + "endpackage" + ); + } + + @Test + public void genericStaticArray_staticMethodShadowedTypeParam_runtime() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class Box", + " private static T array store", + " static function put(int i, T v)", + " store[i] = v", + " static function get(int i) returns T", + " return store[i]", + " static function same(T v) returns T", + " return v", + " init", + " Box.put(1, 42)", + " if Box.get(1) == 42 and Box.same(1.5) == 1.5", + " testSuccess()", + "endpackage" + ); + } + @Test public void genericStaticTuple_runtime() { testAssertOkLines(true,