Skip to content

Commit 307674d

Browse files
committed
tests
1 parent 9333e5d commit 307674d

4 files changed

Lines changed: 201 additions & 1 deletion

File tree

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,8 +776,91 @@ private void checkTypeExpr(TypeExpr e) {
776776
checkTypeparamsUsedCorrectly(e, tp);
777777
}
778778

779+
// Cross-flavor generics ban inside generic declarations:
780+
checkGenericFlavorCompatibility(e);
781+
782+
}
783+
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+
815+
// --- Generic flavor detection ----------------------------------------------
816+
private enum GenericFlavor { NEW, LEGACY }
817+
818+
/** Returns NEW if all TPs are new style (colon), LEGACY if any exist and none are colon, else null (non-generic). */
819+
private @Nullable GenericFlavor flavorOf(AstElementWithTypeParameters owner) {
820+
TypeParamDefs tps = owner.getTypeParameters();
821+
if (tps == null || tps.size() == 0) return null;
822+
823+
boolean anyNew = false, anyOld = false;
824+
for (TypeParamDef tp : owner.getTypeParameters()) {
825+
if (isTypeParamNewGeneric(tp)) anyNew = true; else anyOld = true;
826+
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:
829+
tp.addError("Mixed generic syntax in one declaration is not allowed. Use either <T:> or <T> consistently.");
830+
// Pick a flavor to avoid cascaded errors:
831+
return GenericFlavor.NEW; // arbitrary; we already emitted an error
832+
}
833+
}
834+
if (anyNew) return GenericFlavor.NEW;
835+
if (anyOld) return GenericFlavor.LEGACY;
836+
return null; // no type parameters after all (shouldn't happen)
837+
}
838+
839+
/** Returns the flavor of the referenced generic definition, or null if the target is non-generic. */
840+
private @Nullable GenericFlavor flavorOf(TypeDef def) {
841+
if (def instanceof AstElementWithTypeParameters) {
842+
return flavorOf((AstElementWithTypeParameters) def);
843+
}
844+
return null;
845+
}
846+
847+
/** Walk up and find the *nearest* generic declaration owning the current node (class/interface/func). */
848+
private @Nullable AstElementWithTypeParameters nearestGenericOwner(Element e) {
849+
Element p = e;
850+
while (p != null) {
851+
if (p instanceof FuncDef || p instanceof ClassDef || p instanceof InterfaceDef) {
852+
AstElementWithTypeParameters a = (AstElementWithTypeParameters) p;
853+
if (a.getTypeParameters() != null && a.getTypeParameters().size() > 0) {
854+
return a;
855+
}
856+
}
857+
p = p.getParent();
858+
}
859+
return null;
779860
}
780861

862+
863+
781864
/**
782865
* Checks that module types are only used in valid places
783866
*/

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/controlflow/DataflowAnomalyAnalysis.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,13 @@ void checkFinal(VarStates fin) {
505505
if (ur instanceof StmtSet) {
506506
errorPos = ((StmtSet) ur).getUpdatedExpr();
507507
}
508-
errorPos.addWarning("The assignment to " + Utils.printElement(var) + " is never read.");
508+
@Nullable ExprClosure exprClosure = errorPos.attrNearestExprClosure();
509+
@Nullable ExprClosure exprClosure1 = var.attrNearestExprClosure();
510+
if (exprClosure != null && exprClosure != exprClosure1) {
511+
errorPos.addWarning("This assignment to the closure-captured variable " + Utils.printElement(var) + " has no effect outside the closure.");
512+
} else {
513+
errorPos.addWarning("The assignment to " + Utils.printElement(var) + " is never read.");
514+
}
509515
}
510516
}
511517
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/BugTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,26 @@ public void unreadVarWarning2() { // #380
827827
);
828828
}
829829

830+
@Test
831+
public void unreadVarWarning3() { // #380
832+
testAssertErrorsLines(true, "closure-captured variable",
833+
"package test",
834+
"@annotation public function annotation()",
835+
"@annotation public function extern()",
836+
"@extern native I2S(int x) returns string",
837+
"native testSuccess()",
838+
"interface Fn",
839+
" function apply()",
840+
"function foo(Fn _f)",
841+
"init",
842+
" var i = 5",
843+
" foo() ->",
844+
" i++",
845+
" if i == 5",
846+
" testSuccess()"
847+
);
848+
}
849+
830850

831851
@Test
832852
public void unreadVarWarningArrays() { // #813

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GenericsWithTypeclassesTests.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,4 +1440,95 @@ public void genericStaticTuple_runtime() {
14401440
);
14411441
}
14421442

1443+
@Test
1444+
public void mixingNewOwner_legacyType_classField() {
1445+
testAssertErrorsLines(false,
1446+
"Cannot reference legacy-generic classimpl 'B<T>' from a new-generic declaration. Migrate 'B<T>' to 'B<T:>' or convert this declaration to legacy generics.",
1447+
"package test",
1448+
"class B<T>",
1449+
"class A<T:>",
1450+
" B<T> b"
1451+
);
1452+
}
1453+
1454+
@Test
1455+
public void mixingLegacyOwner_newType_classField() {
1456+
testAssertErrorsLines(false,
1457+
"Cannot reference new-generic classimpl 'B<T:>' from a legacy-generic declaration. Use legacy syntax here or migrate this declaration to new generics.",
1458+
"package test",
1459+
"class B<T:>",
1460+
"class A<T>",
1461+
" B<T> b"
1462+
);
1463+
}
1464+
1465+
@Test
1466+
public void mixingNewOwner_legacyType_functionReturn() {
1467+
testAssertErrorsLines(false,
1468+
"Cannot reference legacy-generic classimpl 'B<T>' from a new-generic declaration. Migrate 'B<T>' to 'B<T:>' or convert this declaration to legacy generics.",
1469+
"package test",
1470+
"class B<T>",
1471+
"function makeB<T:>() returns B<T>",
1472+
" return null"
1473+
);
1474+
}
1475+
1476+
@Test
1477+
public void mixingLegacyOwner_newType_functionReturn() {
1478+
testAssertErrorsLines(false,
1479+
"Cannot reference new-generic classimpl 'B<T:>' from a legacy-generic declaration. Use legacy syntax here or migrate this declaration to new generics.",
1480+
"package test",
1481+
"class B<T:>",
1482+
"function makeB<T>() returns B<T>",
1483+
" return null"
1484+
);
1485+
}
1486+
1487+
@Test
1488+
public void mixingNewOwner_legacyType_inExtendsClause() {
1489+
testAssertErrorsLines(false,
1490+
"Cannot reference legacy-generic interfaceimpl 'I<T>' from a new-generic declaration. Migrate 'I<T>' to 'I<T:>' or convert this declaration to legacy generics.",
1491+
"package test",
1492+
"interface I<T>",
1493+
"class C<T:> implements I<T>"
1494+
);
1495+
}
1496+
1497+
@Test
1498+
public void mixingLegacyOwner_newType_methodReturn() {
1499+
testAssertErrorsLines(false,
1500+
"Cannot reference new-generic interfaceimpl 'I<T:>' from a legacy-generic declaration. Use legacy syntax here or migrate this declaration to new generics.",
1501+
"package test",
1502+
"interface I<T:>",
1503+
"class C<T>",
1504+
" function f() returns I<T>",
1505+
" return null"
1506+
);
1507+
}
1508+
1509+
@Test
1510+
public void mixingNewOwner_legacyType_nestedGenericUse() {
1511+
testAssertErrorsLines(false,
1512+
"Cannot reference legacy-generic classimpl 'Box<T>' from a new-generic declaration. Migrate 'Box<T>' to 'Box<T:>' or convert this declaration to legacy generics.",
1513+
"package test",
1514+
"class Box<X>",
1515+
"class B<T>",
1516+
"class A<T:>",
1517+
" Box<B<T>> field"
1518+
);
1519+
}
1520+
1521+
@Test
1522+
public void mixingLegacyOwner_newType_insideGenericClassMethod() {
1523+
testAssertErrorsLines(false,
1524+
"Cannot reference new-generic classimpl 'B<T:>' from a legacy-generic declaration. Use legacy syntax here or migrate this declaration to new generics.",
1525+
"package test",
1526+
"class B<T:>",
1527+
"class A<T>",
1528+
" function usee()",
1529+
" B<T> x = null"
1530+
);
1531+
}
1532+
1533+
14431534
}

0 commit comments

Comments
 (0)