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,