From 34eb9f18acc2370973adcc7be0d010d62300e9a9 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Sat, 9 Feb 2019 20:41:26 -0500
Subject: [PATCH 1/4] fix not updating debug info type of optional error sets
There's an unfortunate footgun in the current design of error sets.
The debug info type for every error set is the same as the debug info
type of the global error set, which is essentially an enum forward
declaration. The problem is that when we "replace" the forward
declaration with the final value, once we know all the possible errors,
we have to update the pointers of every error set.
So the footgun is that if you ever copy the debug info type of the
global error set, you have to add the address of the pointer to a list
of pointers that need to be updated once we "replace" the forward
declaration. I activated the footgun when I introduced the optimization
that `?anyerror` types are the same size as `anyerror` types (using 0 as
the null value), because I introduced a pointer copy of the global error
set debug info type, but forgot to add it to the list.
I'm sure that there is a better way to code this, which does not have
the footgun, but this commit contains only a fix, not a reworking of the
logic.
closes #1937
---
src/analyze.cpp | 3 +++
test/stage1/behavior/error.zig | 5 +++++
2 files changed, 8 insertions(+)
diff --git a/src/analyze.cpp b/src/analyze.cpp
index 0c493ebda1..970d1cc382 100644
--- a/src/analyze.cpp
+++ b/src/analyze.cpp
@@ -594,6 +594,9 @@ ZigType *get_optional_type(CodeGen *g, ZigType *child_type) {
// function types are technically pointers
entry->type_ref = child_type->type_ref;
entry->di_type = child_type->di_type;
+ if (entry->di_type == g->builtin_types.entry_global_error_set->di_type) {
+ g->error_di_types.append(&entry->di_type);
+ }
} else {
assert(child_type->di_type);
// create a struct with a boolean whether this is the null value
diff --git a/test/stage1/behavior/error.zig b/test/stage1/behavior/error.zig
index 265ddd9d6c..7d9dae7bdd 100644
--- a/test/stage1/behavior/error.zig
+++ b/test/stage1/behavior/error.zig
@@ -330,3 +330,8 @@ test "optional error set is the same size as error set" {
expect(S.returnsOptErrSet() == null);
comptime expect(S.returnsOptErrSet() == null);
}
+
+test "debug info for optional error set" {
+ const SomeError = error{Hello};
+ var a_local_variable: ?SomeError = null;
+}
From 31be1ddf09fafae270d9946a3f09aa25816dd153 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Sat, 9 Feb 2019 20:57:45 -0500
Subject: [PATCH 2/4] docs: add threadlocal keyword to grammar
---
doc/langref.html.in | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 2cd35c2f4e..dfea8e1e04 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -7870,7 +7870,7 @@ TopLevelComptime <- KEYWORD_comptime BlockExpr
TopLevelDecl
<- (KEYWORD_export / KEYWORD_extern STRINGLITERAL? / KEYWORD_inline)? FnProto (SEMICOLON / Block)
- / (KEYWORD_export / KEYWORD_extern STRINGLITERAL?)? VarDecl
+ / (KEYWORD_export / KEYWORD_extern STRINGLITERAL?)? KEYWORD_threadlocal? VarDecl
/ KEYWORD_use Expr SEMICOLON
FnProto <- FnCC? KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr)
@@ -8330,6 +8330,7 @@ KEYWORD_struct <- 'struct' end_of_word
KEYWORD_suspend <- 'suspend' end_of_word
KEYWORD_switch <- 'switch' end_of_word
KEYWORD_test <- 'test' end_of_word
+KEYWORD_threadlocal <- 'threadlocal' end_of_word
KEYWORD_true <- 'true' end_of_word
KEYWORD_try <- 'try' end_of_word
KEYWORD_undefined <- 'undefined' end_of_word
@@ -8350,7 +8351,7 @@ keyword <- KEYWORD_align / KEYWORD_and / KEYWORD_anyerror / KEYWORD_asm
/ KEYWORD_orelse / KEYWORD_packed / KEYWORD_promise / KEYWORD_pub
/ KEYWORD_resume / KEYWORD_return / KEYWORD_linksection
/ KEYWORD_stdcallcc / KEYWORD_struct / KEYWORD_suspend
- / KEYWORD_switch / KEYWORD_test / KEYWORD_true / KEYWORD_try
+ / KEYWORD_switch / KEYWORD_test / KEYWORD_threadlocal / KEYWORD_true / KEYWORD_try
/ KEYWORD_undefined / KEYWORD_union / KEYWORD_unreachable
/ KEYWORD_use / KEYWORD_var / KEYWORD_volatile / KEYWORD_while
{#header_close#}
From caf672c49586f1af5e3d41ae200aded991b8b0f7 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Sat, 9 Feb 2019 21:10:59 -0500
Subject: [PATCH 3/4] `@truncate`: comptime 0 when target type is 0 bits
also if the dest type is a comptime_int, then treat it
as an implicit cast.
also compile error for attempting to truncate undefined
closes #1568
---
doc/langref.html.in | 11 +++++++----
src/ir.cpp | 24 ++++++++++++++++--------
test/compile_errors.zig | 11 ++++++++++-
test/stage1/behavior/truncate.zig | 23 +++++++++++++++++++++++
4 files changed, 56 insertions(+), 13 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index dfea8e1e04..779eb6a31b 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -6381,14 +6381,14 @@ fn List(comptime T: type) type {
{#header_close#}
{#header_open|@truncate#}
- {#syntax#}@truncate(comptime T: type, integer) T{#endsyntax#}
+ {#syntax#}@truncate(comptime T: type, integer: var) T{#endsyntax#}
This function truncates bits from an integer type, resulting in a smaller
integer type.
- The following produces a crash in debug mode and undefined behavior in
- release mode:
+ The following produces a crash in {#link|Debug#} mode and {#link|Undefined Behavior#} in
+ {#link|ReleaseFast#} mode:
{#syntax#}const a: u16 = 0xabcd;
const b: u8 = u8(a);{#endsyntax#}
@@ -6402,7 +6402,10 @@ const b: u8 = @truncate(u8, a);
This function always truncates the significant bits of the integer, regardless
of endianness on the target platform.
-
+
+ If {#syntax#}T{#endsyntax#} is {#syntax#}comptime_int{#endsyntax#},
+ then this is semantically equivalent to an {#link|implicit cast|Implicit Casts#}.
+
{#header_close#}
{#header_open|@typeId#}
diff --git a/src/ir.cpp b/src/ir.cpp
index d87486bbdd..5d4013b4b9 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -18491,7 +18491,22 @@ static IrInstruction *ir_analyze_instruction_truncate(IrAnalyze *ira, IrInstruct
return ira->codegen->invalid_instruction;
}
- if (src_type->data.integral.bit_count == 0) {
+ if (dest_type->id == ZigTypeIdComptimeInt) {
+ return ir_implicit_cast(ira, target, dest_type);
+ }
+
+ if (instr_is_comptime(target)) {
+ ConstExprValue *val = ir_resolve_const(ira, target, UndefBad);
+ if (val == nullptr)
+ return ira->codegen->invalid_instruction;
+
+ IrInstruction *result = ir_const(ira, &instruction->base, dest_type);
+ bigint_truncate(&result->value.data.x_bigint, &val->data.x_bigint,
+ dest_type->data.integral.bit_count, dest_type->data.integral.is_signed);
+ return result;
+ }
+
+ if (src_type->data.integral.bit_count == 0 || dest_type->data.integral.bit_count == 0) {
IrInstruction *result = ir_const(ira, &instruction->base, dest_type);
bigint_init_unsigned(&result->value.data.x_bigint, 0);
return result;
@@ -18507,13 +18522,6 @@ static IrInstruction *ir_analyze_instruction_truncate(IrAnalyze *ira, IrInstruct
return ira->codegen->invalid_instruction;
}
- if (target->value.special == ConstValSpecialStatic) {
- IrInstruction *result = ir_const(ira, &instruction->base, dest_type);
- bigint_truncate(&result->value.data.x_bigint, &target->value.data.x_bigint,
- dest_type->data.integral.bit_count, dest_type->data.integral.is_signed);
- return result;
- }
-
IrInstruction *new_instruction = ir_build_truncate(&ira->new_irb, instruction->base.scope,
instruction->base.source_node, dest_type_value, target);
new_instruction->value.type = dest_type;
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
index de01a5ac45..b47cdf2ed1 100644
--- a/test/compile_errors.zig
+++ b/test/compile_errors.zig
@@ -1,6 +1,15 @@
const tests = @import("tests.zig");
pub fn addCases(cases: *tests.CompileErrorContext) void {
+ cases.addTest(
+ "@truncate undefined value",
+ \\export fn entry() void {
+ \\ var z = @truncate(u8, u16(undefined));
+ \\}
+ ,
+ ".tmp_source.zig:2:30: error: use of undefined value",
+ );
+
cases.addTest(
"return invalid type from test",
\\test "example" { return 1; }
@@ -3335,7 +3344,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
cases.add(
"truncate sign mismatch",
\\fn f() i8 {
- \\ const x: u32 = 10;
+ \\ var x: u32 = 10;
\\ return @truncate(i8, x);
\\}
\\
diff --git a/test/stage1/behavior/truncate.zig b/test/stage1/behavior/truncate.zig
index c195b64cbf..568346369f 100644
--- a/test/stage1/behavior/truncate.zig
+++ b/test/stage1/behavior/truncate.zig
@@ -6,3 +6,26 @@ test "truncate u0 to larger integer allowed and has comptime known result" {
const y = @truncate(u8, x);
comptime expect(y == 0);
}
+
+test "truncate.u0.literal" {
+ var z = @truncate(u0, 0);
+ expect(z == 0);
+}
+
+test "truncate.u0.const" {
+ const c0: usize = 0;
+ var z = @truncate(u0, c0);
+ expect(z == 0);
+}
+
+test "truncate.u0.var" {
+ var d: u8 = 2;
+ var z = @truncate(u0, d);
+ expect(z == 0);
+}
+
+test "truncate sign mismatch but comptime known so it works anyway" {
+ const x: u32 = 10;
+ var result = @truncate(i8, x);
+ expect(result == 10);
+}
From 2f9fedabf0805a47aba5c348e5369c1c28f6cf21 Mon Sep 17 00:00:00 2001
From: Jimmi HC
Date: Sun, 10 Feb 2019 12:43:49 +0100
Subject: [PATCH 4/4] testing.expectEqual use expected type as the type of
actual This allows for impl casts
---
std/testing.zig | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/std/testing.zig b/std/testing.zig
index ade6e8b0dd..bece76ee5c 100644
--- a/std/testing.zig
+++ b/std/testing.zig
@@ -24,11 +24,7 @@ pub fn expectError(expected_error: anyerror, actual_error_union: var) void {
/// equal, prints diagnostics to stderr to show exactly how they are not equal,
/// then aborts.
/// The types must match exactly.
-pub fn expectEqual(expected: var, actual: var) void {
- if (@typeOf(actual) != @typeOf(expected)) {
- @compileError("type mismatch. expected " ++ @typeName(@typeOf(expected)) ++ ", found " ++ @typeName(@typeOf(actual)));
- }
-
+pub fn expectEqual(expected: var, actual: @typeOf(expected)) void {
switch (@typeInfo(@typeOf(actual))) {
TypeId.NoReturn,
TypeId.BoundFn,