From c1f7a31076b60df0a4de613247f976c2c1924a66 Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Tue, 24 Mar 2026 13:04:42 +0100 Subject: [PATCH] Language: split decl and cond in `IfStmt` * simplify code generation for `if` statement with declaration * support for optional separate decl and cond as per next C Standard * factor code for `Analyser.analyseCondition` for all conditions * more explicit error messages for condition decls without initializers * accept void expression in `for` init clause but reject init call because the code generator does not support this yet * add test cases --- analyser/module_analyser_stmt.c2 | 80 ++++++++++++++++------ analyser/module_analyser_switch.c2 | 20 +----- ast/ast_evaluator.c2 | 4 ++ ast/if_stmt.c2 | 69 ++++++++++++++----- ast/switch_stmt.c2 | 9 ++- common/ast_builder.c2 | 4 +- generator/ast_visitor.c2 | 6 +- generator/c/c_generator_stmt.c2 | 15 ++-- generator/c2i/c2i_generator_stmt.c2 | 19 +++-- generator/ir/ir_generator.c2 | 3 +- generator/ir/ir_generator_stmt.c2 | 22 +++--- parser/c2_parser_stmt.c2 | 19 ++++- test/init/init_call_bad_for.c2 | 5 +- test/parser/condition_decl_without_init.c2 | 10 ++- test/stmt/for_bad_init.c2 | 34 +++++++++ 15 files changed, 219 insertions(+), 100 deletions(-) create mode 100644 test/stmt/for_bad_init.c2 diff --git a/analyser/module_analyser_stmt.c2 b/analyser/module_analyser_stmt.c2 index ac64338f0..0dbda7add 100644 --- a/analyser/module_analyser_stmt.c2 +++ b/analyser/module_analyser_stmt.c2 @@ -251,27 +251,37 @@ fn FlowBits Analyser.analyseCompoundStmt(Analyser* ma, CompoundStmt* c) { return flow | flow2; // return FlowNext if last statement has FlowNext or empty block } -fn QualType Analyser.analyseCondition(Analyser* ma, Stmt** s_ptr, bool check_assign) { +fn QualType Analyser.analyseConditionDecl(Analyser* ma, Stmt** s_ptr, const char* context) { Stmt* s = *s_ptr; - if (s.isDecl()) { - QualType qt = ma.analyseDeclStmt(s); - if (qt.isValid()) { - DeclStmt* ds = (DeclStmt*)s; - VarDecl* vd = ds.getDecl(0); - if (!vd.getInit()) { - ma.error(vd.asDecl().getLoc(), "variable declaration in condition must have an initializer"); - return QualType_Invalid; - } + assert(s.isDecl()); + QualType qt = ma.analyseDeclStmt(s); + if (qt.isValid()) { + DeclStmt* ds = (DeclStmt*)s; + VarDecl* vd = ds.getDecl(0); + if (vd.hasInitCall()) { + ma.error(vd.asDecl().getLoc(), "variable declaration in %s cannot have an init call", context); + return QualType_Invalid; + } + if (!vd.hasInit()) { + ma.error(vd.asDecl().getLoc(), "variable declaration in %s must have an initializer", context); + return QualType_Invalid; } - return qt; } + return qt; +} +fn QualType Analyser.analyseCondition(Analyser* ma, Stmt** s_ptr, const char* context, bool check_assign, bool check_bool) { + Stmt* s = *s_ptr; + if (s.isDecl()) { + return ma.analyseConditionDecl(s_ptr, context); + } assert(s.isExpr()); QualType qt = ma.analyseExpr((Expr**)s_ptr, true, RHS); Expr* e = (Expr*)*s_ptr; - if (qt.isValid()) ma.checker.check(getBuiltinQT(Bool), qt, (Expr**)s_ptr, e.getLoc()); - e = (Expr*)*s_ptr; // re-read in case of ImplicitCast insertions - + if (check_bool) { + if (qt.isValid()) ma.checker.check(getBuiltinQT(Bool), qt, (Expr**)s_ptr, e.getLoc()); + e = (Expr*)*s_ptr; // re-read in case of ImplicitCast insertions + } if (check_assign && e.isAssignment()) { ma.warn(e.getLoc(), "using the result of an assignment as a condition without parentheses"); } @@ -284,8 +294,34 @@ fn FlowBits Analyser.analyseIfStmt(Analyser* ma, Stmt* s) { IfStmt* i = (IfStmt*)s; ma.scope.enter(scope.Decl); - ma.analyseCondition(i.getCond2(), true); - if (ma.has_error) goto done; + if (i.hasDecl()) { + QualType ct = ma.analyseConditionDecl(i.getDecl2(), "'if' condition"); + if (ct.isInvalid()) { + ma.scope.exit(ma.has_error); + return FlowNext | FlowError; + } + } + VarDecl* vd = nil; + Expr** condp = i.getCond2(); + if (!*condp) { + // Use declared variable as condition + DeclStmt* ds = (DeclStmt*)i.getDecl(); + assert(ds); + vd = ds.getDecl(0); + *condp = ma.builder.actOnIdentifier(0, vd.asDecl().getNameIdx(), 0); + } + QualType qt = ma.analyseExpr(condp, true, RHS); + if (qt.isInvalid()) goto done; + // reset the used bit so warning is emitted if variable is not used in the 'if' scope + if (vd) vd.asDecl().clearUsed(); + + Expr* e = *condp; + bool ok = ma.checker.check(getBuiltinQT(Bool), qt, condp, e.getLoc()); + e = *condp; // re-read in case of ImplicitCast insertions + if (e.isAssignment()) { + ma.warn(e.getLoc(), "using the result of an assignment as a condition without parentheses"); + } + if (!ok) goto done; // TODO: handle constant conditions ma.scope.enter(scope.Decl); @@ -293,10 +329,10 @@ fn FlowBits Analyser.analyseIfStmt(Analyser* ma, Stmt* s) { ma.scope.exit(ma.has_error); FlowBits flow2 = FlowNext; - Stmt** else_ = i.getElse2(); - if (else_) { + Stmt** else_stmt = i.getElse2(); + if (else_stmt) { ma.scope.enter(scope.Decl); - flow2 = ma.analyseStmt(else_, true); + flow2 = ma.analyseStmt(else_stmt, true); ma.scope.exit(ma.has_error); } flow |= flow2; @@ -312,7 +348,7 @@ fn FlowBits Analyser.analyseForStmt(Analyser* ma, Stmt* s) { ma.scope.enter(scope.Break | scope.Continue | scope.Decl | scope.Control); Stmt** init = f.getInit2(); if (init) { - QualType ct = ma.analyseCondition(init, false); + QualType ct = ma.analyseCondition(init, "'for' init clause", false, false); if (ct.isInvalid()) goto done; } @@ -352,8 +388,8 @@ fn FlowBits Analyser.analyseWhileStmt(Analyser* ma, Stmt* s) { WhileStmt* w = (WhileStmt*)s; ma.scope.enter(scope.Decl); - ma.analyseCondition(w.getCond2(), true); - if (ma.has_error) goto done; + QualType ct = ma.analyseCondition(w.getCond2(), "'while' condition", true, true); + if (ct.isInvalid()) goto done; Expr* cond = getCondExpr(w.getCond()); if (cond.isCtv()) { diff --git a/analyser/module_analyser_switch.c2 b/analyser/module_analyser_switch.c2 index adf20d43d..7b7b63bac 100644 --- a/analyser/module_analyser_switch.c2 +++ b/analyser/module_analyser_switch.c2 @@ -23,21 +23,6 @@ import string_buffer; import string; import value_type local; -fn QualType Analyser.analyseSwitchDecl(Analyser* ma, Stmt** s_ptr) { - Stmt* s = *s_ptr; - assert(s.isDecl()); - QualType qt = ma.analyseDeclStmt(s); - if (qt.isValid()) { - DeclStmt* ds = (DeclStmt*)s; - VarDecl* vd = ds.getDecl(0); - if (!vd.getInit()) { - ma.error(vd.asDecl().getLoc(), "variable declaration in 'switch' expression must have an initializer"); - return QualType_Invalid; - } - } - return qt; -} - fn FlowBits Analyser.analyseSwitchStmt(Analyser* ma, Stmt* s) { FlowBits flow = 0; SwitchStmt* sw = (SwitchStmt*)s; @@ -50,7 +35,7 @@ fn FlowBits Analyser.analyseSwitchStmt(Analyser* ma, Stmt* s) { if (sw.hasDecl()) { Stmt** declp = sw.getDecl2(); - QualType ct = ma.analyseSwitchDecl(declp); + QualType ct = ma.analyseConditionDecl(declp, "'switch' expression"); if (ct.isInvalid()) { ma.scope.exit(ma.has_error); return FlowNext | FlowError; @@ -216,7 +201,8 @@ fn FlowBits Analyser.analyseSwitchStmt(Analyser* ma, Stmt* s) { ct = ma.analyseExpr(&call, true, RHS); if (ct.isInvalid()) flow |= FlowError; else { - if (sw.hasCond()) { + if (sw.hasSourceCond()) { + // create alternate node for c2i generator Expr* alt = ma.builder.actOnAlternate(cond, call); // set alternate type to that of generated branch alt.setType(call.getType()); diff --git a/ast/ast_evaluator.c2 b/ast/ast_evaluator.c2 index a79181582..c24f19411 100644 --- a/ast/ast_evaluator.c2 +++ b/ast/ast_evaluator.c2 @@ -575,6 +575,10 @@ fn Cont Expr.eval(Expr* e, Evaluator* sf) { } fn Cont IfStmt.eval(IfStmt* s, Evaluator* sf) { + if (s.hasDecl()) { + Cont cont = s.getDecl().eval(sf); + if (cont != Normal) return cont; + } Cont cont = s.getCond().eval(sf); if (cont != Normal) return cont; if (!sf.result.isZero()) { diff --git a/ast/if_stmt.c2 b/ast/if_stmt.c2 index c23dd3f08..e1a308c48 100644 --- a/ast/if_stmt.c2 +++ b/ast/if_stmt.c2 @@ -21,28 +21,41 @@ import string_buffer; type IfStmtBits struct { u32 : NumStmtBits; + u32 has_decl : 1; + u32 has_source_cond : 1; u32 has_else : 1; } public type IfStmt struct @(opaque) { Stmt base; - Stmt* cond; + Expr* cond; Stmt* then; - Stmt*[0] else_stmt; // tail-allocated + Stmt*[0] tail; // tail-allocated: else/decl } -public fn IfStmt* IfStmt.create(ast_context.Context* c, SrcLoc loc, Stmt* cond, Stmt* then, Stmt* else_stmt) { - u32 size = sizeof(IfStmt); - if (else_stmt) size += sizeof(Stmt*); - +public fn IfStmt* IfStmt.create(ast_context.Context* c, + SrcLoc loc, + Stmt* decl, + Expr* cond, + Stmt* then, + Stmt* else_stmt) +{ + u32 has_decl = (decl != nil); + u32 has_else = (else_stmt != nil); + u32 size = sizeof(IfStmt) + (has_decl + has_else) * sizeof(Stmt*); IfStmt* s = c.alloc(size); s.base.init(If, loc); + s.base.ifStmtBits.has_source_cond = (cond != nil); s.cond = cond; s.then = then; - if (else_stmt) { + if (has_else) { s.base.ifStmtBits.has_else = 1; - s.else_stmt[0] = else_stmt; + s.tail[0] = else_stmt; + } + if (has_decl) { + s.base.ifStmtBits.has_decl = 1; + s.tail[has_else] = decl; } #if AstStatistics Stats.addStmt(If, size); @@ -51,36 +64,60 @@ public fn IfStmt* IfStmt.create(ast_context.Context* c, SrcLoc loc, Stmt* cond, } fn Stmt* IfStmt.instantiate(IfStmt* s, Instantiator* inst) { - Stmt* cond2 = s.cond.instantiate(inst); + Expr* cond2 = nil; + if (s.base.ifStmtBits.has_source_cond) cond2 = s.cond.instantiate(inst); Stmt* then2 = s.then.instantiate(inst); Stmt* else2 = nil; - if (s.base.ifStmtBits.has_else) else2 = s.else_stmt[0].instantiate(inst); + if (s.base.ifStmtBits.has_else) else2 = s.getElse().instantiate(inst); + Stmt* decl2 = nil; + if (s.base.ifStmtBits.has_decl) else2 = s.getDecl().instantiate(inst); - return (Stmt*)IfStmt.create(inst.c, s.base.loc, cond2, then2, else2); + return (Stmt*)IfStmt.create(inst.c, s.base.loc, decl2, cond2, then2, else2); } -public fn Stmt* IfStmt.getCond(const IfStmt* s) { return s.cond; } -public fn Stmt** IfStmt.getCond2(IfStmt* s) { return &s.cond; } +public fn bool IfStmt.hasDecl(const IfStmt* s) { return s.base.ifStmtBits.has_decl; } + +public fn Stmt* IfStmt.getDecl(const IfStmt* s) { + if (!s.base.ifStmtBits.has_decl) return nil; + return s.tail[s.base.ifStmtBits.has_else]; +} + +public fn Stmt** IfStmt.getDecl2(IfStmt* s) { + if (!s.base.ifStmtBits.has_decl) return nil; + return &s.tail[s.base.ifStmtBits.has_else]; +} + +public fn bool IfStmt.hasSourceCond(const IfStmt* s) { return s.base.ifStmtBits.has_source_cond; } + +public fn Expr* IfStmt.getCond(const IfStmt* s) { return s.cond; } +public fn Expr** IfStmt.getCond2(IfStmt* s) { return &s.cond; } public fn Stmt* IfStmt.getThen(const IfStmt* s) { return s.then; } public fn Stmt** IfStmt.getThen2(IfStmt* s) { return &s.then; } +public fn bool IfStmt.hasElse(const IfStmt* s) { return s.base.ifStmtBits.has_else; } public fn Stmt* IfStmt.getElse(const IfStmt* s) { - if (s.base.ifStmtBits.has_else) return s.else_stmt[0]; + if (s.base.ifStmtBits.has_else) return s.tail[0]; return nil; } public fn Stmt** IfStmt.getElse2(IfStmt* s) { - if (s.base.ifStmtBits.has_else) return &s.else_stmt[0]; + if (s.base.ifStmtBits.has_else) return &s.tail[0]; return nil; } fn void IfStmt.print(const IfStmt* s, string_buffer.Buf* out, u32 indent) { s.base.printKind(out, indent); + out.color(col_Attr); + if (s.hasDecl()) { + out.add(" decl"); + if (s.hasSourceCond()) out.add(" ; cond"); + } out.newline(); + if (s.hasDecl()) s.getDecl().print(out, indent + 1); s.cond.print(out, indent + 1); s.then.print(out, indent + 1); - if (s.base.ifStmtBits.has_else) s.else_stmt[0].print(out, indent + 1); + if (s.hasElse()) s.getElse().print(out, indent + 1); } diff --git a/ast/switch_stmt.c2 b/ast/switch_stmt.c2 index 6bc084185..09e8840fd 100644 --- a/ast/switch_stmt.c2 +++ b/ast/switch_stmt.c2 @@ -25,7 +25,7 @@ type SwitchStmtBits struct { u32 is_string : 1; u32 has_default : 1; u32 has_decl : 1; - u32 has_cond : 1; + u32 has_source_cond : 1; u32 num_cases : 32 - NumStmtBits - 4; } @@ -51,7 +51,7 @@ public fn SwitchStmt* SwitchStmt.create(ast_context.Context* c, s.base.switchStmtBits.has_default = has_default; s.base.switchStmtBits.num_cases = numCases; s.base.switchStmtBits.has_decl = has_decl; - s.base.switchStmtBits.has_cond = cond != nil; + s.base.switchStmtBits.has_source_cond = cond != nil; s.cond = cond; if (has_decl) s.decl[0] = decl; SwitchCase** cases_dest = (void*)(s.decl + has_decl); @@ -95,9 +95,8 @@ public fn Stmt** SwitchStmt.getDecl2(SwitchStmt* s) { return s.decl; } -// does the original code have a condition expression -public fn bool SwitchStmt.hasCond(const SwitchStmt* s) { - return s.base.switchStmtBits.has_cond; +public fn bool SwitchStmt.hasSourceCond(const SwitchStmt* s) { + return s.base.switchStmtBits.has_source_cond; } public fn Expr* SwitchStmt.getCond(const SwitchStmt* s) { diff --git a/common/ast_builder.c2 b/common/ast_builder.c2 index 5b82a7e72..245b17f76 100644 --- a/common/ast_builder.c2 +++ b/common/ast_builder.c2 @@ -995,8 +995,8 @@ public fn Stmt* Builder.actOnReturnStmt(Builder* b, SrcLoc loc, Expr* ret) { return (Stmt*)ReturnStmt.create(b.context, loc, ret); } -public fn Stmt* Builder.actOnIfStmt(Builder* b, SrcLoc loc, Stmt* cond, Stmt* then, Stmt* else_stmt) { - return (Stmt*)IfStmt.create(b.context, loc, cond, then, else_stmt); +public fn Stmt* Builder.actOnIfStmt(Builder* b, SrcLoc loc, Stmt* decl, Expr* cond, Stmt* then, Stmt* else_stmt) { + return (Stmt*)IfStmt.create(b.context, loc, decl, cond, then, else_stmt); } public fn Stmt* Builder.actOnWhileStmt(Builder* b, SrcLoc loc, Stmt* cond, Stmt* then) { diff --git a/generator/ast_visitor.c2 b/generator/ast_visitor.c2 index f7dc69076..04eb24970 100644 --- a/generator/ast_visitor.c2 +++ b/generator/ast_visitor.c2 @@ -151,10 +151,10 @@ fn void Visitor.handleStmt(Visitor* v, Stmt* s) { break; case If: IfStmt* i = (IfStmt*)s; - v.handleStmt(i.getCond()); + if (i.hasDecl()) v.handleStmt(i.getDecl()); + if (i.getCond()) v.handleExpr(i.getCond()); v.handleStmt(i.getThen()); - Stmt* e = i.getElse(); - if (e) v.handleStmt(e); + if (i.hasElse()) v.handleStmt(i.getElse()); break; case While: WhileStmt* w = (WhileStmt*)s; diff --git a/generator/c/c_generator_stmt.c2 b/generator/c/c_generator_stmt.c2 index 50571edff..d277b5857 100644 --- a/generator/c/c_generator_stmt.c2 +++ b/generator/c/c_generator_stmt.c2 @@ -67,22 +67,15 @@ fn void Generator.emitStmt(Generator* gen, string_buffer.Buf* out, Stmt* s, u32 break; case If: IfStmt* i = (IfStmt*)s; - Stmt* cond = i.getCond(); - bool is_decl = cond.isDecl(); + bool is_decl = i.hasDecl(); if (is_decl) { out.add("{\n"); indent++; - gen.emitStmt(out, cond, indent, true); + gen.emitStmt(out, i.getDecl(), indent, true); out.indent(indent); - out.add("if ("); - DeclStmt* ds = (DeclStmt*)cond; - VarDecl* vd = ds.getDecl(0); - gen.emitName(out, vd.asDecl()); - } else { - assert(cond.isExpr()); - out.add("if ("); - gen.emitExpr(out, (Expr*)cond); } + out.add("if ("); + gen.emitExpr(out, i.getCond()); out.add(") "); Stmt* thenStmt = i.getThen(); gen.emitStmt(out, thenStmt, indent, true); diff --git a/generator/c2i/c2i_generator_stmt.c2 b/generator/c2i/c2i_generator_stmt.c2 index fb8b51f95..0f8353929 100644 --- a/generator/c2i/c2i_generator_stmt.c2 +++ b/generator/c2i/c2i_generator_stmt.c2 @@ -46,7 +46,13 @@ fn void Generator.emitStmt(Generator* gen, Stmt* s, u32 indent, bool newline) { case If: IfStmt* i = (IfStmt*)s; out.add("if ("); - gen.emitStmt(i.getCond(), 0, false); + if (i.hasDecl()) { + gen.emitStmt(i.getDecl(), 0, false); + } + if (i.hasSourceCond()) { + if (i.hasDecl()) out.add("; "); + gen.emitExpr(out, i.getCond()); + } out.add(") "); Stmt* thenStmt = i.getThen(); gen.emitStmt(thenStmt, indent, true); @@ -237,12 +243,13 @@ fn void Generator.emitSwitchStmt(Generator* gen, SwitchStmt* sw, u32 indent) { string_buffer.Buf* out = gen.out; out.add("switch ("); - Stmt* decl = sw.getDecl(); - if (decl) { - gen.emitStmt(decl, 0, false); - if (sw.hasCond()) out.add("; "); + if (sw.hasDecl()) { + gen.emitStmt(sw.getDecl(), 0, false); + } + if (sw.hasSourceCond()) { + if (sw.hasDecl()) out.add("; "); + gen.emitExpr(out, sw.getCond()); } - if (sw.hasCond()) gen.emitExpr(out, sw.getCond()); out.add(") {\n"); const u32 num_cases = sw.getNumCases(); diff --git a/generator/ir/ir_generator.c2 b/generator/ir/ir_generator.c2 index 992aa7e1b..764a6fc0f 100644 --- a/generator/ir/ir_generator.c2 +++ b/generator/ir/ir_generator.c2 @@ -655,7 +655,8 @@ fn void Generator.collectLocalVars(Generator* gen, Stmt* s) { switch (s.getKind()) { case If: const IfStmt* if_stmt = (IfStmt*)s; - gen.collectLocalVars(if_stmt.getCond()); + Stmt* decl_stmt = if_stmt.getDecl(); + if (decl_stmt) gen.collectLocalVars(decl_stmt); Stmt* then_stmt = if_stmt.getThen(); if (then_stmt) gen.collectLocalVars(then_stmt); Stmt* else_stmt = if_stmt.getElse(); diff --git a/generator/ir/ir_generator_stmt.c2 b/generator/ir/ir_generator_stmt.c2 index ecfef5f10..81954afd4 100644 --- a/generator/ir/ir_generator_stmt.c2 +++ b/generator/ir/ir_generator_stmt.c2 @@ -18,6 +18,7 @@ module ir_generator; import ast local; import ir local; import ir_context; +import value_type local; // returns true if more stmts can follow fn bool Generator.emitStmt(Generator* gen, const Stmt* s) { @@ -118,15 +119,13 @@ fn void Generator.emitIfStmt(Generator* gen, const Stmt* s) { ir_context.Context* c = gen.ctx; const IfStmt* if_stmt = (IfStmt*)s; - const Stmt* cond = if_stmt.getCond(); + const Stmt* decl = if_stmt.getDecl(); + const Expr* cond = if_stmt.getCond(); const Stmt* then_stmt = if_stmt.getThen(); const Stmt* else_stmt = if_stmt.getElse(); -#if 0 - assert(cond.isExpr()); // TODO handle if (Decl* d = ..) .. - Expr* e = (Expr*)cond; - if (e.isCtv()) { - Value v = ast.evalExpr(e); + if (!decl && cond.isCtv()) { + Value v = ast.evalExpr(cond); if (v.isZero()) { if (else_stmt) gen.emitStmt(else_stmt); } else { @@ -134,7 +133,6 @@ fn void Generator.emitIfStmt(Generator* gen, const Stmt* s) { } return; } -#endif BlockId then_blk = c.createBlock(BlockKind.IfTrue); BlockId else_blk = 0; @@ -145,8 +143,8 @@ fn void Generator.emitIfStmt(Generator* gen, const Stmt* s) { if (else_stmt) false_blk = else_blk; // cond - if (cond.isDecl()) { - DeclStmt* ds = (DeclStmt*)cond; + if (decl && !if_stmt.hasSourceCond()) { + DeclStmt* ds = (DeclStmt*)decl; VarDecl* vd = ds.getDecl(0); Decl* d = (Decl*)vd; @@ -158,9 +156,9 @@ fn void Generator.emitIfStmt(Generator* gen, const Stmt* s) { ir.Type t = ir.Type.I32; // TODO get type gen.emitAssignCond(t, lhs, rhs, then_blk, join_blk, then_blk); } else { - assert(cond.isExpr()); - Expr* e = (Expr*)cond; - gen.emitCond(e, then_blk, false_blk, then_blk); + if (decl) gen.emitStmt(decl); + // TODO should check for CTV values + gen.emitCond(cond, then_blk, false_blk, then_blk); } // then-block diff --git a/parser/c2_parser_stmt.c2 b/parser/c2_parser_stmt.c2 index 8c50aca53..ebbe93bab 100644 --- a/parser/c2_parser_stmt.c2 +++ b/parser/c2_parser_stmt.c2 @@ -434,7 +434,22 @@ fn Stmt* Parser.parseIfStmt(Parser* p) { SrcLoc loc = p.tok.loc; p.consumeToken(); - Stmt* cond = p.parseCondition(); + p.expectAndConsume(LParen); + + Stmt* decl = nil; + Expr* cond = nil; + if (p.isDeclaration()) { + // !checkSemi, !allowLocal, isCondition + decl = p.parseDeclStmt(false, false, true); + if (p.tok.kind == Semicolon) { + p.consumeToken(); + cond = p.parseExpr(); + } + } else { + cond = p.parseExpr(); + } + p.expectAndConsume(RParen); + Stmt* then = p.parseStmt(); Stmt* else_stmt = nil; @@ -443,7 +458,7 @@ fn Stmt* Parser.parseIfStmt(Parser* p) { else_stmt = p.parseStmt(); } - return p.builder.actOnIfStmt(loc, cond, then, else_stmt); + return p.builder.actOnIfStmt(loc, decl, cond, then, else_stmt); } fn Stmt* Parser.parseReturnStmt(Parser* p) { diff --git a/test/init/init_call_bad_for.c2 b/test/init/init_call_bad_for.c2 index 91d6fea05..167a3efd0 100644 --- a/test/init/init_call_bad_for.c2 +++ b/test/init/init_call_bad_for.c2 @@ -8,7 +8,8 @@ type Foo struct { fn void Foo.init(Foo* f, i32 v) { f.v = v; } fn void test4() { - // Init call accepted in init part of for statement (not a condition) - for (Foo foo.init(1); false;) + // Init call should be accepted in init part of for statement (not a condition) + // but the Code generator does not support this yet + for (Foo foo.init(1); false;) // @error{variable declaration in 'for' init clause cannot have an init call} return; } diff --git a/test/parser/condition_decl_without_init.c2 b/test/parser/condition_decl_without_init.c2 index 839195e1d..fad805a87 100644 --- a/test/parser/condition_decl_without_init.c2 +++ b/test/parser/condition_decl_without_init.c2 @@ -1,7 +1,15 @@ module test; +public fn void foo() { + while (i32 a) {} // @error{variable declaration in 'while' condition must have an initializer} +} + +public fn void bar() { + for (i32 i; i < 10; i++) {} // @error{variable declaration in 'for' init clause must have an initializer} +} + public fn i32 main() { - if (i32 a) {} // @error{variable declaration in condition must have an initializer} + if (i32 a) {} // @error{variable declaration in 'if' condition must have an initializer} return 0; } diff --git a/test/stmt/for_bad_init.c2 b/test/stmt/for_bad_init.c2 new file mode 100644 index 000000000..8189b80dd --- /dev/null +++ b/test/stmt/for_bad_init.c2 @@ -0,0 +1,34 @@ +// @warnings{no-unused} +module test; + +// void function with an effect +fn void foo() { static u32 i; i++; } + +fn void bar1() { + // init clause: calling a void function is OK + for (foo();;) {} +} + +fn void bar2() { + // init clause: using an assignment is OK + i32 i; + for (i = 0;;) {} +} + +fn void bar3() { + // init clause: expression without an effect is not OK + i32 i = 0; + for (i;;) {} +} + +fn void bar4() { + // init clause: defining an uninitialized object is not OK + for (i32 i;;) {} // @error{variable declaration in 'for' init clause must have an initializer} +} + +type S struct { i32 x; } + +public fn i32 main() { + // init clause: defining an uninitialized object is not OK + for (S s;;) {} // @error{variable declaration in 'for' init clause must have an initializer} +}