Skip to content

Commit 6ed40bb

Browse files
committed
iterators
1 parent 307674d commit 6ed40bb

4 files changed

Lines changed: 171 additions & 91 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -524,18 +524,17 @@ public void visit(ImSet set) {
524524
}
525525
}
526526
}
527-
528527
@Override
529528
public void visit(ImVar v) {
530529
super.visit(v);
531-
// Skip globals - they're handled separately
532-
if (v.isGlobal()) {
533-
return;
534-
}
530+
531+
// Skip globals - they're handled elsewhere
532+
if (v.isGlobal()) return;
533+
534+
// Do NOT error on type variables here. The initializer/method calls may
535+
// still specialize this. We'll validate at the very end.
536+
// If it's generic-but-concrete, schedule specialization:
535537
if (isGenericType(v.getType())) {
536-
if (containsTypeVariable(v.getType())) {
537-
throw new CompileError(v, "Var should not have type variables.");
538-
}
539538
genericsUses.add(new GenericVar(v));
540539
}
541540
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/StmtTranslation.java

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -109,87 +109,141 @@ public static ImStmt translate(StmtForIn forIn, ImTranslator t, ImFunction f) {
109109
if (itrType instanceof WurstTypeVararg) {
110110
return case_StmtForVararg(forIn, t, f);
111111
}
112-
List<ImStmt> result = Lists.newArrayList();
112+
List<ImStmt> result = com.google.common.collect.Lists.newArrayList();
113113

114114
Optional<FuncLink> iteratorFuncOpt = forIn.attrIteratorFunc();
115115
Optional<FuncLink> nextFuncOpt = forIn.attrGetNextFunc();
116116
Optional<FuncLink> hasNextFuncOpt = forIn.attrHasNextFunc();
117+
117118
if (iteratorFuncOpt.isPresent() && nextFuncOpt.isPresent() && hasNextFuncOpt.isPresent()) {
118119
FuncLink iteratorFunc = iteratorFuncOpt.get();
119120
FuncLink nextFunc = nextFuncOpt.get();
120121
FuncLink hasNextFunc = hasNextFuncOpt.get();
121122

122-
// Type of loop Variable:
123-
WurstType loopVarType = forIn.getLoopVar().attrTyp();
123+
// Type of loop variable (element type S):
124+
WurstType elemType = forIn.getLoopVar().attrTyp();
124125

125-
// get the iterator function in the intermediate language
126+
// IM functions
126127
ImFunction iteratorFuncIm = t.getFuncFor(iteratorFunc.getDef());
127-
ImFunction nextFuncIm = t.getFuncFor(nextFunc.getDef());
128-
ImFunction hasNextFuncIm = t.getFuncFor(hasNextFunc.getDef());
128+
ImFunction nextFuncIm = t.getFuncFor(nextFunc.getDef());
129+
ImFunction hasNextFuncIm = t.getFuncFor(hasNextFunc.getDef());
129130

130-
// translate target:
131+
// Translate receiver (iteration target):
131132
ImExprs iterationTargetList;
132-
if (forIn.getIn().attrTyp().isStaticRef()) {
133+
if (itrType.isStaticRef()) {
133134
iterationTargetList = ImExprs();
134135
} else {
135-
ImExpr iterationTargetIm = forIn.getIn().imTranslateExpr(t, f);
136+
ImExpr iterationTargetIm = iterationTarget.imTranslateExpr(t, f);
136137
iterationTargetList = JassIm.ImExprs(iterationTargetIm);
137138
}
138139

139-
// call XX.iterator()
140-
ImFunctionCall iteratorCall = ImFunctionCall(forIn, iteratorFuncIm, ImTypeArguments(), iterationTargetList, false, CallType.NORMAL);
141-
// create IM-variable for iterator
142-
ImVar iteratorVar = JassIm.ImVar(forIn.getLoopVar(), iteratorCall.attrTyp(), "iterator", false);
140+
// --- CONCRETE type argument for Iterator<S> and its methods ---
141+
ImType elemImType = elemType.imTranslateType(t);
142+
ImTypeArguments iterTypeArgs = JassIm.ImTypeArguments(
143+
JassIm.ImTypeArgument(elemImType, java.util.Collections.emptyMap())
144+
);
145+
146+
// call XX.iterator()<S>()
147+
ImFunctionCall iteratorCall = ImFunctionCall(
148+
forIn, iteratorFuncIm, iterTypeArgs, iterationTargetList, false, CallType.NORMAL
149+
);
150+
151+
// Materialize a concrete IM class type for the iterator local (Iterator<S>)
152+
ImType iteratorImType;
153+
WurstType retWT = iteratorFunc.getReturnType().normalize();
154+
if (retWT instanceof de.peeeq.wurstscript.types.WurstTypeClass) {
155+
de.peeeq.wurstscript.types.WurstTypeClass rtc = (de.peeeq.wurstscript.types.WurstTypeClass) retWT;
156+
de.peeeq.wurstscript.ast.ClassDef rtClassDef = rtc.getClassDef();
157+
ImClass imIterClass = t.getClassFor(rtClassDef);
158+
iteratorImType = JassIm.ImClassType(imIterClass, iterTypeArgs.copy());
159+
} else {
160+
// fallback – should not happen for a well-formed iterator()
161+
iteratorImType = retWT.imTranslateType(t);
162+
}
143163

164+
// locals: iterator and loopVar
165+
ImVar iteratorVar = JassIm.ImVar(forIn.getLoopVar(), iteratorImType, "iterator", false);
144166
f.getLocals().add(iteratorVar);
145167
f.getLocals().add(t.getVarFor(forIn.getLoopVar()));
146-
// create code for initializing iterator:
147168

148-
ImSet setIterator = ImSet(forIn, ImVarAccess(iteratorVar), iteratorCall);
149-
150-
result.add(setIterator);
169+
// init iterator
170+
result.add(ImSet(forIn, ImVarAccess(iteratorVar), iteratorCall));
151171

152172
ImStmts imBody = ImStmts();
153-
// exitwhen not #hasNext()
154-
imBody.add(ImExitwhen(forIn, JassIm.ImOperatorCall(WurstOperator.NOT, JassIm.ImExprs(ImFunctionCall(forIn, hasNextFuncIm, ImTypeArguments(), JassIm.ImExprs
155-
(JassIm
156-
.ImVarAccess(iteratorVar)), false, CallType.NORMAL)))));
157-
// elem = next()
158-
ImFunctionCall nextCall = ImFunctionCall(forIn, nextFuncIm, ImTypeArguments(), JassIm.ImExprs(JassIm.ImVarAccess(iteratorVar)), false, CallType.NORMAL);
159-
WurstType nextReturn = nextFunc.getReturnType();
160-
ImExpr nextCallWrapped = ExprTranslation.wrapTranslation(forIn, t, nextCall, nextReturn, loopVarType);
173+
174+
// exitwhen not iterator.hasNext()<S>()
175+
imBody.add(ImExitwhen(
176+
forIn,
177+
JassIm.ImOperatorCall(
178+
de.peeeq.wurstscript.WurstOperator.NOT,
179+
JassIm.ImExprs(
180+
ImFunctionCall(
181+
forIn, hasNextFuncIm, iterTypeArgs.copy(),
182+
JassIm.ImExprs(JassIm.ImVarAccess(iteratorVar)),
183+
false, CallType.NORMAL
184+
)
185+
)
186+
)
187+
));
188+
189+
// elem = iterator.next()<S>()
190+
ImFunctionCall nextCall = ImFunctionCall(
191+
forIn, nextFuncIm, iterTypeArgs.copy(),
192+
JassIm.ImExprs(JassIm.ImVarAccess(iteratorVar)),
193+
false, CallType.NORMAL
194+
);
195+
196+
ImExpr nextCallWrapped = de.peeeq.wurstscript.translation.imtranslation.ExprTranslation
197+
.wrapTranslation(forIn, t, nextCall, nextFunc.getReturnType(), elemType);
161198

162199
imBody.add(ImSet(forIn, ImVarAccess(t.getVarFor(forIn.getLoopVar())), nextCallWrapped));
163200

201+
// loop body
164202
imBody.addAll(t.translateStatements(f, forIn.getBody()));
165203

204+
// optional close()<S>()
166205
Optional<FuncLink> closeFunc = forIn.attrCloseFunc();
167206
closeFunc.ifPresent(funcLink -> {
168-
169-
// close iterator before each return
170207
imBody.accept(new de.peeeq.wurstscript.jassIm.Element.DefaultVisitor() {
171208
@Override
172209
public void visit(ImReturn imReturn) {
173210
super.visit(imReturn);
174-
imReturn.replaceBy(ImHelper.statementExprVoid(JassIm.ImStmts(ImFunctionCall(forIn, t.getFuncFor(funcLink.getDef()), ImTypeArguments(), JassIm
175-
.ImExprs(JassIm.ImVarAccess(iteratorVar)), false, CallType.NORMAL), imReturn.copy())));
211+
imReturn.replaceBy(
212+
ImHelper.statementExprVoid(
213+
JassIm.ImStmts(
214+
ImFunctionCall(
215+
forIn, t.getFuncFor(funcLink.getDef()),
216+
iterTypeArgs.copy(),
217+
JassIm.ImExprs(JassIm.ImVarAccess(iteratorVar)),
218+
false, CallType.NORMAL
219+
),
220+
imReturn.copy()
221+
)
222+
)
223+
);
176224
}
177-
178225
});
179-
180226
});
181227

182228
result.add(ImLoop(forIn, imBody));
183-
// close iterator after loop
184-
closeFunc.ifPresent(nameLink -> result.add(ImFunctionCall(forIn, t.getFuncFor(nameLink.getDef()), ImTypeArguments(), JassIm.ImExprs(JassIm
185-
.ImVarAccess(iteratorVar)), false, CallType.NORMAL)));
186229

230+
// close after loop
231+
closeFunc.ifPresent(nameLink ->
232+
result.add(
233+
ImFunctionCall(
234+
forIn, t.getFuncFor(nameLink.getDef()),
235+
iterTypeArgs.copy(),
236+
JassIm.ImExprs(JassIm.ImVarAccess(iteratorVar)),
237+
false, CallType.NORMAL
238+
)
239+
)
240+
);
187241
}
188242

189-
190243
return ImHelper.statementExprVoid(ImStmts(result));
191244
}
192245

246+
193247
/**
194248
* Translate a for in vararg loop. Unlike the other for loops we don't need
195249
* an iterator etc. because the loop is unrolled in the VarargEliminator

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -781,37 +781,6 @@ private void checkTypeExpr(TypeExpr e) {
781781

782782
}
783783

784-
/** For type usages inside a generic declaration, ban cross-flavor references (NEW cannot use LEGACY and vice versa). */
785-
private void checkGenericFlavorCompatibility(TypeExpr e) {
786-
// Only enforce *inside* a generic declaration:
787-
AstElementWithTypeParameters owner = nearestGenericOwner(e);
788-
if (owner == null) return;
789-
790-
@Nullable GenericFlavor ownerFlavor = flavorOf(owner);
791-
if (ownerFlavor == null) return; // non-generic owner (defensive)
792-
793-
// Resolve the referenced type definition:
794-
TypeDef targetDef = e.attrTypeDef();
795-
if (targetDef == null) return;
796-
797-
// Allow non-generic targets:
798-
@Nullable GenericFlavor targetFlavor = flavorOf(targetDef);
799-
if (targetFlavor == null) return;
800-
801-
if (ownerFlavor != targetFlavor) {
802-
// Craft a clear, actionable message:
803-
String targetKind = targetDef.getClass().getSimpleName().replace("Def", "").toLowerCase(); // "class", "interface", ...
804-
String targetName = targetDef.getName();
805-
if (ownerFlavor == GenericFlavor.NEW) {
806-
e.addError("Cannot reference legacy-generic " + targetKind + " '" + targetName
807-
+ "<T>' from a new-generic declaration. Migrate '" + targetName + "<T>' to '" + targetName + "<T:>' or convert this declaration to legacy generics.");
808-
} else {
809-
e.addError("Cannot reference new-generic " + targetKind + " '" + targetName
810-
+ "<T:>' from a legacy-generic declaration. Use legacy syntax here or migrate this declaration to new generics.");
811-
}
812-
}
813-
}
814-
815784
// --- Generic flavor detection ----------------------------------------------
816785
private enum GenericFlavor { NEW, LEGACY }
817786

@@ -824,16 +793,14 @@ private enum GenericFlavor { NEW, LEGACY }
824793
for (TypeParamDef tp : owner.getTypeParameters()) {
825794
if (isTypeParamNewGeneric(tp)) anyNew = true; else anyOld = true;
826795
if (anyNew && anyOld) {
827-
// Mixed syntax in a single declaration – if you already forbid this elsewhere,
828-
// this path won’t be reachable; otherwise warn/error here:
829796
tp.addError("Mixed generic syntax in one declaration is not allowed. Use either <T:> or <T> consistently.");
830797
// Pick a flavor to avoid cascaded errors:
831-
return GenericFlavor.NEW; // arbitrary; we already emitted an error
798+
return GenericFlavor.NEW;
832799
}
833800
}
834801
if (anyNew) return GenericFlavor.NEW;
835802
if (anyOld) return GenericFlavor.LEGACY;
836-
return null; // no type parameters after all (shouldn't happen)
803+
return null;
837804
}
838805

839806
/** Returns the flavor of the referenced generic definition, or null if the target is non-generic. */
@@ -844,6 +811,65 @@ private enum GenericFlavor { NEW, LEGACY }
844811
return null;
845812
}
846813

