Skip to content

Commit

Permalink
blocks check that their statements are void
Browse files Browse the repository at this point in the history
closes #291

This changes the error message "return value ignored" to "expression value is ignored".
This is because this error also applies to {1;}, which has no function calls.

Also fix ignored expression values in std and test.
This caught a bug in debug.readAllocBytes where an early Eof error would have been missed.
See #219.
  • Loading branch information
thejoshwolfe committed Apr 24, 2017
1 parent 6de33de commit c6605cb
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 22 deletions.
7 changes: 7 additions & 0 deletions src/all_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1738,6 +1738,7 @@ enum IrInstructionId {
IrInstructionIdIntToErr,
IrInstructionIdErrToInt,
IrInstructionIdCheckSwitchProngs,
IrInstructionIdCheckStatementIsVoid,
IrInstructionIdTestType,
IrInstructionIdTypeName,
IrInstructionIdCanImplicitCast,
Expand Down Expand Up @@ -2435,6 +2436,12 @@ struct IrInstructionCheckSwitchProngs {
size_t range_count;
};

struct IrInstructionCheckStatementIsVoid {
IrInstruction base;

IrInstruction *statement_value;
};

struct IrInstructionTestType {
IrInstruction base;

Expand Down
1 change: 1 addition & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
case IrInstructionIdTestComptime:
case IrInstructionIdGeneratedCode:
case IrInstructionIdCheckSwitchProngs:
case IrInstructionIdCheckStatementIsVoid:
case IrInstructionIdTestType:
case IrInstructionIdTypeName:
case IrInstructionIdCanImplicitCast:
Expand Down
61 changes: 48 additions & 13 deletions src/ir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckSwitchProng
return IrInstructionIdCheckSwitchProngs;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckStatementIsVoid *) {
return IrInstructionIdCheckStatementIsVoid;
}

static constexpr IrInstructionId ir_instruction_id(IrInstructionTestType *) {
return IrInstructionIdTestType;
}
Expand Down Expand Up @@ -2803,6 +2807,15 @@ static IrInstruction *ir_instruction_checkswitchprongs_get_dep(IrInstructionChec
return nullptr;
}

static IrInstruction *ir_instruction_checkstatementisvoid_get_dep(IrInstructionCheckStatementIsVoid *instruction,
size_t index)
{
switch (index) {
case 0: return instruction->statement_value;
default: return nullptr;
}
}

static IrInstruction *ir_instruction_testtype_get_dep(IrInstructionTestType *instruction, size_t index) {
switch (index) {
case 0: return instruction->type_value;
Expand Down Expand Up @@ -3060,6 +3073,8 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t
return ir_instruction_errtoint_get_dep((IrInstructionErrToInt *) instruction, index);
case IrInstructionIdCheckSwitchProngs:
return ir_instruction_checkswitchprongs_get_dep((IrInstructionCheckSwitchProngs *) instruction, index);
case IrInstructionIdCheckStatementIsVoid:
return ir_instruction_checkstatementisvoid_get_dep((IrInstructionCheckStatementIsVoid *) instruction, index);
case IrInstructionIdTestType:
return ir_instruction_testtype_get_dep((IrInstructionTestType *) instruction, index);
case IrInstructionIdTypeName:
Expand Down Expand Up @@ -3333,6 +3348,18 @@ static ScopeBlock *find_block_scope(IrExecutable *exec, Scope *scope) {
return nullptr;
}

static IrInstruction *ir_build_check_statement_is_void(IrBuilder *irb, Scope *scope, AstNode *source_node,
IrInstruction* statement_value)
{
IrInstructionCheckStatementIsVoid *instruction = ir_build_instruction<IrInstructionCheckStatementIsVoid>(
irb, scope, source_node);
instruction->statement_value = statement_value;

ir_ref_instruction(statement_value, irb->current_basic_block);

return &instruction->base;
}

static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode *block_node) {
assert(block_node->type == NodeTypeBlock);

Expand Down Expand Up @@ -3410,12 +3437,8 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode
return_value = statement_value;
} else {
// there are more statements ahead of this one. this statement's value must be void
TypeTableEntry *instruction_type = statement_value->value.type;
if (instruction_type &&
instruction_type->id != TypeTableEntryIdInvalid &&
instruction_type->id != TypeTableEntryIdVoid &&
instruction_type->id != TypeTableEntryIdUnreachable) {
add_node_error(irb->codegen, statement_node, buf_sprintf("expression valued ignored"));
if (statement_value != irb->codegen->invalid_instruction) {
ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, statement_node, statement_value));
}
}
}
Expand Down Expand Up @@ -12640,6 +12663,22 @@ static TypeTableEntry *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira
return ira->codegen->builtin_types.entry_void;
}

