Skip to content

Commit 317aee1

Browse files
committed
constructor chaining
1 parent a3547b6 commit 317aee1

8 files changed

Lines changed: 444 additions & 22 deletions

File tree

de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ argumentList:
398398
;
399399

400400
exprFunctionCall:
401-
funcName=ID typeArgs argumentList
401+
funcName=(ID|THIS) typeArgs argumentList
402402
;
403403

404404
exprNewObject:'new' className=ID typeArgs argumentList?;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ public static FuncLink calculate(final ExprFuncRef node) {
166166

167167

168168
public static @Nullable FuncLink calculate(final ExprFunctionCall node) {
169+
if (isConstructorThisCall(node)) {
170+
return null;
171+
}
169172
FuncLink result = searchFunction(node.getFuncName(), node, argumentTypes(node));
170173

171174
if (result == null) {
@@ -181,6 +184,20 @@ public static FuncLink calculate(final ExprFuncRef node) {
181184
return result;
182185
}
183186

187+
private static boolean isConstructorThisCall(ExprFunctionCall node) {
188+
if (!node.getFuncName().equals("this")) {
189+
return false;
190+
}
191+
Element current = node;
192+
while (current != null) {
193+
if (current instanceof ConstructorDef) {
194+
return true;
195+
}
196+
current = current.getParent();
197+
}
198+
return false;
199+
}
200+
184201
private static @Nullable FuncLink getExtensionFunction(Expr left, Expr right, WurstOperator op) {
185202
String funcName = op.getOverloadingFuncName();
186203
if (funcName == null || nativeOperator(op, left.attrTyp(), right.attrTyp(), left)) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFunctionSignature.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ private static FunctionSignature filterSigs(
6464
Collection<FunctionSignature> sigs,
6565
List<WurstType> argTypes, StmtCall location) {
6666
if (sigs.isEmpty()) {
67+
if (location instanceof ExprFunctionCall && isConstructorThisCall((ExprFunctionCall) location)) {
68+
return FunctionSignature.empty;
69+
}
6770
if (!isInitTrigFunc(location)) {
6871
if (location instanceof ExprMemberMethodDot) {
6972
ExprMemberMethodDot emmd = (ExprMemberMethodDot) location;
@@ -117,6 +120,20 @@ private static FunctionSignature filterSigs(
117120
return candidates.get(0);
118121
}
119122

123+
private static boolean isConstructorThisCall(ExprFunctionCall call) {
124+
if (!call.getFuncName().equals("this")) {
125+
return false;
126+
}
127+
Element current = call;
128+
while (current != null) {
129+
if (current instanceof ConstructorDef) {
130+
return true;
131+
}
132+
current = current.getParent();
133+
}
134+
return false;
135+
}
136+
120137
private static List<FunctionSignature> filterByIfNotDefinedAnnotation(List<FunctionSignature> candidates) {
121138
List<FunctionSignature> list = new ArrayList<>();
122139
for (FunctionSignature sig : candidates) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/OverloadingResolver.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,5 +196,35 @@ void handleError(List<String> hints) {
196196
}.resolve(constructors, node).orElse(null);
197197
}
198198

199+
public static @Nullable ConstructorDef resolveThisCall(List<ConstructorDef> constructors, final FunctionCall node) {
200+
return new OverloadingResolver<ConstructorDef, FunctionCall>() {
201+
202+
@Override
203+
int getParameterCount(ConstructorDef f) {
204+
return f.getParameters().size();
205+
}
206+
207+
@Override
208+
WurstType getParameterType(ConstructorDef f, int i) {
209+
return f.getParameters().get(i).attrTyp();
210+
}
211+
212+
@Override
213+
int getArgumentCount(FunctionCall c) {
214+
return c.getArgs().size();
215+
}
216+
217+
@Override
218+
WurstType getArgumentType(FunctionCall c, int i) {
219+
return c.getArgs().get(i).attrTyp();
220+
}
221+
222+
@Override
223+
void handleError(List<String> hints) {
224+
node.addError("No suitable constructor found. \n" + Utils.join(hints, ", \n"));
225+
}
226+
}.resolve(constructors, node).orElse(null);
227+
}
228+
199229

200230
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/PossibleFuncDefs.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,26 @@ public static ImmutableCollection<FuncLink> calculate(final ExprMemberMethod nod
5050
}
5151

5252
public static ImmutableCollection<FuncLink> calculate(final ExprFunctionCall node) {
53+
if (isConstructorThisCall(node)) {
54+
return ImmutableList.of();
55+
}
5356
return searchFunction(node.getFuncName(), node);
5457
}
5558

59+
private static boolean isConstructorThisCall(ExprFunctionCall node) {
60+
if (!node.getFuncName().equals("this")) {
61+
return false;
62+
}
63+
Element current = node;
64+
while (current != null) {
65+
if (current instanceof ConstructorDef) {
66+
return true;
67+
}
68+
current = current.getParent();
69+
}
70+
return false;
71+
}
72+
5673
private static ImmutableCollection<FuncLink> getExtensionFunction(Expr left, Expr right, WurstOperator op) {
5774
String funcName = op.getOverloadingFuncName();
5875
if (funcName == null || nativeOperator(left.attrTyp(), right.attrTyp(), left)) {

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

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import de.peeeq.wurstscript.ast.*;
44
import de.peeeq.wurstscript.ast.Element;
5+
import de.peeeq.wurstscript.attributes.OverloadingResolver;
56
import de.peeeq.wurstscript.jassIm.Element.DefaultVisitor;
67
import de.peeeq.wurstscript.jassIm.*;
78
import de.peeeq.wurstscript.types.*;
@@ -382,36 +383,75 @@ private void createConstructFunc(ConstructorDef constr) {
382383
ConstructorDef trace = constr;
383384
ImFunction f = translator.getConstructFunc(constr);
384385
ImVar thisVar = translator.getThisVar(constr);
385-
ConstructorDef superConstr = constr.attrSuperConstructor();
386-
if (superConstr != null) {
387-
// call super constructor
388-
ImFunction superConstrFunc = translator.getConstructFunc(superConstr);
389-
ImExprs arguments = ImExprs(ImVarAccess(thisVar));
390-
for (Expr a : superArgs(constr)) {
391-
arguments.add(a.imTranslateExpr(translator, f));
386+
int firstRelevantIndex = firstRelevantStatementIndex(constr);
387+
ExprFunctionCall thisCall = null;
388+
if (firstRelevantIndex >= 0 && constr.getBody().get(firstRelevantIndex) instanceof ExprFunctionCall) {
389+
ExprFunctionCall first = (ExprFunctionCall) constr.getBody().get(firstRelevantIndex);
390+
if (first.getFuncName().equals("this")) {
391+
thisCall = first;
392392
}
393-
ImTypeArguments typeArgs = ImTypeArguments();
394-
ClassDef classDef = constr.attrNearestClassDef();
395-
assert classDef != null;
396-
WurstType extendedType = classDef.getExtendedClass().attrTyp();
397-
if (extendedType instanceof WurstTypeClass) {
398-
WurstTypeClass extendedTypeC = (WurstTypeClass) extendedType;
399-
for (WurstTypeBoundTypeParam bt : extendedTypeC.getTypeParameters()) {
400-
if (bt.isTemplateTypeParameter()) {
401-
typeArgs.add(bt.imTranslateToTypeArgument(translator));
393+
}
394+
int bodyStartIndex = 0;
395+
if (thisCall != null) {
396+
ConstructorDef calledConstr = OverloadingResolver.resolveThisCall(
397+
constr.attrNearestClassOrModule().getConstructors(),
398+
thisCall
399+
);
400+
if (calledConstr != null && calledConstr != constr) {
401+
ImFunction calledConstrFunc = translator.getConstructFunc(calledConstr);
402+
ImExprs arguments = ImExprs(ImVarAccess(thisVar));
403+
for (Expr a : thisCall.getArgs()) {
404+
arguments.add(a.imTranslateExpr(translator, f));
405+
}
406+
f.getBody().add(ImFunctionCall(trace, calledConstrFunc, classTypeArgs(), arguments, false, CallType.NORMAL));
407+
bodyStartIndex = firstRelevantIndex + 1;
408+
}
409+
} else {
410+
ConstructorDef superConstr = constr.attrSuperConstructor();
411+
if (superConstr != null) {
412+
// call super constructor
413+
ImFunction superConstrFunc = translator.getConstructFunc(superConstr);
414+
ImExprs arguments = ImExprs(ImVarAccess(thisVar));
415+
for (Expr a : superArgs(constr)) {
416+
arguments.add(a.imTranslateExpr(translator, f));
417+
}
418+
ImTypeArguments typeArgs = ImTypeArguments();
419+
ClassDef classDef = constr.attrNearestClassDef();
420+
assert classDef != null;
421+
WurstType extendedType = classDef.getExtendedClass().attrTyp();
422+
if (extendedType instanceof WurstTypeClass) {
423+
WurstTypeClass extendedTypeC = (WurstTypeClass) extendedType;
424+
for (WurstTypeBoundTypeParam bt : extendedTypeC.getTypeParameters()) {
425+
if (bt.isTemplateTypeParameter()) {
426+
typeArgs.add(bt.imTranslateToTypeArgument(translator));
427+
}
402428
}
403429
}
430+
f.getBody().add(ImFunctionCall(trace, superConstrFunc, typeArgs, arguments, false, CallType.NORMAL));
404431
}
405-
f.getBody().add(ImFunctionCall(trace, superConstrFunc, typeArgs, arguments, false, CallType.NORMAL));
432+
// call classInitFunc:
433+
f.getBody().add(ImFunctionCall(trace, classInitFunc, classTypeArgs(), JassIm.ImExprs(JassIm.ImVarAccess(thisVar)), false, CallType.NORMAL));
406434
}
407-
// call classInitFunc:
435+
// constructor user code
436+
f.getBody().addAll(translator.translateStatements(f, constr.getBody().subList(bodyStartIndex, constr.getBody().size())));
437+
}
438+
439+
private int firstRelevantStatementIndex(ConstructorDef constr) {
440+
for (int i = 0; i < constr.getBody().size(); i++) {
441+
WStatement s = constr.getBody().get(i);
442+
if (!(s instanceof StartFunctionStatement) && !(s instanceof EndFunctionStatement)) {
443+
return i;
444+
}
445+
}
446+
return -1;
447+
}
448+
449+
private ImTypeArguments classTypeArgs() {
408450
ImTypeArguments typeArguments = JassIm.ImTypeArguments();
409451
for (ImTypeVar tv : imClass.getTypeVariables()) {
410452
typeArguments.add(JassIm.ImTypeArgument(JassIm.ImTypeVarRef(tv), Collections.emptyMap()));
411453
}
412-
f.getBody().add(ImFunctionCall(trace, classInitFunc, typeArguments, JassIm.ImExprs(JassIm.ImVarAccess(thisVar)), false, CallType.NORMAL));
413-
// constructor user code
414-
f.getBody().addAll(translator.translateStatements(f, constr.getBody()));
454+
return typeArguments;
415455
}
416456

417457
private void translateClassInitFunc() {

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import de.peeeq.wurstscript.attributes.CofigOverridePackages;
77
import de.peeeq.wurstscript.attributes.CompileError;
88
import de.peeeq.wurstscript.attributes.ImplicitFuncs;
9+
import de.peeeq.wurstscript.attributes.OverloadingResolver;
910
import de.peeeq.wurstscript.attributes.names.DefLink;
1011
import de.peeeq.wurstscript.attributes.names.FuncLink;
1112
import de.peeeq.wurstscript.attributes.names.NameLink;
@@ -385,6 +386,7 @@ private void check(Element e) {
385386
checkPackageName((CompilationUnit) e);
386387
if (e instanceof ConstructorDef) {
387388
checkConstructor((ConstructorDef) e);
389+
checkThisConstructorCall((ConstructorDef) e);
388390
checkConstructorSuperCall((ConstructorDef) e);
389391
}
390392
if (e instanceof ExprBinary)
@@ -1613,6 +1615,9 @@ private void checkUninitializedVars(FunctionLike f) {
16131615

16141616

16151617
private void checkCall(StmtCall call) {
1618+
if (call instanceof ExprFunctionCall && isConstructorThisCall((ExprFunctionCall) call)) {
1619+
return;
1620+
}
16161621
String funcName;
16171622
if (call instanceof FunctionCall) {
16181623
FunctionCall fcall = (FunctionCall) call;
@@ -1725,6 +1730,9 @@ private void checkAnnotation(Annotation a) {
17251730

17261731
private void visit(ExprFunctionCall stmtCall) {
17271732
String funcName = stmtCall.getFuncName();
1733+
if (isConstructorThisCall(stmtCall)) {
1734+
return;
1735+
}
17281736
// calculating the exprType should reveal most errors:
17291737
stmtCall.attrTyp();
17301738

@@ -2051,6 +2059,9 @@ public VariableBinding case_TypeExprSimple(TypeExprSimple e) {
20512059

20522060
@Override
20532061
public VariableBinding case_ExprFunctionCall(ExprFunctionCall e) {
2062+
if (isConstructorThisCall(e)) {
2063+
return null;
2064+
}
20542065
return e.attrTyp().getTypeArgBinding();
20552066
}
20562067

@@ -2146,6 +2157,9 @@ public static boolean isTypeParamNewGeneric(TypeParamDef tp) {
21462157
}
21472158

21482159
private void checkFuncRef(FuncRef ref) {
2160+
if (isConstructorThisCall(ref)) {
2161+
return;
2162+
}
21492163
if (ref.getFuncName().isEmpty()) {
21502164
ref.addError("Missing function name.");
21512165
}
@@ -2417,12 +2431,17 @@ private void checkConstructor(ConstructorDef d) {
24172431
d.getParameters().addError("Module constructors must not have parameters.");
24182432
}
24192433
}
2434+
FunctionCall thisCall = getFirstThisConstructorCall(d);
24202435
StructureDef s = d.attrNearestStructureDef();
24212436
if (s instanceof ClassDef) {
24222437
ClassDef c = (ClassDef) s;
24232438
WurstTypeClass ct = c.attrTypC();
24242439
WurstTypeClass extendedClass = ct.extendedClass();
24252440
if (extendedClass != null) {
2441+
if (thisCall != null) {
2442+
// Delegating constructors call another constructor which then handles super().
2443+
return;
2444+
}
24262445
// Use the *bound* super-constructor signature
24272446
ConstructorDef sc = d.attrSuperConstructor();
24282447
if (sc == null) {
@@ -2886,6 +2905,78 @@ private void checkConstructorSuperCall(ConstructorDef c) {
28862905
}
28872906
}
28882907

2908+
private void checkThisConstructorCall(ConstructorDef c) {
2909+
FunctionCall firstThisCall = getFirstThisConstructorCall(c);
2910+
int firstRelevantIndex = firstRelevantStatementIndex(c);
2911+
for (int i = 0; i < c.getBody().size(); i++) {
2912+
WStatement s = c.getBody().get(i);
2913+
if (s instanceof FunctionCall) {
2914+
FunctionCall call = (FunctionCall) s;
2915+
if (isConstructorThisCall(call) && i != firstRelevantIndex) {
2916+
call.addError("Constructor call this(...) must be the first statement.");
2917+
}
2918+
}
2919+
}
2920+
if (firstThisCall == null) {
2921+
return;
2922+
}
2923+
if (c.getSuperConstructorCall() instanceof SomeSuperConstructorCall) {
2924+
c.addError("Cannot call super(...) and this(...) in the same constructor.");
2925+
return;
2926+
}
2927+
ClassOrModule owner = c.attrNearestClassOrModule();
2928+
if (owner == null) {
2929+
return;
2930+
}
2931+
ConstructorDef target = OverloadingResolver.resolveThisCall(owner.getConstructors(), firstThisCall);
2932+
if (target == c) {
2933+
firstThisCall.addError("Constructor cannot call itself using this(...).");
2934+
}
2935+
}
2936+
2937+
private @Nullable FunctionCall getFirstThisConstructorCall(ConstructorDef c) {
2938+
int i = firstRelevantStatementIndex(c);
2939+
if (i >= 0 && c.getBody().get(i) instanceof FunctionCall) {
2940+
FunctionCall call = (FunctionCall) c.getBody().get(i);
2941+
if (isConstructorThisCall(call)) {
2942+
return call;
2943+
}
2944+
}
2945+
return null;
2946+
}
2947+
2948+
private int firstRelevantStatementIndex(ConstructorDef c) {
2949+
for (int i = 0; i < c.getBody().size(); i++) {
2950+
WStatement s = c.getBody().get(i);
2951+
if (!(s instanceof StartFunctionStatement) && !(s instanceof EndFunctionStatement)) {
2952+
return i;
2953+
}
2954+
}
2955+
return -1;
2956+
}
2957+
2958+
private boolean isConstructorThisCall(FunctionCall call) {
2959+
return call.getFuncName().equals("this") && nearestEnclosingConstructor(call) != null;
2960+
}
2961+
2962+
private boolean isConstructorThisCall(FuncRef ref) {
2963+
if (ref instanceof FunctionCall) {
2964+
return isConstructorThisCall((FunctionCall) ref);
2965+
}
2966+
return false;
2967+
}
2968+
2969+
private @Nullable ConstructorDef nearestEnclosingConstructor(Element e) {
2970+
Element current = e;
2971+
while (current != null) {
2972+
if (current instanceof ConstructorDef) {
2973+
return (ConstructorDef) current;
2974+
}
2975+
current = current.getParent();
2976+
}
2977+
return null;
2978+
}
2979+
28892980
private void checkParameter(WParameter param) {
28902981
if (param.attrTyp() instanceof WurstTypeArray) {
28912982
param.addError("Cannot use arrays as parameters.");

0 commit comments

Comments
 (0)