814+
/** Walk up to the nearest *structure* (ClassDef/InterfaceDef) which actually has type parameters. */
815+
private @Nullable AstElementWithTypeParameters nearestGenericStructureOwner(Element e) {
816+
Element p = e;
817+
while (p != null) {
818+
if (p instanceof ClassDef || p instanceof InterfaceDef) {
819+
AstElementWithTypeParameters a = (AstElementWithTypeParameters) p;
820+
if (a.getTypeParameters() != null && a.getTypeParameters().size() > 0) {
821+
return a;
822+
}
823+
// keep walking if the structure itself has no TPs
824+
}
825+
// IMPORTANT: skip function owners entirely — method generics are allowed to mix.
826+
if (p instanceof FuncDef) {
827+
return null;
828+
}
829+
p = p.getParent();
830+
}
831+
return null;
832+
}
833+
834+
/** For type usages inside ANY generic declaration (class/interface/function),
835+
* ban cross-flavor references (NEW cannot use LEGACY and vice versa). */
836+
private void checkGenericFlavorCompatibility(TypeExpr e) {
837+
// Enforce inside the nearest generic owner: class, interface, or function
838+
AstElementWithTypeParameters owner = nearestGenericOwner(e);
839+
if (owner == null) return;
840+
841+
@Nullable GenericFlavor ownerFlavor = flavorOf(owner);
842+
if (ownerFlavor == null) return; // owner not actually generic
843+
844+
// What type is being referenced?
845+
TypeDef targetDef = e.attrTypeDef();
846+
if (targetDef == null) return;
847+
848+
// Only care when the referenced definition itself is generic
849+
@Nullable GenericFlavor targetFlavor = flavorOf(targetDef);
850+
if (targetFlavor == null) return; // non-generic target → allowed
851+
852+
if (ownerFlavor != targetFlavor) {
853+
String targetKind =
854+
(targetDef instanceof ClassDef) ? "class" :
855+
(targetDef instanceof InterfaceDef) ? "interface" : "type";
856+
String targetName = targetDef.getName();
857+
858+
if (ownerFlavor == GenericFlavor.NEW) {
859+
// owner is <T:> and target is legacy
860+
e.addError("Cannot reference legacy-generic " + targetKind + " '" + targetName
861+
+ "<T>' from a new-generic declaration. Migrate '" + targetName
862+
+ "<T>' to '" + targetName + "<T:>' or convert this declaration to legacy generics.");
863+
} else {
864+
// owner is legacy <T> and target is new
865+
e.addError("Cannot reference new-generic " + targetKind + " '" + targetName
866+
+ "<T:>' from a legacy-generic declaration. Use legacy syntax here or migrate this declaration to new generics.");
867+
}
868+
}
869+
}
870+
871+
872+
847873
/** Walk up and find the *nearest* generic declaration owning the current node (class/interface/func). */
848874
private @Nullable AstElementWithTypeParameters nearestGenericOwner(Element e) {
849875
Element p = e;

0 commit comments

Comments
 (0)