static TypeTableEntry *ir_analyze_instruction_check_statement_is_void(IrAnalyze *ira,
IrInstructionCheckStatementIsVoid *instruction)
{
IrInstruction *statement_value = instruction->statement_value;

This comment has been minimized.

Copy link
@andrewrk

andrewrk Apr 24, 2017

Member

I believe you want

IrInstruction *statement_value = instruction->statement_value->other;

here. This is how the pass1/pass2 reference each other.

This comment has been minimized.

Copy link
@thejoshwolfe

thejoshwolfe Apr 24, 2017

Author Contributor

fixed

TypeTableEntry *statement_type = statement_value->value.type;
if (type_is_invalid(statement_type))
return ira->codegen->builtin_types.entry_invalid;

if (statement_type->id != TypeTableEntryIdVoid) {
ir_add_error(ira, &instruction->base, buf_sprintf("expression value is ignored"));
}

ir_build_const_from(ira, &instruction->base);
return ira->codegen->builtin_types.entry_void;
}

static TypeTableEntry *ir_analyze_instruction_test_type(IrAnalyze *ira, IrInstructionTestType *instruction) {
IrInstruction *type_value = instruction->type_value->other;
TypeTableEntry *type_entry = ir_resolve_type(ira, type_value);
Expand Down Expand Up @@ -12987,6 +13026,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
return ir_analyze_instruction_test_comptime(ira, (IrInstructionTestComptime *)instruction);
case IrInstructionIdCheckSwitchProngs:
return ir_analyze_instruction_check_switch_prongs(ira, (IrInstructionCheckSwitchProngs *)instruction);
case IrInstructionIdCheckStatementIsVoid:
return ir_analyze_instruction_check_statement_is_void(ira, (IrInstructionCheckStatementIsVoid *)instruction);
case IrInstructionIdTestType:
return ir_analyze_instruction_test_type(ira, (IrInstructionTestType *)instruction);
case IrInstructionIdCanImplicitCast:
Expand Down Expand Up @@ -13026,13 +13067,6 @@ static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *ins
instruction_type->id == TypeTableEntryIdUnreachable);
instruction->other = instruction;
}
if (instruction_type->id != TypeTableEntryIdInvalid &&
instruction_type->id != TypeTableEntryIdVoid &&
instruction_type->id != TypeTableEntryIdUnreachable &&
instruction->ref_count == 0)
{
ir_add_error(ira, instruction, buf_sprintf("return value ignored"));
}

return instruction_type;
}
Expand Down Expand Up @@ -13123,6 +13157,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
case IrInstructionIdBreakpoint:
case IrInstructionIdOverflowOp: // TODO when we support multiple returns this can be side effect free
case IrInstructionIdCheckSwitchProngs:
case IrInstructionIdCheckStatementIsVoid:
case IrInstructionIdSetGlobalAlign:
case IrInstructionIdSetGlobalSection:
case IrInstructionIdSetGlobalLinkage:
Expand Down
9 changes: 9 additions & 0 deletions src/ir_print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,12 @@ static void ir_print_check_switch_prongs(IrPrint *irp, IrInstructionCheckSwitchP
fprintf(irp->f, ")");
}

static void ir_print_check_statement_is_void(IrPrint *irp, IrInstructionCheckStatementIsVoid *instruction) {
fprintf(irp->f, "@checkStatementIsVoid(");
ir_print_other_instruction(irp, instruction->statement_value);
fprintf(irp->f, ")");
}

