mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
compiler: add error for unnecessary use of 'var'
When a local variable is never used as an lvalue, we can determine that `const` would be sufficient for this variable, so emit an error in this case. More sophisticated checking is unfortunately not possible with Zig's current analysis model, since whether an lvalue is actually mutated depends on semantic analysis, in which some code paths may not be analyzed, so attempting to determine this would result in false positive compile errors. It's worth noting that an unfortunate consequence of this is that any field call `a.b()` will allow `a` to be `var`, even if `b` does not take a pointer as its first parameter - this is again a necessary compromise because the parameter type is not known until semantic analysis. Also update `translate-c` to not trigger these errors. This is done by replacing the `_ = @TypeOf(x)` emitted with `_ = &x` - the reference there means that the local is permitted to be `var`. A similar strategy will be used to prevent compile errors in the behavior tests, where we sometimes want to force a value to be runtime-known. Resolves: #224
This commit is contained in:
parent
325e0f5f0e
commit
baabc6013e
2 changed files with 28 additions and 9 deletions
|
|
@ -1226,7 +1226,7 @@ fn awaitExpr(
|
|||
try astgen.errNoteNode(gz.suspend_node, "suspend block here", .{}),
|
||||
});
|
||||
}
|
||||
const operand = try expr(gz, scope, .{ .rl = .none }, rhs_node);
|
||||
const operand = try expr(gz, scope, .{ .rl = .ref }, rhs_node);
|
||||
const result = if (gz.nosuspend_node != 0)
|
||||
try gz.addExtendedPayload(.await_nosuspend, Zir.Inst.UnNode{
|
||||
.node = gz.nodeIndexToRelative(node),
|
||||
|
|
@ -1248,7 +1248,7 @@ fn resumeExpr(
|
|||
const tree = astgen.tree;
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
const rhs_node = node_datas[node].lhs;
|
||||
const operand = try expr(gz, scope, .{ .rl = .none }, rhs_node);
|
||||
const operand = try expr(gz, scope, .{ .rl = .ref }, rhs_node);
|
||||
const result = try gz.addUnNode(.@"resume", operand, node);
|
||||
return rvalue(gz, ri, result, node);
|
||||
}
|
||||
|
|
@ -2941,11 +2941,19 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v
|
|||
const s = scope.cast(Scope.LocalPtr).?;
|
||||
if (s.used == 0 and s.discarded == 0) {
|
||||
try astgen.appendErrorTok(s.token_src, "unused {s}", .{@tagName(s.id_cat)});
|
||||
} else if (s.used != 0 and s.discarded != 0) {
|
||||
} else {
|
||||
if (s.used != 0 and s.discarded != 0) {
|
||||
try astgen.appendErrorTokNotes(s.discarded, "pointless discard of {s}", .{@tagName(s.id_cat)}, &[_]u32{
|
||||
try gz.astgen.errNoteTok(s.used, "used here", .{}),
|
||||
try astgen.errNoteTok(s.used, "used here", .{}),
|
||||
});
|
||||
}
|
||||
if (s.id_cat == .@"local variable" and !s.used_as_lvalue) {
|
||||
try astgen.appendErrorTokNotes(s.token_src, "local variable is never mutated", .{}, &.{
|
||||
try astgen.errNoteTok(s.token_src, "consider using 'const'", .{}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scope = s.parent;
|
||||
},
|
||||
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
|
||||
|
|
@ -7579,7 +7587,10 @@ fn localVarRef(
|
|||
);
|
||||
|
||||
switch (ri.rl) {
|
||||
.ref, .ref_coerced_ty => return ptr_inst,
|
||||
.ref, .ref_coerced_ty => {
|
||||
local_ptr.used_as_lvalue = true;
|
||||
return ptr_inst;
|
||||
},
|
||||
else => {
|
||||
const loaded = try gz.addUnNode(.load, ptr_inst, ident);
|
||||
return rvalueNoCoercePreRef(gz, ri, loaded, ident);
|
||||
|
|
@ -10948,6 +10959,9 @@ const Scope = struct {
|
|||
/// Track the identifier where it is discarded, like this `_ = foo;`.
|
||||
/// 0 means never discarded.
|
||||
discarded: Ast.TokenIndex = 0,
|
||||
/// Whether this value is used as an lvalue after inititialization.
|
||||
/// If not, we know it can be `const`, so will emit a compile error if it is `var`.
|
||||
used_as_lvalue: bool = false,
|
||||
/// String table index.
|
||||
name: u32,
|
||||
id_cat: IdCat,
|
||||
|
|
|
|||
|
|
@ -1625,13 +1625,18 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
|
|||
});
|
||||
const main_token = try c.addToken(.equal, "=");
|
||||
if (payload.value.tag() == .identifier) {
|
||||
// Render as `_ = @TypeOf(foo);` to avoid tripping "pointless discard" error.
|
||||
// Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors.
|
||||
var addr_of_pl: Payload.UnOp = .{
|
||||
.base = .{ .tag = .address_of },
|
||||
.data = payload.value,
|
||||
};
|
||||
const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base };
|
||||
return c.addNode(.{
|
||||
.tag = .assign,
|
||||
.main_token = main_token,
|
||||
.data = .{
|
||||
.lhs = lhs,
|
||||
.rhs = try renderBuiltinCall(c, "@TypeOf", &.{payload.value}),
|
||||
.rhs = try renderNode(c, addr_of),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue