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 8273e2179..a2149d68b 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 @@ -25,7 +25,7 @@ jassGlobalsBlock: ; jassGlobalDecl: - constant='constant'? typeExpr name=ID ('=' initial=expr)? NL + constant='constant'? typeExpr name=(ID|CONTINUE|SKIP_|BREAK) ('=' initial=expr)? NL ; jassFuncDef: @@ -35,7 +35,7 @@ jassFuncDef: 'endfunction' NL ; -jassLocal: 'local' typeExpr name=ID ('=' initial=expr)? NL; +jassLocal: 'local' typeExpr name=(ID|CONTINUE|SKIP_|BREAK) ('=' initial=expr)? NL; jassStatements: stmts+=jassStatement*; @@ -224,7 +224,7 @@ funcSignature: formalParameters: '(' (params+=formalParameter (',' params+=formalParameter)*)? ')'; formalParameter: - vararg=VARARG? typeExpr name=ID + vararg=VARARG? typeExpr name=(ID|CONTINUE|SKIP_|BREAK) ; typeExpr: @@ -238,7 +238,7 @@ typeExpr: varDef: modifiersWithDoc ('var'|constant='constant' varType=typeExpr?|constant='let'|varType=typeExpr) - name=ID ('=' variableInit)? NL + name=(ID|CONTINUE|SKIP_|BREAK) ('=' variableInit)? NL ; variableInit: (arrayInit | initial=expr); @@ -312,12 +312,12 @@ stmtWhile: localVarDef: (var='var'|let='let'|type=typeExpr) - name=ID ('=' variableInit)? + name=(ID|CONTINUE|SKIP_|BREAK) ('=' variableInit)? ; localVarDefInline: - typeExpr? name=ID + typeExpr? name=(ID|CONTINUE|SKIP_|BREAK) ; stmtSet: @@ -335,12 +335,12 @@ exprAssignable: ; exprMemberVar: - expr dots=('.'|'..') varname=ID? indexes? + expr dots=('.'|'..') varname=(ID|CONTINUE|SKIP_|BREAK)? indexes? ; exprVarAccess: - varname=(ID|IT) indexes? + varname=(ID|CONTINUE|SKIP_|BREAK|IT) indexes? ; @@ -354,7 +354,7 @@ expr: | left=expr 'castTo' castToType=typeExpr | left=expr 'instanceof' instaneofType=typeExpr | receiver=expr dotsCall=('.'|'..') funcName=ID? typeArgs argumentList - | receiver=expr dotsVar=('.'|'..') varName=ID? indexes? + | receiver=expr dotsVar=('.'|'..') varName=(ID|CONTINUE|SKIP_|BREAK)? indexes? | left=expr op=('*'|'/'|'%'|'div'|'mod') right=expr | op='-' right=expr // TODO move unary minus one up to be compatible with Java etc. // currently it is here to be backwards compatible with the old wurst parser @@ -375,7 +375,7 @@ exprPrimary: | exprClosure | exprStatementsBlock | exprDestroy - | varname=(ID|IT) indexes? + | varname=(ID|CONTINUE|SKIP_|BREAK|IT) indexes? | atom=(INT | REAL | STRING @@ -412,7 +412,7 @@ shortFormalParameters: | /* empty */ ; -shortFormalParameter: typeExpr? name=ID; +shortFormalParameter: typeExpr? name=(ID|CONTINUE|SKIP_|BREAK); typeParams: ('<' (params+=typeParam (',' params+=typeParam)*)? '>')?; @@ -447,6 +447,7 @@ exprList : exprs+=expr (',' exprs+=expr)*; + nativeType: 'nativetype' name=ID ('extends' extended=ID)? NL; initBlock: 'init' NL statementsBlock; nativeDef: modifiersWithDoc 'native' funcSignature NL; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java index b4aa919bc..325723a9d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java @@ -6,9 +6,12 @@ import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstscript.WLogger; import de.peeeq.wurstscript.ast.*; +import de.peeeq.wurstscript.attributes.AttrFuncDef; import de.peeeq.wurstscript.attributes.CofigOverridePackages; +import de.peeeq.wurstscript.attributes.names.FuncLink; import de.peeeq.wurstscript.attributes.names.NameLink; import de.peeeq.wurstscript.parser.WPos; +import de.peeeq.wurstscript.types.WurstTypeArray; import de.peeeq.wurstscript.types.WurstType; import de.peeeq.wurstscript.types.WurstTypeNamedScope; import de.peeeq.wurstscript.utils.Utils; @@ -77,6 +80,10 @@ private List execute2(ModelManager modelManager) { return linkTo(originalDecl); } } + FunctionDefinition indexOpDecl = getIndexOperatorDeclarationAtPos(e); + if (indexOpDecl != null) { + return linkTo(indexOpDecl); + } if (e instanceof FuncRef) { FuncRef funcRef = (FuncRef) e; FunctionDefinition decl = funcRef.attrFuncDef(); @@ -117,6 +124,37 @@ private List execute2(ModelManager modelManager) { return Collections.emptyList(); } + private FunctionDefinition getIndexOperatorDeclarationAtPos(Element e) { + Element target = e; + if (target instanceof Indexes && target.getParent() instanceof NameRef) { + target = target.getParent(); + } + if (!(target instanceof NameRef) || !(target instanceof AstElementWithIndexes)) { + return null; + } + + NameRef nr = (NameRef) target; + AstElementWithIndexes withIndexes = (AstElementWithIndexes) target; + if (withIndexes.getIndexes().size() != 1) { + return null; + } + NameLink link = nr.attrNameLink(); + if (link == null || link.getTyp() instanceof WurstTypeArray) { + return null; + } + + WurstType receiverType = link.getTyp(); + WurstType indexType = withIndexes.getIndexes().get(0).attrTyp(); + if (nr.getParent() instanceof StmtSet && ((StmtSet) nr.getParent()).getUpdatedExpr() == nr) { + StmtSet set = (StmtSet) nr.getParent(); + FuncLink f = AttrFuncDef.getIndexSetOperator(nr, receiverType, indexType, set.getRight().attrTyp()); + return f == null ? null : f.getDef(); + } + + FuncLink f = AttrFuncDef.getIndexGetOperator(nr, receiverType, indexType); + return f == null ? null : f.getDef(); + } + private List typeDefinitionFor(Element e) { if (e instanceof TypeExpr) { TypeExpr typeExpr = (TypeExpr) e; 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 98239cd59..2ed203c66 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 @@ -101,9 +101,23 @@ public static WurstType calculate(ExprVarArrayAccess term) { WurstType varDefType = varDef.getTyp(); if (varDefType instanceof WurstTypeArray) { return ((WurstTypeArray) varDefType).getBaseType(); - } else { - term.addError("Variable " + varDef.getName() + " is of type " + varDefType + ", should be an array variable."); } + if (term.getIndexes().size() == 1) { + WurstType indexType = term.getIndexes().get(0).attrTyp(); + FuncLink getOverload = AttrFuncDef.getIndexGetOperator(term, varDefType, indexType); + if (getOverload != null) { + return getOverload.getReturnType(); + } + if (isWriteAccess(term)) { + FuncLink setOverload = AttrFuncDef.getIndexSetOperatorByIndex(term, varDefType, indexType); + if (setOverload != null) { + return setOverload.getParameterType(1); + } + return WurstTypeUnknown.instance(); + } + } + term.addError("Variable " + varDef.getName() + " is of type " + varDefType + + ", should be an array variable or define operator overloading function " + AttrFuncDef.overloadingIndexGet + "."); return WurstTypeUnknown.instance(); } @@ -412,7 +426,21 @@ public static WurstType calculate(ExprMemberArrayVarDot term) { WurstTypeArray ar = (WurstTypeArray) typ; return ar.getBaseType(); } - term.addError("Variable " + term.getVarName() + " is not an array."); + if (term.getIndexes().size() == 1) { + WurstType indexType = term.getIndexes().get(0).attrTyp(); + FuncLink getOverload = AttrFuncDef.getIndexGetOperator(term, typ, indexType); + if (getOverload != null) { + return getOverload.getReturnType(); + } + if (isWriteAccess(term)) { + FuncLink setOverload = AttrFuncDef.getIndexSetOperatorByIndex(term, typ, indexType); + if (setOverload != null) { + return setOverload.getParameterType(1); + } + return WurstTypeUnknown.instance(); + } + } + term.addError("Variable " + term.getVarName() + " is not an array and has no " + AttrFuncDef.overloadingIndexGet + " overload."); return typ; } @@ -592,4 +620,12 @@ public static WurstType calculate(ExprArrayLength exprArrayLength) { exprArrayLength.addError(".length is only valid on arrays."); return de.peeeq.wurstscript.types.WurstTypeUnknown.instance(); } + + private static boolean isWriteAccess(NameRef node) { + if (node.getParent() instanceof StmtSet) { + StmtSet stmtSet = (StmtSet) node.getParent(); + return stmtSet.getUpdatedExpr() == node; + } + return false; + } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java index b28e21b90..8e3d6cbfa 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java @@ -34,6 +34,8 @@ public class AttrFuncDef { public final static String overloadingMinus = "op_minus"; public final static String overloadingMult = "op_mult"; public final static String overloadingDiv = "op_divReal"; + public final static String overloadingIndexGet = "op_index"; + public final static String overloadingIndexSet = "op_indexAssign"; public static FuncLink calculate(final ExprFuncRef node) { @@ -206,6 +208,76 @@ private static boolean isConstructorThisCall(ExprFunctionCall node) { return searchMemberFunc(left, left.attrTyp(), funcName, Collections.singletonList(right.attrTyp())); } + public static @Nullable FuncLink getIndexGetOperator(Expr node, WurstType receiverType, WurstType indexType) { + List argTypes = Collections.singletonList(indexType); + FuncLink candidate = searchMemberFunc(node, receiverType, overloadingIndexGet, argTypes); + if (candidate == null || !matchesArguments(node, candidate, argTypes)) { + return null; + } + return candidate; + } + + public static @Nullable FuncLink getIndexSetOperator(Expr node, WurstType receiverType, WurstType indexType, WurstType valueType) { + List argTypes = Lists.newArrayList(indexType, valueType); + FuncLink candidate = searchMemberFunc(node, receiverType, overloadingIndexSet, argTypes); + if (candidate == null || !matchesArguments(node, candidate, argTypes)) { + return null; + } + return candidate; + } + + public static @Nullable FuncLink getIndexSetOperatorByIndex(Expr node, WurstType receiverType, WurstType indexType) { + Collection funcs1 = node.lookupMemberFuncs(receiverType, overloadingIndexSet); + if (funcs1.isEmpty()) { + return null; + } + List funcs = filterInvisible(overloadingIndexSet, node, funcs1); + funcs = filterByReceiverType(node, overloadingIndexSet, funcs); + List byParamCount = Lists.newArrayList(); + for (FuncLink f : funcs) { + if (f.getParameterTypes().size() == 2) { + byParamCount.add(f); + } + } + if (byParamCount.isEmpty()) { + return null; + } + List byIndexType = Lists.newArrayList(); + for (FuncLink f : byParamCount) { + VariableBinding mapping = f.getVariableBinding(); + WurstType expectedIndexType = f.getParameterType(0); + VariableBinding m2 = indexType.matchAgainstSupertype(expectedIndexType, node, mapping, VariablePosition.RIGHT); + if (m2 != null) { + byIndexType.add(f); + } + } + if (byIndexType.isEmpty()) { + return null; + } + if (byIndexType.size() == 1) { + return byIndexType.get(0); + } + // ambiguous write-only expected type context: pick deterministic first + return byIndexType.get(0); + } + + private static boolean matchesArguments(Element node, FuncLink f, List argumentTypes) { + if (f.getParameterTypes().size() != argumentTypes.size()) { + return false; + } + VariableBinding mapping = f.getVariableBinding(); + for (int i = 0; i < argumentTypes.size(); i++) { + WurstType at = argumentTypes.get(i); + WurstType pt = f.getParameterType(i); + VariableBinding m2 = at.matchAgainstSupertype(pt, node, mapping, VariablePosition.RIGHT); + if (m2 == null) { + return false; + } + mapping = m2; + } + return true; + } + /** * checks if operator is a native operator like for 1+2 diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateClasses.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateClasses.java index 69a918c9e..ef465592e 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateClasses.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateClasses.java @@ -43,6 +43,7 @@ public class EliminateClasses { INSTANCE_COUNT, MAX_INSTANCE_COUNT ); + private final Map classIds; private ImFunction typeIdToTypeNameFunc; private ImFunction maxTypeIdFunc; private ImFunction instanceCountFunc; @@ -52,6 +53,7 @@ public EliminateClasses(ImTranslator tr, ImProg prog, boolean checkedDispatch) { translator = tr; this.prog = prog; this.checkedDispatch = checkedDispatch; + this.classIds = TypeId.calculate(prog); } public void eliminateClasses() { @@ -88,32 +90,47 @@ private void createReflectionFunctions() { "instanceCount", TypesHelper.imInt(), JassIm.ImIntVal(0), - c -> - JassIm.ImOperatorCall(WurstOperator.MINUS, + c -> { + ClassManagementVars m = translator.getClassManagementVarsFor(c); + if (m == null) { + return JassIm.ImIntVal(0); + } + return JassIm.ImOperatorCall(WurstOperator.MINUS, JassIm.ImExprs( - JassIm.ImVarAccess(translator.getClassManagementVarsFor(c).maxIndex), - JassIm.ImVarAccess(translator.getClassManagementVarsFor(c).freeCount)))); + JassIm.ImVarAccess(m.maxIndex), + JassIm.ImVarAccess(m.freeCount))); + }); maxInstanceCountFunc = accessClassManagementVar( "maxInstanceCount", TypesHelper.imInt(), JassIm.ImIntVal(0), - c -> JassIm.ImVarAccess(translator.getClassManagementVarsFor(c).maxIndex)); + c -> { + ClassManagementVars m = translator.getClassManagementVarsFor(c); + if (m == null) { + return JassIm.ImIntVal(0); + } + return JassIm.ImVarAccess(m.maxIndex); + }); maxTypeIdFunc = maxTypeIdFunc(); } @NotNull private ImFunction maxTypeIdFunc() { ImVars parameters = JassIm.ImVars(); - int maxTypeId = calculateMaxTypeId(prog); + int maxTypeId = calculateMaxTypeId(classIds); ImFunction f = JassIm.ImFunction(prog.getTrace(), "maxTypeId", JassIm.ImTypeVars(), parameters, imInt(), JassIm.ImVars(), JassIm.ImStmts(JassIm.ImReturn(prog.getTrace(), JassIm.ImIntVal(maxTypeId))), Collections.emptyList()); prog.getFunctions().add(f); return f; } public static int calculateMaxTypeId(ImProg prog) { + return calculateMaxTypeId(TypeId.calculate(prog)); + } + + private static int calculateMaxTypeId(Map classId) { boolean seen = false; int best = 0; - for (Integer x : prog.attrTypeId().values()) { + for (Integer x : classId.values()) { int i = x; if (!seen || i > best) { seen = true; @@ -129,10 +146,9 @@ private ImFunction accessClassManagementVar(String funcName, ImType returnType, ImVar typeId = JassIm.ImVar(trace, TypesHelper.imInt(), "typeId", false); ImVars parameters = JassIm.ImVars(typeId); ImVars locals = JassIm.ImVars(); - Map classId = prog.attrTypeId(); - int maxTypeId = calculateMaxTypeId(prog); + int maxTypeId = calculateMaxTypeId(classIds); ImClass[] typeIdToClass = new ImClass[maxTypeId + 1]; - for (Map.Entry e : classId.entrySet()) { + for (Map.Entry e : classIds.entrySet()) { typeIdToClass[e.getValue()] = e.getKey(); } ImStmts body = generateBinarySearch(1, maxTypeId, typeId, typeIdToClass, makeAccess); @@ -149,7 +165,11 @@ private ImStmts generateBinarySearch(int lower, int upper, ImVar typeId, ImClass if (lower > upper) { return JassIm.ImStmts(); } else if (lower == upper) { - return JassIm.ImStmts(JassIm.ImReturn(prog.getTrace(), makeAccess.apply(typeIdToClass[lower]))); + ImClass c = typeIdToClass[lower]; + if (c == null) { + return JassIm.ImStmts(); + } + return JassIm.ImStmts(JassIm.ImReturn(prog.getTrace(), makeAccess.apply(c))); } else { int mid = lower + (upper - lower) / 2; return @@ -541,7 +561,7 @@ private void calculateTypeIdToMethodHelper(ImClass c, } } if (current != null) { - typeIdToMethod.put(c.attrTypeId(), current); + typeIdToMethod.put(typeIdOf(c), current); } // process subclasses: for (ImClass sc : c.attrSubclasses()) { @@ -667,7 +687,7 @@ private void replaceTypeIdOfObj(ImTypeIdOfObj e) { } private void replaceTypeIdOfClass(ImTypeIdOfClass e) { - e.replaceBy(JassIm.ImIntVal(e.getClazz().getClassDef().attrTypeId())); + e.replaceBy(JassIm.ImIntVal(typeIdOf(e.getClazz().getClassDef()))); } private void replaceInstanceof(ImInstanceof e) { @@ -675,8 +695,7 @@ private void replaceInstanceof(ImInstanceof e) { List allSubClasses = getAllSubclasses(e.getClazz().getClassDef()); List subClassIds = new ArrayList<>(); for (ImClass allSubClass : allSubClasses) { - Integer attrTypeId = allSubClass.attrTypeId(); - subClassIds.add(attrTypeId); + subClassIds.add(typeIdOf(allSubClass)); } List idRanges = IntRange.createFromIntList(subClassIds); ImExpr obj = e.getObj(); @@ -736,6 +755,18 @@ private void getAllSubclassesH(List result, ImClass clazz) { } } + private int typeIdOf(ImClass c) { + Integer id = classIds.get(c); + if (id != null) { + return id; + } + Integer fallback = TypeId.calculate(prog).get(c); + if (fallback != null) { + return fallback; + } + throw new CompileError(c, "Could not resolve type id for class " + c.getName() + "."); + } + private void replaceDealloc(ImDealloc e) { ImFunction deallocFunc = translator.deallocFunc.getFor(e.getClazz().getClassDef()); ImExpr obj = e.getObj(); 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 4abe4b137..d0f9a25a5 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 @@ -5,7 +5,9 @@ import de.peeeq.wurstscript.WurstOperator; import de.peeeq.wurstscript.ast.*; import de.peeeq.wurstscript.ast.Element; +import de.peeeq.wurstscript.attributes.AttrFuncDef; import de.peeeq.wurstscript.attributes.CompileError; +import de.peeeq.wurstscript.attributes.names.FuncLink; import de.peeeq.wurstscript.attributes.names.NameLink; import de.peeeq.wurstscript.attributes.names.OtherLink; import de.peeeq.wurstscript.jassIm.*; @@ -262,6 +264,7 @@ private static ImExpr translateNameDef(NameRef e, ImTranslator t, ImFunction f) VarDef varDef = (VarDef) decl; ImVar v = t.getVarFor(varDef); + @Nullable FuncLink indexGetOverload = getIndexGetOverload(e, link); if (e.attrImplicitParameter() instanceof Expr) { // we have implicit parameter @@ -279,6 +282,13 @@ private static ImExpr translateNameDef(NameRef e, ImTranslator t, ImFunction f) } if (e instanceof AstElementWithIndexes) { + if (indexGetOverload != null) { + AstElementWithIndexes withIndexes = (AstElementWithIndexes) e; + ImExpr receiver = JassIm.ImMemberAccess(e, implicitParam.imTranslateExpr(t, f), JassIm.ImTypeArguments(), v, JassIm.ImExprs()); + ImExpr index = withIndexes.getIndexes().get(0).imTranslateExpr(t, f); + ImFunction calledFunc = t.getFuncFor(indexGetOverload.getDef()); + return ImFunctionCall(e, calledFunc, ImTypeArguments(), ImExprs(receiver, index), false, CallType.NORMAL); + } ImExpr index1 = implicitParam.imTranslateExpr(t, f); ImExpr index2 = ((AstElementWithIndexes) e).getIndexes().get(0).imTranslateExpr(t, f); return JassIm.ImMemberAccess(e, index1, JassIm.ImTypeArguments(), v, JassIm.ImExprs(index2)); @@ -289,6 +299,13 @@ private static ImExpr translateNameDef(NameRef e, ImTranslator t, ImFunction f) } else { // direct var access if (e instanceof AstElementWithIndexes) { + if (indexGetOverload != null) { + AstElementWithIndexes withIndexes = (AstElementWithIndexes) e; + ImExpr receiver = ImVarAccess(v); + ImExpr index = withIndexes.getIndexes().get(0).imTranslateExpr(t, f); + ImFunction calledFunc = t.getFuncFor(indexGetOverload.getDef()); + return ImFunctionCall(e, calledFunc, ImTypeArguments(), ImExprs(receiver, index), false, CallType.NORMAL); + } // direct access array var AstElementWithIndexes withIndexes = (AstElementWithIndexes) e; if (withIndexes.getIndexes().size() > 1) { @@ -752,6 +769,10 @@ public static ImLExpr translateLvalue(LExpr e, ImTranslator t, ImFunction f) { VarDef varDef = (VarDef) decl; ImVar v = t.getVarFor(varDef); + NameLink link = e.attrNameLink(); + @Nullable FuncLink indexGetOverload = (link == null || !(e instanceof NameRef)) + ? null + : getIndexGetOverload((NameRef) e, link); if (e.attrImplicitParameter() instanceof Expr) { // we have implicit parameter @@ -771,6 +792,9 @@ public static ImLExpr translateLvalue(LExpr e, ImTranslator t, ImFunction f) { } if (e instanceof AstElementWithIndexes) { + if (indexGetOverload != null) { + throw new CompileError(e.getSource(), "Cannot assign to overloaded [] access without " + AttrFuncDef.overloadingIndexSet + "."); + } ImExpr index1 = implicitParam.imTranslateExpr(t, f); ImExpr index2 = ((AstElementWithIndexes) e).getIndexes().get(0).imTranslateExpr(t, f); return JassIm.ImMemberAccess(e, index1, JassIm.ImTypeArguments(), v, JassIm.ImExprs(index2)); @@ -782,6 +806,9 @@ public static ImLExpr translateLvalue(LExpr e, ImTranslator t, ImFunction f) { } else { // direct var access if (e instanceof AstElementWithIndexes) { + if (indexGetOverload != null) { + throw new CompileError(e.getSource(), "Cannot assign to overloaded [] access without " + AttrFuncDef.overloadingIndexSet + "."); + } // direct access array var AstElementWithIndexes withIndexes = (AstElementWithIndexes) e; if (withIndexes.getIndexes().size() > 1) { @@ -810,4 +837,19 @@ public static ImExpr translate(ExprArrayLength exprArrayLength, ImTranslator tra return JassIm.ImIntVal(0); } + private static @Nullable FuncLink getIndexGetOverload(NameRef e, NameLink link) { + if (!(e instanceof AstElementWithIndexes)) { + return null; + } + AstElementWithIndexes withIndexes = (AstElementWithIndexes) e; + if (withIndexes.getIndexes().size() != 1) { + return null; + } + WurstType receiverType = link.getTyp(); + if (receiverType instanceof WurstTypeArray) { + return null; + } + return AttrFuncDef.getIndexGetOperator(e, receiverType, withIndexes.getIndexes().get(0).attrTyp()); + } + } 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 db5870f25..267e5607f 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 @@ -2024,7 +2024,40 @@ public ImMethod getMethodFor(FuncDef f) { } public ClassManagementVars getClassManagementVarsFor(ImClass c) { - return getClassManagementVars().get(c); + Map vars = getClassManagementVars(); + ClassManagementVars res = vars.get(c); + if (res != null) { + return res; + } + // Try to recover by aliasing to an already existing entry without rebuilding. + ClassManagementVars alias = findClassManagementAlias(c, vars); + if (alias != null) { + vars.put(c, alias); + return alias; + } + // Extend mapping for current class graph only; do not clear and rebuild map, + // as rebuilding creates duplicate globals/initializers. + Partitions p = buildClassPartitions(); + p.add(c); + for (ImClassType sc : c.getSuperClasses()) { + p.union(c, sc.getClassDef()); + } + ImClass rep = p.getRep(c); + ClassManagementVars repVars = vars.get(rep); + if (repVars == null) { + repVars = findClassManagementAlias(rep, vars); + if (repVars == null) { + repVars = new ClassManagementVars(rep, this); + } + vars.put(rep, repVars); + } + for (ImClass cls : imProg.getClasses()) { + if (p.getRep(cls) == rep) { + vars.putIfAbsent(cls, repVars); + } + } + vars.put(c, repVars); + return repVars; } @@ -2036,8 +2069,18 @@ public Map getClassManagementVars() { if (classManagementVars != null) { return classManagementVars; } - // create partitions, such that each sub-class and super-class are in - // the same partition + Partitions p = buildClassPartitions(); + // generate typeId variables + classManagementVars = new IdentityHashMap<>(); + for (ImClass c : imProg.getClasses()) { + ImClass rep = p.getRep(c); + ClassManagementVars v = classManagementVars.computeIfAbsent(rep, r -> new ClassManagementVars(r, this)); + classManagementVars.put(c, v); + } + return classManagementVars; + } + + private Partitions buildClassPartitions() { Partitions p = new Partitions<>(); for (ImClass c : imProg.getClasses()) { p.add(c); @@ -2045,14 +2088,17 @@ public Map getClassManagementVars() { p.union(c, sc.getClassDef()); } } - // generate typeId variables - classManagementVars = Maps.newLinkedHashMap(); - for (ImClass c : imProg.getClasses()) { - ImClass rep = p.getRep(c); - ClassManagementVars v = classManagementVars.computeIfAbsent(rep, r -> new ClassManagementVars(r, this)); - classManagementVars.put(c, v); + return p; + } + + private @Nullable ClassManagementVars findClassManagementAlias(ImClass target, Map vars) { + for (Map.Entry e : vars.entrySet()) { + ImClass k = e.getKey(); + if (k == target || k.attrTrace() == target.attrTrace()) { + return e.getValue(); + } } - return classManagementVars; + return null; } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/StmtTranslation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/StmtTranslation.java index 24940f14d..ffe88cfa0 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/StmtTranslation.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/StmtTranslation.java @@ -4,12 +4,17 @@ import de.peeeq.wurstscript.WurstOperator; import de.peeeq.wurstscript.ast.*; import de.peeeq.wurstscript.ast.Element; +import de.peeeq.wurstscript.attributes.AttrFuncDef; import de.peeeq.wurstscript.attributes.CompileError; import de.peeeq.wurstscript.attributes.names.FuncLink; +import de.peeeq.wurstscript.attributes.names.NameLink; +import de.peeeq.wurstscript.attributes.names.OtherLink; import de.peeeq.wurstscript.jassIm.*; import de.peeeq.wurstscript.types.TypesHelper; import de.peeeq.wurstscript.types.WurstType; +import de.peeeq.wurstscript.types.WurstTypeArray; import de.peeeq.wurstscript.types.WurstTypeVararg; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; import java.util.Optional; @@ -330,11 +335,54 @@ public static ImStmt translate(StmtReturn s, ImTranslator t, ImFunction f) { public static ImStmt translate(StmtSet s, ImTranslator t, ImFunction f) { + ImStmt overloadedIndexSet = translateOverloadedIndexSet(s, t, f); + if (overloadedIndexSet != null) { + return overloadedIndexSet; + } ImLExpr updated = s.getUpdatedExpr().imTranslateExprLvalue(t, f); ImExpr right = s.getRight().imTranslateExpr(t, f); return ImSet(s, updated, right); } + private static @Nullable ImStmt translateOverloadedIndexSet(StmtSet s, ImTranslator t, ImFunction f) { + if (!(s.getUpdatedExpr() instanceof NameRef) || !(s.getUpdatedExpr() instanceof AstElementWithIndexes)) { + return null; + } + NameRef left = (NameRef) s.getUpdatedExpr(); + AstElementWithIndexes withIndexes = (AstElementWithIndexes) s.getUpdatedExpr(); + if (withIndexes.getIndexes().size() != 1) { + return null; + } + NameLink link = left.attrNameLink(); + if (link == null || link.getTyp() instanceof WurstTypeArray) { + return null; + } + FuncLink setOverload = AttrFuncDef.getIndexSetOperator( + left, + link.getTyp(), + withIndexes.getIndexes().get(0).attrTyp(), + s.getRight().attrTyp()); + if (setOverload == null) { + return null; + } + if (link instanceof OtherLink || !(link.getDef() instanceof VarDef)) { + throw new CompileError(s.getSource(), "Could not resolve assignment receiver for overloaded [] assignment."); + } + VarDef varDef = (VarDef) link.getDef(); + ImVar receiverVar = t.getVarFor(varDef); + ImExpr receiver; + if (left.attrImplicitParameter() instanceof Expr) { + Expr implicit = (Expr) left.attrImplicitParameter(); + receiver = JassIm.ImMemberAccess(left, implicit.imTranslateExpr(t, f), JassIm.ImTypeArguments(), receiverVar, JassIm.ImExprs()); + } else { + receiver = ImVarAccess(receiverVar); + } + ImExpr index = withIndexes.getIndexes().get(0).imTranslateExpr(t, f); + ImExpr value = s.getRight().imTranslateExpr(t, f); + ImFunction calledFunc = t.getFuncFor(setOverload.getDef()); + return ImFunctionCall(s, calledFunc, ImTypeArguments(), ImExprs(receiver, index, value), false, CallType.NORMAL); + } + public static ImStmt translate(StmtWhile s, ImTranslator t, ImFunction f) { List body = Lists.newArrayList(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java index eadfc08b7..304988317 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java @@ -7,6 +7,7 @@ import de.peeeq.wurstscript.attributes.CompileError; import de.peeeq.wurstscript.attributes.ImplicitFuncs; import de.peeeq.wurstscript.attributes.OverloadingResolver; +import de.peeeq.wurstscript.attributes.AttrFuncDef; import de.peeeq.wurstscript.attributes.names.DefLink; import de.peeeq.wurstscript.attributes.names.FuncLink; import de.peeeq.wurstscript.attributes.names.NameLink; @@ -1198,10 +1199,40 @@ private void checkStmtSet(StmtSet s) { .addError("Invalid assignment. This is not a variable, this is a " + nameLink); return; } - - WurstType leftType = s.getUpdatedExpr().attrTyp(); WurstType rightType = s.getRight().attrTyp(); - + if (s.getUpdatedExpr() instanceof NameRef && s.getUpdatedExpr() instanceof AstElementWithIndexes) { + NameRef left = (NameRef) s.getUpdatedExpr(); + AstElementWithIndexes leftWithIndex = (AstElementWithIndexes) s.getUpdatedExpr(); + WurstType targetType = nameLink.getTyp(); + if (!(targetType instanceof WurstTypeArray)) { + if (leftWithIndex.getIndexes().size() != 1) { + s.addError("Only one index is supported for overloaded [] assignment."); + return; + } + FuncLink setOverload = AttrFuncDef.getIndexSetOperator( + left, + targetType, + leftWithIndex.getIndexes().get(0).attrTyp(), + rightType); + if (setOverload == null) { + s.addError("No operator overloading function for [] assignment was found for receiver type " + + targetType + ". The overloading function has to be named: " + AttrFuncDef.overloadingIndexSet); + return; + } + WurstType indexType = leftWithIndex.getIndexes().get(0).attrTyp(); + WurstType expectedIndexType = setOverload.getParameterType(0); + if (!indexType.isSubtypeOf(expectedIndexType, s)) { + s.addError("Index expression has type " + indexType + + " but overloaded [] assignment expects " + expectedIndexType + "."); + return; + } + checkAssignment(Utils.isJassCode(s), s, setOverload.getParameterType(1), rightType); + checkIfAssigningToConstant(s.getUpdatedExpr()); + checkIfNoEffectAssignment(s); + return; + } + } + WurstType leftType = s.getUpdatedExpr().attrTyp(); checkAssignment(Utils.isJassCode(s), s, leftType, rightType); checkIfAssigningToConstant(s.getUpdatedExpr()); @@ -2644,9 +2675,12 @@ private void checkConstructor(ConstructorDef d) { private void checkArrayAccess(ExprVarArrayAccess ea) { checkNameRefDeprecated(ea, ea.tryGetNameDef()); - for (Expr index : ea.getIndexes()) { - if (!(index.attrTyp().isSubtypeOf(WurstTypeInt.instance(), ea))) { - index.addError("Arrayindices have to be of type int"); + NameLink nameLink = ea.attrNameLink(); + if (nameLink != null && nameLink.getTyp() instanceof WurstTypeArray) { + for (Expr index : ea.getIndexes()) { + if (!(index.attrTyp().isSubtypeOf(WurstTypeInt.instance(), ea))) { + index.addError("Arrayindices have to be of type int"); + } } } } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java index 7b0dd0973..4cbcc9697 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java @@ -95,6 +95,39 @@ public void configAnnotationJumpsToConfigurableVar() { testGetDef(testData, "1:22-1:42"); } + @Test + public void indexReadOperatorOnBracketJumpsToOverload() { + CompletionTestData testData = input( + "package test", + " class ListLike", + " function op_index(int i) returns int", + " return i", + " init", + " ListLike l = new ListLike()", + " int x = l|[1]", + "endpackage" + ); + + testGetDef(testData, "2:17-2:25"); + } + + @Test + public void indexWriteOperatorOnBracketJumpsToOverload() { + CompletionTestData testData = input( + "package test", + " class ListLike", + " function op_index(int i) returns int", + " return i", + " function op_indexAssign(int i, int v)", + " init", + " ListLike l = new ListLike()", + " l|[1] = 2", + "endpackage" + ); + + testGetDef(testData, "4:17-4:31"); + } + private void testGetDef(CompletionTestData testData, String... expectedPositions) { testGetDef(testData, Arrays.asList(expectedPositions)); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OpOverloading.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OpOverloading.java index b11a1a91a..0c27bec6b 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OpOverloading.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OpOverloading.java @@ -182,5 +182,117 @@ public void testOverloading_shortForm2() { "endpackage"); } + @Test + public void testOverloading_indexRead() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class ListLike", + " function op_index(int i) returns int", + " return i + 5", + " init", + " ListLike l = new ListLike()", + " int x = l[3]", + " if x == 8", + " testSuccess()", + "endpackage"); + } + + @Test + public void testOverloading_indexReadMissing() { + testAssertErrorsLines(true, "op_index", + "package test", + " class ListLike", + " init", + " ListLike l = new ListLike()", + " int x = l[3]", + "endpackage"); + } + + @Test + public void testOverloading_indexWrite() { + testAssertOkLines(true, + "package test", + " native testSuccess()", + " class ListLike", + " int x = 0", + " function op_index(int i) returns int", + " return x + i", + " function op_indexAssign(int i, int v)", + " x = v + i", + " init", + " ListLike l = new ListLike()", + " l[1] = 2", + " if l[1] == 4", + " testSuccess()", + "endpackage"); + } + + @Test + public void testOverloading_indexWriteMissingSetter() { + testAssertErrorsLines(true, "op_indexAssign", + "package test", + " class ListLike", + " function op_index(int i) returns int", + " return i", + " init", + " ListLike l = new ListLike()", + " l[1] = 2", + "endpackage"); + } + + @Test + public void testOverloading_indexReadWrongIndexType() { + testAssertErrorsLines(true, "op_index", + "package test", + " class ListLike", + " function op_index(int i) returns int", + " return i", + " init", + " ListLike l = new ListLike()", + " int x = l[\"x\"]", + "endpackage"); + } + + @Test + public void testOverloading_indexWriteWrongIndexType() { + testAssertErrorsLines(true, "op_indexAssign", + "package test", + " class ListLike", + " function op_index(int i) returns int", + " return i", + " function op_indexAssign(int i, int v)", + " init", + " ListLike l = new ListLike()", + " l[\"x\"] = 1", + "endpackage"); + } + + @Test + public void testOverloading_indexWriteOnlySetter() { + testAssertOkLines(false, + "package test", + " class ListLike", + " function op_indexAssign(int i, int v)", + " init", + " ListLike l = new ListLike()", + " l[1] = 2", + "endpackage"); + } + + @Test + public void testOverloading_memberIndexWriteOnlySetter() { + testAssertOkLines(false, + "package test", + " class ListLike", + " function op_indexAssign(int i, int v)", + " class Box", + " ListLike l = new ListLike()", + " init", + " Box b = new Box()", + " b.l[1] = 2", + "endpackage"); + } + } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/SimpleStatementTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/SimpleStatementTests.java index 7506b1aa8..e2149361b 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/SimpleStatementTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/SimpleStatementTests.java @@ -103,6 +103,31 @@ public void testContinueOutsideLoop() { ); } + @Test + public void testContinueAsVariableName() { + assertOk(true, + "int continue = 5", + "int x = continue", + "if x == 5", + " testSuccess()", + ""); + } + + @Test + public void testContinueKeywordAndVariableNameTogether() { + assertOk(true, + "int continue = 0", + "int sum = 0", + "while continue < 5", + " continue++", + " if continue mod 2 == 0", + " continue", + " sum += continue", + "if sum == 9", + " testSuccess()", + ""); + } + @Test public void testWhileContinue() { assertOk(true,