static void ir_print_test_type(IrPrint *irp, IrInstructionTestType *instruction) {
fprintf(irp->f, "testtype ");
ir_print_other_instruction(irp, instruction->type_value);
Expand Down Expand Up @@ -1149,6 +1155,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
case IrInstructionIdCheckSwitchProngs:
ir_print_check_switch_prongs(irp, (IrInstructionCheckSwitchProngs *)instruction);
break;
case IrInstructionIdCheckStatementIsVoid:
ir_print_check_statement_is_void(irp, (IrInstructionCheckStatementIsVoid *)instruction);
break;
case IrInstructionIdTestType:
ir_print_test_type(irp, (IrInstructionTestType *)instruction);
break;
Expand Down
4 changes: 2 additions & 2 deletions std/buf_map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ pub const BufMap = struct {
test (self.hash_map.get(key)) |entry| {
const value_copy = %return self.copy(value);
%defer self.free(value_copy);
%return self.hash_map.put(key, value_copy);
_ = %return self.hash_map.put(key, value_copy);
self.free(entry.value);
} else {
const key_copy = %return self.copy(key);
%defer self.free(key_copy);
const value_copy = %return self.copy(value);
%defer self.free(value_copy);
%return self.hash_map.put(key_copy, value_copy);
_ = %return self.hash_map.put(key_copy, value_copy);
}
}

Expand Down
2 changes: 1 addition & 1 deletion std/buf_set.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub const BufSet = struct {
if (self.hash_map.get(key) == null) {
const key_copy = %return self.copy(key);
%defer self.free(key_copy);
%return self.hash_map.put(key_copy, {});
_ = %return self.hash_map.put(key_copy, {});
}
}

Expand Down
7 changes: 5 additions & 2 deletions std/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -415,20 +415,23 @@ pub const Builder = struct {
.value = UserValue.Scalar { value },
.used = false,
})) |*prev_value| {
// option already exists
switch (prev_value.value) {
UserValue.Scalar => |s| {
// turn it into a list
var list = List([]const u8).init(self.allocator);
%%list.append(s);
%%list.append(value);
%%self.user_input_options.put(name, UserInputOption {
_ = %%self.user_input_options.put(name, UserInputOption {
.name = name,
.value = UserValue.List { list },
.used = false,
});
},
UserValue.List => |*list| {
// append to the list
%%list.append(value);
%%self.user_input_options.put(name, UserInputOption {
_ = %%self.user_input_options.put(name, UserInputOption {
.name = name,
.value = UserValue.List { *list },
.used = false,
Expand Down
2 changes: 1 addition & 1 deletion std/debug.zig
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ fn getString(st: &ElfStackTrace, offset: u64) -> %[]u8 {
fn readAllocBytes(in_stream: &io.InStream, size: usize) -> %[]u8 {
const buf = %return global_allocator.alloc(u8, size);
%defer global_allocator.free(buf);
%return in_stream.read(buf);
if (size < %return in_stream.read(buf)) return error.Eof;

This comment has been minimized.

Copy link
@andrewrk

andrewrk Apr 24, 2017

Member

don't you want > instead of <?

This comment has been minimized.

Copy link
@thejoshwolfe

thejoshwolfe Apr 24, 2017

Author Contributor

yeah, i just reversed the order and used parens around the %return expression. clearer that way.

return buf;
}

Expand Down
2 changes: 1 addition & 1 deletion std/fmt.zig
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: u
}

test "testParseU64DigitTooBig" {
parseUnsigned(u64, "123a", 10) %% |err| {
_ = parseUnsigned(u64, "123a", 10) %% |err| {
if (err == error.InvalidChar) return;
unreachable;
};
Expand Down
5 changes: 4 additions & 1 deletion test/cases/switch_prong_err_enum.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ fn doThing(form_id: u64) -> %FormValue {
}

test "switchProngReturnsErrorEnum" {
%%doThing(17);
switch (%%doThing(17)) {
FormValue.Address => |payload| { assert(payload == 1); },
else => unreachable,
}
assert(read_count == 1);
}
2 changes: 1 addition & 1 deletion test/compile_errors.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1346,7 +1346,7 @@ pub fn addCases(cases: &tests.CompileErrorContext) {
\\ bar();
\\}
\\fn bar() -> i32 { 0 }
, ".tmp_source.zig:2:8: error: return value ignored");
, ".tmp_source.zig:2:8: error: expression value is ignored");

cases.add("integer literal on a non-comptime var",
\\export fn foo() {
Expand Down

2 comments on commit c6605cb

@andrewrk
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

want to add a compile error test?

@thejoshwolfe
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. i actually discovered a new bug by trying to add a test.

Please sign in to comment.