authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-31 18:05:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-31 18:05:37-07:00
log3cebaaad1ca16a9e0203ed8c1684d0ce72da9487
treea153e50f75fad0a1b6c683ad5c06e28e102ff848
parent08eedc962d8e2582db8fb5b4a50114f2913519fd

astgen: improved handling of coercion

GenZir struct now has rl_ty_inst field which tracks the result location type (if any) a block expects all of its results to be coerced to. Remove a redundant coercion on const local initialization with a specified type. Switch expressions, during elision of store_to_block_ptr instructions, now re-purpose them to be type coercion when the block has a type in the result location.

4 files changed, 227 insertions(+), 105 deletions(-)

src/AstGen.zig+65-22
...@@ -1415,6 +1415,7 @@ fn varDecl(...@@ -1415,6 +1415,7 @@ fn varDecl(
1415 const type_inst = try typeExpr(gz, &init_scope.base, var_decl.ast.type_node);1415 const type_inst = try typeExpr(gz, &init_scope.base, var_decl.ast.type_node);
1416 opt_type_inst = type_inst;1416 opt_type_inst = type_inst;
1417 init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node);1417 init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node);
1418 init_scope.rl_ty_inst = type_inst;
1418 } else {1419 } else {
1419 const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node);1420 const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node);
1420 resolve_inferred_alloc = alloc;1421 resolve_inferred_alloc = alloc;
...@@ -1441,20 +1442,13 @@ fn varDecl(...@@ -1441,20 +1442,13 @@ fn varDecl(
1441 parent_zir.appendAssumeCapacity(src_inst);1442 parent_zir.appendAssumeCapacity(src_inst);
1442 }1443 }
1443 assert(parent_zir.items.len == expected_len);1444 assert(parent_zir.items.len == expected_len);
1444 const casted_init = if (opt_type_inst != .none)
1445 try gz.addPlNode(.as_node, var_decl.ast.type_node, zir.Inst.As{
1446 .dest_type = opt_type_inst,
1447 .operand = init_inst,
1448 })
1449 else
1450 init_inst;
14511445
1452 const sub_scope = try block_arena.create(Scope.LocalVal);1446 const sub_scope = try block_arena.create(Scope.LocalVal);
1453 sub_scope.* = .{1447 sub_scope.* = .{
1454 .parent = scope,1448 .parent = scope,
1455 .gen_zir = gz,1449 .gen_zir = gz,
1456 .name = ident_name,1450 .name = ident_name,
1457 .inst = casted_init,1451 .inst = init_inst,
1458 .src = name_src,1452 .src = name_src,
1459 };1453 };
1460 return &sub_scope.base;1454 return &sub_scope.base;
...@@ -3029,25 +3023,48 @@ fn switchExpr(...@@ -3029,25 +3023,48 @@ fn switchExpr(
3029 // all prongs, except for prongs that ended with a noreturn instruction.3023 // all prongs, except for prongs that ended with a noreturn instruction.
3030 // Elide all the `store_to_block_ptr` instructions.3024 // Elide all the `store_to_block_ptr` instructions.
30313025
3026 // The break instructions need to have their operands coerced if the
3027 // switch's result location is a `ty`. In this case we overwrite the
3028 // `store_to_block_ptr` instruction with an `as` instruction and repurpose
3029 // it as the break operand.
3030
3032 var extra_index: usize = 0;3031 var extra_index: usize = 0;
3033 extra_index += 2;3032 extra_index += 2;
3034 extra_index += @boolToInt(multi_cases_len != 0);3033 extra_index += @boolToInt(multi_cases_len != 0);
3035 if (special_prong != .none) {3034 if (special_prong != .none) special_prong: {
3036 const body_len_index = extra_index;3035 const body_len_index = extra_index;
3037 const body_len = scalar_cases_payload.items[extra_index];3036 const body_len = scalar_cases_payload.items[extra_index];
3038 extra_index += 1;3037 extra_index += 1;
3038 if (body_len < 2) {
3039 extra_index += body_len;
3040 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
3041 break :special_prong;
3042 }
3039 extra_index += body_len - 2;3043 extra_index += body_len - 2;
3040 const store_inst = scalar_cases_payload.items[extra_index];3044 const store_inst = scalar_cases_payload.items[extra_index];
3041 if (zir_tags[store_inst] == .store_to_block_ptr) {3045 if (zir_tags[store_inst] != .store_to_block_ptr) {
3042 assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);3046 extra_index += 2;
3043 scalar_cases_payload.items[body_len_index] -= 1;
3044 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);3047 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
3048 break :special_prong;
3049 }
3050 assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
3051 if (block_scope.rl_ty_inst != .none) {
3045 extra_index += 1;3052 extra_index += 1;
3046 astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);3053 const break_inst = scalar_cases_payload.items[extra_index];
3047 extra_index += 1;3054 extra_index += 1;
3055 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
3056 zir_tags[store_inst] = .as;
3057 zir_datas[store_inst].bin = .{
3058 .lhs = block_scope.rl_ty_inst,
3059 .rhs = zir_datas[break_inst].@"break".operand,
3060 };
3061 zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
3048 } else {3062 } else {
3049 extra_index += 2;3063 scalar_cases_payload.items[body_len_index] -= 1;
3050 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);3064 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
3065 extra_index += 1;
3066 astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
3067 extra_index += 1;
3051 }3068 }
3052 } else {3069 } else {
3053 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);3070 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
...@@ -3066,16 +3083,29 @@ fn switchExpr(...@@ -3066,16 +3083,29 @@ fn switchExpr(
3066 }3083 }
3067 extra_index += body_len - 2;3084 extra_index += body_len - 2;
3068 const store_inst = scalar_cases_payload.items[extra_index];3085 const store_inst = scalar_cases_payload.items[extra_index];
3069 if (zir_tags[store_inst] == .store_to_block_ptr) {3086 if (zir_tags[store_inst] != .store_to_block_ptr) {
3087 extra_index += 2;
3088 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
3089 continue;
3090 }
3091 if (block_scope.rl_ty_inst != .none) {
3092 extra_index += 1;
3093 const break_inst = scalar_cases_payload.items[extra_index];
3094 extra_index += 1;
3095 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
3096 zir_tags[store_inst] = .as;
3097 zir_datas[store_inst].bin = .{
3098 .lhs = block_scope.rl_ty_inst,
3099 .rhs = zir_datas[break_inst].@"break".operand,
3100 };
3101 zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
3102 } else {
3070 assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);3103 assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
3071 scalar_cases_payload.items[body_len_index] -= 1;3104 scalar_cases_payload.items[body_len_index] -= 1;
3072 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);3105 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
3073 extra_index += 1;3106 extra_index += 1;
3074 astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);3107 astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
3075 extra_index += 1;3108 extra_index += 1;
3076 } else {
3077 extra_index += 2;
3078 astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
3079 }3109 }
3080 }3110 }
3081 extra_index = 0;3111 extra_index = 0;
...@@ -3098,16 +3128,29 @@ fn switchExpr(...@@ -3098,16 +3128,29 @@ fn switchExpr(
3098 }3128 }
3099 extra_index += body_len - 2;3129 extra_index += body_len - 2;
3100 const store_inst = multi_cases_payload.items[extra_index];3130 const store_inst = multi_cases_payload.items[extra_index];
3101 if (zir_tags[store_inst] == .store_to_block_ptr) {3131 if (zir_tags[store_inst] != .store_to_block_ptr) {
3132 extra_index += 2;
3133 astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
3134 continue;
3135 }
3136 if (block_scope.rl_ty_inst != .none) {
3137 extra_index += 1;
3138 const break_inst = multi_cases_payload.items[extra_index];
3139 extra_index += 1;
3140 astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
3141 zir_tags[store_inst] = .as;
3142 zir_datas[store_inst].bin = .{
3143 .lhs = block_scope.rl_ty_inst,
3144 .rhs = zir_datas[break_inst].@"break".operand,
3145 };
3146 zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
3147 } else {
3102 assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);3148 assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
3103 multi_cases_payload.items[body_len_index] -= 1;3149 multi_cases_payload.items[body_len_index] -= 1;
3104 astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);3150 astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
3105 extra_index += 1;3151 extra_index += 1;
3106 astgen.extra.appendAssumeCapacity(multi_cases_payload.items[extra_index]);3152 astgen.extra.appendAssumeCapacity(multi_cases_payload.items[extra_index]);
3107 extra_index += 1;3153 extra_index += 1;
3108 } else {
3109 extra_index += 2;
3110 astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
3111 }3154 }
3112 }3155 }
31133156
src/Module.zig+8-1
...@@ -942,6 +942,8 @@ pub const Scope = struct {...@@ -942,6 +942,8 @@ pub const Scope = struct {
942 break_result_loc: AstGen.ResultLoc = undefined,942 break_result_loc: AstGen.ResultLoc = undefined,
943 /// When a block has a pointer result location, here it is.943 /// When a block has a pointer result location, here it is.
944 rl_ptr: zir.Inst.Ref = .none,944 rl_ptr: zir.Inst.Ref = .none,
945 /// When a block has a type result location, here it is.
946 rl_ty_inst: zir.Inst.Ref = .none,
945 /// Keeps track of how many branches of a block did not actually947 /// Keeps track of how many branches of a block did not actually
946 /// consume the result location. astgen uses this to figure out948 /// consume the result location. astgen uses this to figure out
947 /// whether to rely on break instructions or writing to the result949 /// whether to rely on break instructions or writing to the result
...@@ -1001,7 +1003,11 @@ pub const Scope = struct {...@@ -1001,7 +1003,11 @@ pub const Scope = struct {
1001 // we emit ZIR for the block break instructions to have the result values,1003 // we emit ZIR for the block break instructions to have the result values,
1002 // and then rvalue() on that to pass the value to the result location.1004 // and then rvalue() on that to pass the value to the result location.
1003 switch (parent_rl) {1005 switch (parent_rl) {
1004 .discard, .none, .ty, .ptr, .ref => {1006 .ty => |ty_inst| {
1007 gz.rl_ty_inst = ty_inst;
1008 gz.break_result_loc = parent_rl;
1009 },
1010 .discard, .none, .ptr, .ref => {
1005 gz.break_result_loc = parent_rl;1011 gz.break_result_loc = parent_rl;
1006 },1012 },
10071013
...@@ -1016,6 +1022,7 @@ pub const Scope = struct {...@@ -1016,6 +1022,7 @@ pub const Scope = struct {
1016 },1022 },
10171023
1018 .block_ptr => |parent_block_scope| {1024 .block_ptr => |parent_block_scope| {
1025 gz.rl_ty_inst = parent_block_scope.rl_ty_inst;
1019 gz.rl_ptr = parent_block_scope.rl_ptr;1026 gz.rl_ptr = parent_block_scope.rl_ptr;
1020 gz.break_result_loc = .{ .block_ptr = gz };1027 gz.break_result_loc = .{ .block_ptr = gz };
1021 },1028 },
src/Sema.zig+88-82
...@@ -4104,102 +4104,108 @@ fn coerce(...@@ -4104,102 +4104,108 @@ fn coerce(
4104 }4104 }
4105 assert(inst.ty.zigTypeTag() != .Undefined);4105 assert(inst.ty.zigTypeTag() != .Undefined);
41064106
4107 // null to ?T
4108 if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) {
4109 return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
4110 }
4111
4112 // T to ?T
4113 if (dest_type.zigTypeTag() == .Optional) {
4114 var buf: Type.Payload.ElemType = undefined;
4115 const child_type = dest_type.optionalChild(&buf);
4116 if (child_type.eql(inst.ty)) {
4117 return sema.wrapOptional(block, dest_type, inst);
4118 } else if (try sema.coerceNum(block, child_type, inst)) |some| {
4119 return sema.wrapOptional(block, dest_type, some);
4120 }
4121 }
4122
4123 // T to E!T or E to E!T4107 // T to E!T or E to E!T
4124 if (dest_type.tag() == .error_union) {4108 if (dest_type.tag() == .error_union) {
4125 return try sema.wrapErrorUnion(block, dest_type, inst);4109 return try sema.wrapErrorUnion(block, dest_type, inst);
4126 }4110 }
41274111
4128 // Coercions where the source is a single pointer to an array.
4129 src_array_ptr: {
4130 if (!inst.ty.isSinglePointer()) break :src_array_ptr;
4131 const array_type = inst.ty.elemType();
4132 if (array_type.zigTypeTag() != .Array) break :src_array_ptr;
4133 const array_elem_type = array_type.elemType();
4134 if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr;
4135 if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr;
4136
4137 const dst_elem_type = dest_type.elemType();
4138 switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
4139 .ok => {},
4140 .no_match => break :src_array_ptr,
4141 }
4142
4143 switch (dest_type.ptrSize()) {
4144 .Slice => {
4145 // *[N]T to []T
4146 return sema.coerceArrayPtrToSlice(block, dest_type, inst);
4147 },
4148 .C => {
4149 // *[N]T to [*c]T
4150 return sema.coerceArrayPtrToMany(block, dest_type, inst);
4151 },
4152 .Many => {
4153 // *[N]T to [*]T
4154 // *[N:s]T to [*:s]T
4155 const src_sentinel = array_type.sentinel();
4156 const dst_sentinel = dest_type.sentinel();
4157 if (src_sentinel == null and dst_sentinel == null)
4158 return sema.coerceArrayPtrToMany(block, dest_type, inst);
4159
4160 if (src_sentinel) |src_s| {
4161 if (dst_sentinel) |dst_s| {
4162 if (src_s.eql(dst_s)) {
4163 return sema.coerceArrayPtrToMany(block, dest_type, inst);
4164 }
4165 }
4166 }
4167 },
4168 .One => {},
4169 }
4170 }
4171
4172 // comptime known number to other number4112 // comptime known number to other number
4173 if (try sema.coerceNum(block, dest_type, inst)) |some|4113 if (try sema.coerceNum(block, dest_type, inst)) |some|
4174 return some;4114 return some;
41754115
4176 const target = sema.mod.getTarget();4116 const target = sema.mod.getTarget();
41774117
4178 // integer widening4118 switch (dest_type.zigTypeTag()) {
4179 if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {4119 .Optional => {
4180 assert(inst.value() == null); // handled above4120 // null to ?T
4121 if (inst.ty.zigTypeTag() == .Null) {
4122 return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
4123 }
41814124
4182 const src_info = inst.ty.intInfo(target);4125 // T to ?T
4183 const dst_info = dest_type.intInfo(target);4126 var buf: Type.Payload.ElemType = undefined;
4184 if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or4127 const child_type = dest_type.optionalChild(&buf);
4185 // small enough unsigned ints can get casted to large enough signed ints4128 if (child_type.eql(inst.ty)) {
4186 (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits))4129 return sema.wrapOptional(block, dest_type, inst);
4187 {4130 } else if (try sema.coerceNum(block, child_type, inst)) |some| {
4188 try sema.requireRuntimeBlock(block, inst_src);4131 return sema.wrapOptional(block, dest_type, some);
4189 return block.addUnOp(inst_src, dest_type, .intcast, inst);4132 }
4190 }4133 },
4191 }4134 .Pointer => {
4135 // Coercions where the source is a single pointer to an array.
4136 src_array_ptr: {
4137 if (!inst.ty.isSinglePointer()) break :src_array_ptr;
4138 const array_type = inst.ty.elemType();
4139 if (array_type.zigTypeTag() != .Array) break :src_array_ptr;
4140 const array_elem_type = array_type.elemType();
4141 if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr;
4142 if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr;
4143
4144 const dst_elem_type = dest_type.elemType();
4145 switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
4146 .ok => {},
4147 .no_match => break :src_array_ptr,
4148 }
41924149
4193 // float widening4150 switch (dest_type.ptrSize()) {
4194 if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) {4151 .Slice => {
4195 assert(inst.value() == null); // handled above4152 // *[N]T to []T
4153 return sema.coerceArrayPtrToSlice(block, dest_type, inst);
4154 },
4155 .C => {
4156 // *[N]T to [*c]T
4157 return sema.coerceArrayPtrToMany(block, dest_type, inst);
4158 },
4159 .Many => {
4160 // *[N]T to [*]T
4161 // *[N:s]T to [*:s]T
4162 const src_sentinel = array_type.sentinel();
4163 const dst_sentinel = dest_type.sentinel();
4164 if (src_sentinel == null and dst_sentinel == null)
4165 return sema.coerceArrayPtrToMany(block, dest_type, inst);
41964166
4197 const src_bits = inst.ty.floatBits(target);4167 if (src_sentinel) |src_s| {
4198 const dst_bits = dest_type.floatBits(target);4168 if (dst_sentinel) |dst_s| {
4199 if (dst_bits >= src_bits) {4169 if (src_s.eql(dst_s)) {
4200 try sema.requireRuntimeBlock(block, inst_src);4170 return sema.coerceArrayPtrToMany(block, dest_type, inst);
4201 return block.addUnOp(inst_src, dest_type, .floatcast, inst);4171 }
4202 }4172 }
4173 }
4174 },
4175 .One => {},
4176 }
4177 }
4178 },
4179 .Int => {
4180 // integer widening
4181 if (inst.ty.zigTypeTag() == .Int) {
4182 assert(inst.value() == null); // handled above
4183
4184 const dst_info = dest_type.intInfo(target);
4185 const src_info = inst.ty.intInfo(target);
4186 if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
4187 // small enough unsigned ints can get casted to large enough signed ints
4188 (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits))
4189 {
4190 try sema.requireRuntimeBlock(block, inst_src);
4191 return block.addUnOp(inst_src, dest_type, .intcast, inst);
4192 }
4193 }
4194 },
4195 .Float => {
4196 // float widening
4197 if (inst.ty.zigTypeTag() == .Float) {
4198 assert(inst.value() == null); // handled above
4199
4200 const src_bits = inst.ty.floatBits(target);
4201 const dst_bits = dest_type.floatBits(target);
4202 if (dst_bits >= src_bits) {
4203 try sema.requireRuntimeBlock(block, inst_src);
4204 return block.addUnOp(inst_src, dest_type, .floatcast, inst);
4205 }
4206 }
4207 },
4208 else => {},
4203 }4209 }
42044210
4205 return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty });4211 return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty });
test/stage2/cbe.zig+66
...@@ -279,6 +279,72 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -279,6 +279,72 @@ pub fn addCases(ctx: *TestContext) !void {
279 \\ return a - 4;279 \\ return a - 4;
280 \\}280 \\}
281 , "");281 , "");
282
283 // Switch expression missing else case.
284 case.addError(
285 \\export fn main() c_int {
286 \\ var cond: c_int = 0;
287 \\ const a: c_int = switch (cond) {
288 \\ 1 => 1,
289 \\ 2 => 2,
290 \\ 3 => 3,
291 \\ 4 => 4,
292 \\ };
293 \\ return a - 4;
294 \\}
295 , &.{":3:22: error: switch must handle all possibilities"});
296
297 // Switch expression, has an unreachable prong.
298 case.addCompareOutput(
299 \\export fn main() c_int {
300 \\ var cond: c_int = 0;
301 \\ const a: c_int = switch (cond) {
302 \\ 1 => 1,
303 \\ 2 => 2,
304 \\ 99...300, 12 => 3,
305 \\ 0 => 4,
306 \\ 13 => unreachable,
307 \\ else => 5,
308 \\ };
309 \\ return a - 4;
310 \\}
311 , "");
312
313 // Switch expression, has an unreachable prong and prongs write
314 // to result locations.
315 case.addCompareOutput(
316 \\export fn main() c_int {
317 \\ var cond: c_int = 0;
318 \\ var a: c_int = switch (cond) {
319 \\ 1 => 1,
320 \\ 2 => 2,
321 \\ 99...300, 12 => 3,
322 \\ 0 => 4,
323 \\ 13 => unreachable,
324 \\ else => 5,
325 \\ };
326 \\ return a - 4;
327 \\}
328 , "");
329
330 // Switch expression has duplicate case value.
331 case.addError(
332 \\export fn main() c_int {
333 \\ var cond: c_int = 0;
334 \\ const a: c_int = switch (cond) {
335 \\ 1 => 1,
336 \\ 2 => 2,
337 \\ 96, 11...13, 97 => 3,
338 \\ 0 => 4,
339 \\ 90, 12 => 100,
340 \\ else => 5,
341 \\ };
342 \\ return a - 4;
343 \\}
344 , &.{
345 ":8:13: error: duplicate switch value",
346 ":6:15: note: previous value here",
347 });
282 }348 }
283 //{349 //{
284 // var case = ctx.exeFromCompiledC("optionals", .{});350 // var case = ctx.exeFromCompiledC("optionals", .{});