| author | |
| committer | |
| log | c6b3d06535f4227541c13fe75da347a485abdb4f |
| tree | 8f0a730e1dc7619168a81d2cd4521db1e7a1b4b6 |
| parent | 6df26a37d13d21be061a1cccd39dd17e46a81322 |
* C pointer types always have allowzero set to true but they omit the
word allowzero when printed.
* Implement coercion from C pointers to other pointers.
* Implement in-memory coercion for slices and pointer-like optionals.
* Make slicing a C pointer drop the allowzero bit.
* Value representation for pointer-like optionals is now allowed to use
pointer tag values in addition to the `opt_payload` tag.6 files changed, 339 insertions(+), 231 deletions(-)
src/Sema.zig+104-65| ... | @@ -9125,7 +9125,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr | ... | @@ -9125,7 +9125,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr |
| 9125 | .pointee_type = elem_type, | 9125 | .pointee_type = elem_type, |
| 9126 | .@"addrspace" = .generic, | 9126 | .@"addrspace" = .generic, |
| 9127 | .mutable = inst_data.is_mutable, | 9127 | .mutable = inst_data.is_mutable, |
| 9128 | .@"allowzero" = inst_data.is_allowzero, | 9128 | .@"allowzero" = inst_data.is_allowzero or inst_data.size == .C, |
| 9129 | .@"volatile" = inst_data.is_volatile, | 9129 | .@"volatile" = inst_data.is_volatile, |
| 9130 | .size = inst_data.size, | 9130 | .size = inst_data.size, |
| 9131 | }); | 9131 | }); |
| ... | @@ -9185,7 +9185,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air | ... | @@ -9185,7 +9185,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air |
| 9185 | .bit_offset = bit_start, | 9185 | .bit_offset = bit_start, |
| 9186 | .host_size = bit_end, | 9186 | .host_size = bit_end, |
| 9187 | .mutable = inst_data.flags.is_mutable, | 9187 | .mutable = inst_data.flags.is_mutable, |
| 9188 | .@"allowzero" = inst_data.flags.is_allowzero, | 9188 | .@"allowzero" = inst_data.flags.is_allowzero or inst_data.size == .C, |
| 9189 | .@"volatile" = inst_data.flags.is_volatile, | 9189 | .@"volatile" = inst_data.flags.is_volatile, |
| 9190 | .size = inst_data.size, | 9190 | .size = inst_data.size, |
| 9191 | }); | 9191 | }); |
| ... | @@ -12102,6 +12102,21 @@ fn coerce( | ... | @@ -12102,6 +12102,21 @@ fn coerce( |
| 12102 | } | 12102 | } |
| 12103 | } | 12103 | } |
| 12104 | 12104 | ||
| 12105 | // coercion from C pointer | ||
| 12106 | if (inst_ty.isCPtr()) src_c_ptr: { | ||
| 12107 | // In this case we must add a safety check because the C pointer | ||
| 12108 | // could be null. | ||
| 12109 | const src_elem_ty = inst_ty.childType(); | ||
| 12110 | const dest_is_mut = dest_info.mutable; | ||
| 12111 | const dst_elem_type = dest_info.pointee_type; | ||
| 12112 | switch (coerceInMemoryAllowed(dst_elem_type, src_elem_ty, dest_is_mut, target)) { | ||
| 12113 | .ok => {}, | ||
| 12114 | .no_match => break :src_c_ptr, | ||
| 12115 | } | ||
| 12116 | // TODO add safety check for null pointer | ||
| 12117 | return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); | ||
| 12118 | } | ||
| 12119 | |||
| 12105 | // coercion to C pointer | 12120 | // coercion to C pointer |
| 12106 | if (dest_info.size == .C) { | 12121 | if (dest_info.size == .C) { |
| 12107 | switch (inst_ty.zigTypeTag()) { | 12122 | switch (inst_ty.zigTypeTag()) { |
| ... | @@ -12262,84 +12277,107 @@ const InMemoryCoercionResult = enum { | ... | @@ -12262,84 +12277,107 @@ const InMemoryCoercionResult = enum { |
| 12262 | /// * sentinel-terminated pointers can coerce into `[*]` | 12277 | /// * sentinel-terminated pointers can coerce into `[*]` |
| 12263 | /// TODO improve this function to report recursive compile errors like it does in stage1. | 12278 | /// TODO improve this function to report recursive compile errors like it does in stage1. |
| 12264 | /// look at the function types_match_const_cast_only | 12279 | /// look at the function types_match_const_cast_only |
| 12265 | fn coerceInMemoryAllowed(dest_ty: Type, src_type: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult { | 12280 | fn coerceInMemoryAllowed(dest_ty: Type, src_ty: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult { |
| 12266 | if (dest_ty.eql(src_type)) | 12281 | if (dest_ty.eql(src_ty)) |
| 12267 | return .ok; | 12282 | return .ok; |
| 12268 | 12283 | ||
| 12269 | if (dest_ty.zigTypeTag() == .Pointer and | 12284 | // Pointers / Pointer-like Optionals |
| 12270 | src_type.zigTypeTag() == .Pointer) | 12285 | var dest_buf: Type.Payload.ElemType = undefined; |
| 12271 | { | 12286 | var src_buf: Type.Payload.ElemType = undefined; |
| 12272 | const dest_info = dest_ty.ptrInfo().data; | 12287 | if (dest_ty.ptrOrOptionalPtrTy(&dest_buf)) |dest_ptr_ty| { |
| 12273 | const src_info = src_type.ptrInfo().data; | 12288 | if (src_ty.ptrOrOptionalPtrTy(&src_buf)) |src_ptr_ty| { |
| 12274 | 12289 | return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ptr_ty, src_ptr_ty, dest_is_mut, target); | |
| 12275 | const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target); | ||
| 12276 | if (child == .no_match) { | ||
| 12277 | return child; | ||
| 12278 | } | 12290 | } |
| 12291 | } | ||
| 12279 | 12292 | ||
| 12280 | if (dest_info.@"addrspace" != src_info.@"addrspace") { | 12293 | // Slices |
| 12281 | return .no_match; | 12294 | if (dest_ty.isSlice() and src_ty.isSlice()) { |
| 12282 | } | 12295 | return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target); |
| 12296 | } | ||
| 12283 | 12297 | ||
| 12284 | const ok_sent = dest_info.sentinel == null or src_info.size == .C or | 12298 | // TODO: arrays |
| 12285 | (src_info.sentinel != null and | 12299 | // TODO: non-pointer-like optionals |
| 12286 | dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type)); | 12300 | // TODO: error unions |
| 12287 | if (!ok_sent) { | 12301 | // TODO: error sets |
| 12288 | return .no_match; | 12302 | // TODO: functions |
| 12289 | } | 12303 | // TODO: vectors |
| 12290 | 12304 | ||
| 12291 | const ok_ptr_size = src_info.size == dest_info.size or | 12305 | return .no_match; |
| 12292 | src_info.size == .C or dest_info.size == .C; | 12306 | } |
| 12293 | if (!ok_ptr_size) { | ||
| 12294 | return .no_match; | ||
| 12295 | } | ||
| 12296 | 12307 | ||
| 12297 | const ok_cv_qualifiers = | 12308 | fn coerceInMemoryAllowedPtrs( |
| 12298 | (src_info.mutable or !dest_info.mutable) and | 12309 | dest_ty: Type, |
| 12299 | (!src_info.@"volatile" or dest_info.@"volatile"); | 12310 | src_ty: Type, |
| 12311 | dest_ptr_ty: Type, | ||
| 12312 | src_ptr_ty: Type, | ||
| 12313 | dest_is_mut: bool, | ||
| 12314 | target: std.Target, | ||
| 12315 | ) InMemoryCoercionResult { | ||
| 12316 | const dest_info = dest_ptr_ty.ptrInfo().data; | ||
| 12317 | const src_info = src_ptr_ty.ptrInfo().data; | ||
| 12300 | 12318 | ||
| 12301 | if (!ok_cv_qualifiers) { | 12319 | const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target); |
| 12302 | return .no_match; | 12320 | if (child == .no_match) { |
| 12303 | } | 12321 | return child; |
| 12322 | } | ||
| 12304 | 12323 | ||
| 12305 | const ok_allows_zero = (dest_info.@"allowzero" and | 12324 | if (dest_info.@"addrspace" != src_info.@"addrspace") { |
| 12306 | (src_info.@"allowzero" or !dest_is_mut)) or | 12325 | return .no_match; |
| 12307 | (!dest_info.@"allowzero" and !src_info.@"allowzero"); | 12326 | } |
| 12308 | if (!ok_allows_zero) { | ||
| 12309 | return .no_match; | ||
| 12310 | } | ||
| 12311 | 12327 | ||
| 12312 | if (dest_ty.hasCodeGenBits() != src_type.hasCodeGenBits()) { | 12328 | const ok_sent = dest_info.sentinel == null or src_info.size == .C or |
| 12313 | return .no_match; | 12329 | (src_info.sentinel != null and |
| 12314 | } | 12330 | dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type)); |
| 12331 | if (!ok_sent) { | ||
| 12332 | return .no_match; | ||
| 12333 | } | ||
| 12315 | 12334 | ||
| 12316 | if (src_info.host_size != dest_info.host_size or | 12335 | const ok_ptr_size = src_info.size == dest_info.size or |
| 12317 | src_info.bit_offset != dest_info.bit_offset) | 12336 | src_info.size == .C or dest_info.size == .C; |
| 12318 | { | 12337 | if (!ok_ptr_size) { |
| 12319 | return .no_match; | 12338 | return .no_match; |
| 12320 | } | 12339 | } |
| 12321 | 12340 | ||
| 12322 | // If both pointers have alignment 0, it means they both want ABI alignment. | 12341 | const ok_cv_qualifiers = |
| 12323 | // In this case, if they share the same child type, no need to resolve | 12342 | (src_info.mutable or !dest_info.mutable) and |
| 12324 | // pointee type alignment. Otherwise both pointee types must have their alignment | 12343 | (!src_info.@"volatile" or dest_info.@"volatile"); |
| 12325 | // resolved and we compare the alignment numerically. | ||
| 12326 | if (src_info.@"align" != 0 or dest_info.@"align" != 0 or | ||
| 12327 | !dest_info.pointee_type.eql(src_info.pointee_type)) | ||
| 12328 | { | ||
| 12329 | const src_align = src_type.ptrAlignment(target); | ||
| 12330 | const dest_align = dest_ty.ptrAlignment(target); | ||
| 12331 | 12344 | ||
| 12332 | if (dest_align > src_align) { | 12345 | if (!ok_cv_qualifiers) { |
| 12333 | return .no_match; | 12346 | return .no_match; |
| 12334 | } | 12347 | } |
| 12335 | } | ||
| 12336 | 12348 | ||
| 12337 | return .ok; | 12349 | const dest_allow_zero = dest_ty.ptrAllowsZero(); |
| 12350 | const src_allow_zero = src_ty.ptrAllowsZero(); | ||
| 12351 | |||
| 12352 | const ok_allows_zero = (dest_allow_zero and | ||
| 12353 | (src_allow_zero or !dest_is_mut)) or | ||
| 12354 | (!dest_allow_zero and !src_allow_zero); | ||
| 12355 | if (!ok_allows_zero) { | ||
| 12356 | return .no_match; | ||
| 12338 | } | 12357 | } |
| 12339 | 12358 | ||
| 12340 | // TODO: implement more of this function | 12359 | if (src_info.host_size != dest_info.host_size or |
| 12360 | src_info.bit_offset != dest_info.bit_offset) | ||
| 12361 | { | ||
| 12362 | return .no_match; | ||
| 12363 | } | ||
| 12341 | 12364 | ||
| 12342 | return .no_match; | 12365 | // If both pointers have alignment 0, it means they both want ABI alignment. |
| 12366 | // In this case, if they share the same child type, no need to resolve | ||
| 12367 | // pointee type alignment. Otherwise both pointee types must have their alignment | ||
| 12368 | // resolved and we compare the alignment numerically. | ||
| 12369 | if (src_info.@"align" != 0 or dest_info.@"align" != 0 or | ||
| 12370 | !dest_info.pointee_type.eql(src_info.pointee_type)) | ||
| 12371 | { | ||
| 12372 | const src_align = src_info.@"align"; | ||
| 12373 | const dest_align = dest_info.@"align"; | ||
| 12374 | |||
| 12375 | if (dest_align > src_align) { | ||
| 12376 | return .no_match; | ||
| 12377 | } | ||
| 12378 | } | ||
| 12379 | |||
| 12380 | return .ok; | ||
| 12343 | } | 12381 | } |
| 12344 | 12382 | ||
| 12345 | fn coerceNum( | 12383 | fn coerceNum( |
| ... | @@ -13297,6 +13335,7 @@ fn analyzeSlice( | ... | @@ -13297,6 +13335,7 @@ fn analyzeSlice( |
| 13297 | const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len); | 13335 | const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len); |
| 13298 | 13336 | ||
| 13299 | const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data; | 13337 | const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data; |
| 13338 | const new_allowzero = new_ptr_ty_info.@"allowzero" and sema.typeOf(ptr).ptrSize() != .C; | ||
| 13300 | 13339 | ||
| 13301 | if (opt_new_len_val) |new_len_val| { | 13340 | if (opt_new_len_val) |new_len_val| { |
| 13302 | const new_len_int = new_len_val.toUnsignedInt(); | 13341 | const new_len_int = new_len_val.toUnsignedInt(); |
| ... | @@ -13312,7 +13351,7 @@ fn analyzeSlice( | ... | @@ -13312,7 +13351,7 @@ fn analyzeSlice( |
| 13312 | .@"align" = new_ptr_ty_info.@"align", | 13351 | .@"align" = new_ptr_ty_info.@"align", |
| 13313 | .@"addrspace" = new_ptr_ty_info.@"addrspace", | 13352 | .@"addrspace" = new_ptr_ty_info.@"addrspace", |
| 13314 | .mutable = new_ptr_ty_info.mutable, | 13353 | .mutable = new_ptr_ty_info.mutable, |
| 13315 | .@"allowzero" = new_ptr_ty_info.@"allowzero", | 13354 | .@"allowzero" = new_allowzero, |
| 13316 | .@"volatile" = new_ptr_ty_info.@"volatile", | 13355 | .@"volatile" = new_ptr_ty_info.@"volatile", |
| 13317 | .size = .One, | 13356 | .size = .One, |
| 13318 | }); | 13357 | }); |
| ... | @@ -13340,7 +13379,7 @@ fn analyzeSlice( | ... | @@ -13340,7 +13379,7 @@ fn analyzeSlice( |
| 13340 | .@"align" = new_ptr_ty_info.@"align", | 13379 | .@"align" = new_ptr_ty_info.@"align", |
| 13341 | .@"addrspace" = new_ptr_ty_info.@"addrspace", | 13380 | .@"addrspace" = new_ptr_ty_info.@"addrspace", |
| 13342 | .mutable = new_ptr_ty_info.mutable, | 13381 | .mutable = new_ptr_ty_info.mutable, |
| 13343 | .@"allowzero" = new_ptr_ty_info.@"allowzero", | 13382 | .@"allowzero" = new_allowzero, |
| 13344 | .@"volatile" = new_ptr_ty_info.@"volatile", | 13383 | .@"volatile" = new_ptr_ty_info.@"volatile", |
| 13345 | .size = .Slice, | 13384 | .size = .Slice, |
| 13346 | }); | 13385 | }); |
src/codegen/llvm.zig+2| ... | @@ -1184,6 +1184,8 @@ pub const DeclGen = struct { | ... | @@ -1184,6 +1184,8 @@ pub const DeclGen = struct { |
| 1184 | if (tv.ty.isPtrLikeOptional()) { | 1184 | if (tv.ty.isPtrLikeOptional()) { |
| 1185 | if (tv.val.castTag(.opt_payload)) |payload| { | 1185 | if (tv.val.castTag(.opt_payload)) |payload| { |
| 1186 | return self.genTypedValue(.{ .ty = payload_ty, .val = payload.data }); | 1186 | return self.genTypedValue(.{ .ty = payload_ty, .val = payload.data }); |
| 1187 | } else if (is_pl) { | ||
| 1188 | return self.genTypedValue(.{ .ty = payload_ty, .val = tv.val }); | ||
| 1187 | } else { | 1189 | } else { |
| 1188 | const llvm_ty = try self.llvmType(tv.ty); | 1190 | const llvm_ty = try self.llvmType(tv.ty); |
| 1189 | return llvm_ty.constNull(); | 1191 | return llvm_ty.constNull(); |
src/type.zig+72-15| ... | @@ -390,7 +390,7 @@ pub const Type = extern union { | ... | @@ -390,7 +390,7 @@ pub const Type = extern union { |
| 390 | .@"addrspace" = .generic, | 390 | .@"addrspace" = .generic, |
| 391 | .bit_offset = 0, | 391 | .bit_offset = 0, |
| 392 | .host_size = 0, | 392 | .host_size = 0, |
| 393 | .@"allowzero" = false, | 393 | .@"allowzero" = true, |
| 394 | .mutable = false, | 394 | .mutable = false, |
| 395 | .@"volatile" = false, | 395 | .@"volatile" = false, |
| 396 | .size = .C, | 396 | .size = .C, |
| ... | @@ -402,7 +402,7 @@ pub const Type = extern union { | ... | @@ -402,7 +402,7 @@ pub const Type = extern union { |
| 402 | .@"addrspace" = .generic, | 402 | .@"addrspace" = .generic, |
| 403 | .bit_offset = 0, | 403 | .bit_offset = 0, |
| 404 | .host_size = 0, | 404 | .host_size = 0, |
| 405 | .@"allowzero" = false, | 405 | .@"allowzero" = true, |
| 406 | .mutable = true, | 406 | .mutable = true, |
| 407 | .@"volatile" = false, | 407 | .@"volatile" = false, |
| 408 | .size = .C, | 408 | .size = .C, |
| ... | @@ -1153,7 +1153,7 @@ pub const Type = extern union { | ... | @@ -1153,7 +1153,7 @@ pub const Type = extern union { |
| 1153 | } | 1153 | } |
| 1154 | if (!payload.mutable) try writer.writeAll("const "); | 1154 | if (!payload.mutable) try writer.writeAll("const "); |
| 1155 | if (payload.@"volatile") try writer.writeAll("volatile "); | 1155 | if (payload.@"volatile") try writer.writeAll("volatile "); |
| 1156 | if (payload.@"allowzero") try writer.writeAll("allowzero "); | 1156 | if (payload.@"allowzero" and payload.size != .C) try writer.writeAll("allowzero "); |
| 1157 | 1157 | ||
| 1158 | ty = payload.pointee_type; | 1158 | ty = payload.pointee_type; |
| 1159 | continue; | 1159 | continue; |
| ... | @@ -2347,7 +2347,48 @@ pub const Type = extern union { | ... | @@ -2347,7 +2347,48 @@ pub const Type = extern union { |
| 2347 | } | 2347 | } |
| 2348 | } | 2348 | } |
| 2349 | 2349 | ||
| 2350 | /// Asserts that the type is an optional or a pointer that can be null. | 2350 | /// For pointer-like optionals, returns true, otherwise returns the allowzero property |
| 2351 | /// of pointers. | ||
| 2352 | pub fn ptrAllowsZero(ty: Type) bool { | ||
| 2353 | if (ty.isPtrLikeOptional()) { | ||
| 2354 | return true; | ||
| 2355 | } | ||
| 2356 | return ty.ptrInfo().data.@"allowzero"; | ||
| 2357 | } | ||
| 2358 | |||
| 2359 | /// For pointer-like optionals, it returns the pointer type. For pointers, | ||
| 2360 | /// the type is returned unmodified. | ||
| 2361 | pub fn ptrOrOptionalPtrTy(ty: Type, buf: *Payload.ElemType) ?Type { | ||
| 2362 | if (isPtrLikeOptional(ty)) return ty.optionalChild(buf); | ||
| 2363 | switch (ty.tag()) { | ||
| 2364 | .c_const_pointer, | ||
| 2365 | .c_mut_pointer, | ||
| 2366 | .single_const_pointer_to_comptime_int, | ||
| 2367 | .single_const_pointer, | ||
| 2368 | .single_mut_pointer, | ||
| 2369 | .many_const_pointer, | ||
| 2370 | .many_mut_pointer, | ||
| 2371 | .manyptr_u8, | ||
| 2372 | .manyptr_const_u8, | ||
| 2373 | => return ty, | ||
| 2374 | |||
| 2375 | .pointer => { | ||
| 2376 | if (ty.ptrSize() == .Slice) { | ||
| 2377 | return null; | ||
| 2378 | } else { | ||
| 2379 | return ty; | ||
| 2380 | } | ||
| 2381 | }, | ||
| 2382 | |||
| 2383 | .inferred_alloc_const => unreachable, | ||
| 2384 | .inferred_alloc_mut => unreachable, | ||
| 2385 | |||
| 2386 | else => return null, | ||
| 2387 | } | ||
| 2388 | } | ||
| 2389 | |||
| 2390 | /// Returns true if the type is optional and would be lowered to a single pointer | ||
| 2391 | /// address value, using 0 for null. Note that this returns true for C pointers. | ||
| 2351 | pub fn isPtrLikeOptional(self: Type) bool { | 2392 | pub fn isPtrLikeOptional(self: Type) bool { |
| 2352 | switch (self.tag()) { | 2393 | switch (self.tag()) { |
| 2353 | .optional_single_const_pointer, | 2394 | .optional_single_const_pointer, |
| ... | @@ -2371,7 +2412,8 @@ pub const Type = extern union { | ... | @@ -2371,7 +2412,8 @@ pub const Type = extern union { |
| 2371 | }, | 2412 | }, |
| 2372 | 2413 | ||
| 2373 | .pointer => return self.castTag(.pointer).?.data.size == .C, | 2414 | .pointer => return self.castTag(.pointer).?.data.size == .C, |
| 2374 | else => unreachable, | 2415 | |
| 2416 | else => return false, | ||
| 2375 | } | 2417 | } |
| 2376 | } | 2418 | } |
| 2377 | 2419 | ||
| ... | @@ -2532,38 +2574,50 @@ pub const Type = extern union { | ... | @@ -2532,38 +2574,50 @@ pub const Type = extern union { |
| 2532 | 2574 | ||
| 2533 | /// Asserts that the type is an optional. | 2575 | /// Asserts that the type is an optional. |
| 2534 | /// Resulting `Type` will have inner memory referencing `buf`. | 2576 | /// Resulting `Type` will have inner memory referencing `buf`. |
| 2535 | pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type { | 2577 | /// Note that for C pointers this returns the type unmodified. |
| 2536 | return switch (self.tag()) { | 2578 | pub fn optionalChild(ty: Type, buf: *Payload.ElemType) Type { |
| 2537 | .optional => self.castTag(.optional).?.data, | 2579 | return switch (ty.tag()) { |
| 2580 | .optional => ty.castTag(.optional).?.data, | ||
| 2538 | .optional_single_mut_pointer => { | 2581 | .optional_single_mut_pointer => { |
| 2539 | buf.* = .{ | 2582 | buf.* = .{ |
| 2540 | .base = .{ .tag = .single_mut_pointer }, | 2583 | .base = .{ .tag = .single_mut_pointer }, |
| 2541 | .data = self.castPointer().?.data, | 2584 | .data = ty.castPointer().?.data, |
| 2542 | }; | 2585 | }; |
| 2543 | return Type.initPayload(&buf.base); | 2586 | return Type.initPayload(&buf.base); |
| 2544 | }, | 2587 | }, |
| 2545 | .optional_single_const_pointer => { | 2588 | .optional_single_const_pointer => { |
| 2546 | buf.* = .{ | 2589 | buf.* = .{ |
| 2547 | .base = .{ .tag = .single_const_pointer }, | 2590 | .base = .{ .tag = .single_const_pointer }, |
| 2548 | .data = self.castPointer().?.data, | 2591 | .data = ty.castPointer().?.data, |
| 2549 | }; | 2592 | }; |
| 2550 | return Type.initPayload(&buf.base); | 2593 | return Type.initPayload(&buf.base); |
| 2551 | }, | 2594 | }, |
| 2595 | |||
| 2596 | .pointer, // here we assume it is a C pointer | ||
| 2597 | .c_const_pointer, | ||
| 2598 | .c_mut_pointer, | ||
| 2599 | => return ty, | ||
| 2600 | |||
| 2552 | else => unreachable, | 2601 | else => unreachable, |
| 2553 | }; | 2602 | }; |
| 2554 | } | 2603 | } |
| 2555 | 2604 | ||
| 2556 | /// Asserts that the type is an optional. | 2605 | /// Asserts that the type is an optional. |
| 2557 | /// Same as `optionalChild` but allocates the buffer if needed. | 2606 | /// Same as `optionalChild` but allocates the buffer if needed. |
| 2558 | pub fn optionalChildAlloc(self: Type, allocator: *Allocator) !Type { | 2607 | pub fn optionalChildAlloc(ty: Type, allocator: *Allocator) !Type { |
| 2559 | switch (self.tag()) { | 2608 | switch (ty.tag()) { |
| 2560 | .optional => return self.castTag(.optional).?.data, | 2609 | .optional => return ty.castTag(.optional).?.data, |
| 2561 | .optional_single_mut_pointer => { | 2610 | .optional_single_mut_pointer => { |
| 2562 | return Tag.single_mut_pointer.create(allocator, self.castPointer().?.data); | 2611 | return Tag.single_mut_pointer.create(allocator, ty.castPointer().?.data); |
| 2563 | }, | 2612 | }, |
| 2564 | .optional_single_const_pointer => { | 2613 | .optional_single_const_pointer => { |
| 2565 | return Tag.single_const_pointer.create(allocator, self.castPointer().?.data); | 2614 | return Tag.single_const_pointer.create(allocator, ty.castPointer().?.data); |
| 2566 | }, | 2615 | }, |
| 2616 | .pointer, // here we assume it is a C pointer | ||
| 2617 | .c_const_pointer, | ||
| 2618 | .c_mut_pointer, | ||
| 2619 | => return ty, | ||
| 2620 | |||
| 2567 | else => unreachable, | 2621 | else => unreachable, |
| 2568 | } | 2622 | } |
| 2569 | } | 2623 | } |
| ... | @@ -4050,6 +4104,9 @@ pub const Type = extern union { | ... | @@ -4050,6 +4104,9 @@ pub const Type = extern union { |
| 4050 | if (d.sentinel != null or d.@"align" != 0 or d.@"addrspace" != .generic or | 4104 | if (d.sentinel != null or d.@"align" != 0 or d.@"addrspace" != .generic or |
| 4051 | d.bit_offset != 0 or d.host_size != 0 or d.@"allowzero" or d.@"volatile") | 4105 | d.bit_offset != 0 or d.host_size != 0 or d.@"allowzero" or d.@"volatile") |
| 4052 | { | 4106 | { |
| 4107 | if (d.size == .C) { | ||
| 4108 | assert(d.@"allowzero"); // All C pointers must set allowzero to true. | ||
| 4109 | } | ||
| 4053 | return Type.Tag.pointer.create(arena, d); | 4110 | return Type.Tag.pointer.create(arena, d); |
| 4054 | } | 4111 | } |
| 4055 | 4112 |
src/value.zig+2-1| ... | @@ -1819,7 +1819,8 @@ pub const Value = extern union { | ... | @@ -1819,7 +1819,8 @@ pub const Value = extern union { |
| 1819 | .unreachable_value => unreachable, | 1819 | .unreachable_value => unreachable, |
| 1820 | .inferred_alloc => unreachable, | 1820 | .inferred_alloc => unreachable, |
| 1821 | .inferred_alloc_comptime => unreachable, | 1821 | .inferred_alloc_comptime => unreachable, |
| 1822 | else => unreachable, | 1822 | |
| 1823 | else => false, | ||
| 1823 | }; | 1824 | }; |
| 1824 | } | 1825 | } |
| 1825 | 1826 |
test/behavior/cast.zig+157| ... | @@ -120,3 +120,160 @@ fn incrementVoidPtrArray(array: ?*c_void, len: usize) void { | ... | @@ -120,3 +120,160 @@ fn incrementVoidPtrArray(array: ?*c_void, len: usize) void { |
| 120 | @ptrCast([*]u8, array.?)[n] += 1; | 120 | @ptrCast([*]u8, array.?)[n] += 1; |
| 121 | } | 121 | } |
| 122 | } | 122 | } |
| 123 | |||
| 124 | test "implicitly cast indirect pointer to maybe-indirect pointer" { | ||
| 125 | const S = struct { | ||
| 126 | const Self = @This(); | ||
| 127 | x: u8, | ||
| 128 | fn constConst(p: *const *const Self) u8 { | ||
| 129 | return p.*.x; | ||
| 130 | } | ||
| 131 | fn maybeConstConst(p: ?*const *const Self) u8 { | ||
| 132 | return p.?.*.x; | ||
| 133 | } | ||
| 134 | fn constConstConst(p: *const *const *const Self) u8 { | ||
| 135 | return p.*.*.x; | ||
| 136 | } | ||
| 137 | fn maybeConstConstConst(p: ?*const *const *const Self) u8 { | ||
| 138 | return p.?.*.*.x; | ||
| 139 | } | ||
| 140 | }; | ||
| 141 | const s = S{ .x = 42 }; | ||
| 142 | const p = &s; | ||
| 143 | const q = &p; | ||
| 144 | const r = &q; | ||
| 145 | try expect(42 == S.constConst(q)); | ||
| 146 | try expect(42 == S.maybeConstConst(q)); | ||
| 147 | try expect(42 == S.constConstConst(r)); | ||
| 148 | try expect(42 == S.maybeConstConstConst(r)); | ||
| 149 | } | ||
| 150 | |||
| 151 | test "@intCast comptime_int" { | ||
| 152 | const result = @intCast(i32, 1234); | ||
| 153 | try expect(@TypeOf(result) == i32); | ||
| 154 | try expect(result == 1234); | ||
| 155 | } | ||
| 156 | |||
| 157 | test "@floatCast comptime_int and comptime_float" { | ||
| 158 | { | ||
| 159 | const result = @floatCast(f16, 1234); | ||
| 160 | try expect(@TypeOf(result) == f16); | ||
| 161 | try expect(result == 1234.0); | ||
| 162 | } | ||
| 163 | { | ||
| 164 | const result = @floatCast(f16, 1234.0); | ||
| 165 | try expect(@TypeOf(result) == f16); | ||
| 166 | try expect(result == 1234.0); | ||
| 167 | } | ||
| 168 | { | ||
| 169 | const result = @floatCast(f32, 1234); | ||
| 170 | try expect(@TypeOf(result) == f32); | ||
| 171 | try expect(result == 1234.0); | ||
| 172 | } | ||
| 173 | { | ||
| 174 | const result = @floatCast(f32, 1234.0); | ||
| 175 | try expect(@TypeOf(result) == f32); | ||
| 176 | try expect(result == 1234.0); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | test "coerce undefined to optional" { | ||
| 181 | try expect(MakeType(void).getNull() == null); | ||
| 182 | try expect(MakeType(void).getNonNull() != null); | ||
| 183 | } | ||
| 184 | |||
| 185 | fn MakeType(comptime T: type) type { | ||
| 186 | return struct { | ||
| 187 | fn getNull() ?T { | ||
| 188 | return null; | ||
| 189 | } | ||
| 190 | |||
| 191 | fn getNonNull() ?T { | ||
| 192 | return @as(T, undefined); | ||
| 193 | } | ||
| 194 | }; | ||
| 195 | } | ||
| 196 | |||
| 197 | test "implicit cast from *[N]T to [*c]T" { | ||
| 198 | var x: [4]u16 = [4]u16{ 0, 1, 2, 3 }; | ||
| 199 | var y: [*c]u16 = &x; | ||
| 200 | |||
| 201 | try expect(std.mem.eql(u16, x[0..4], y[0..4])); | ||
| 202 | x[0] = 8; | ||
| 203 | y[3] = 6; | ||
| 204 | try expect(std.mem.eql(u16, x[0..4], y[0..4])); | ||
| 205 | } | ||
| 206 | |||
| 207 | test "*usize to *void" { | ||
| 208 | var i = @as(usize, 0); | ||
| 209 | var v = @ptrCast(*void, &i); | ||
| 210 | v.* = {}; | ||
| 211 | } | ||
| 212 | |||
| 213 | test "compile time int to ptr of function" { | ||
| 214 | try foobar(FUNCTION_CONSTANT); | ||
| 215 | } | ||
| 216 | |||
| 217 | pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize)); | ||
| 218 | pub const PFN_void = fn (*c_void) callconv(.C) void; | ||
| 219 | |||
| 220 | fn foobar(func: PFN_void) !void { | ||
| 221 | try std.testing.expect(@ptrToInt(func) == maxInt(usize)); | ||
| 222 | } | ||
| 223 | |||
| 224 | test "implicit ptr to *c_void" { | ||
| 225 | var a: u32 = 1; | ||
| 226 | var ptr: *align(@alignOf(u32)) c_void = &a; | ||
| 227 | var b: *u32 = @ptrCast(*u32, ptr); | ||
| 228 | try expect(b.* == 1); | ||
| 229 | var ptr2: ?*align(@alignOf(u32)) c_void = &a; | ||
| 230 | var c: *u32 = @ptrCast(*u32, ptr2.?); | ||
| 231 | try expect(c.* == 1); | ||
| 232 | } | ||
| 233 | |||
| 234 | test "@intToEnum passed a comptime_int to an enum with one item" { | ||
| 235 | const E = enum { A }; | ||
| 236 | const x = @intToEnum(E, 0); | ||
| 237 | try expect(x == E.A); | ||
| 238 | } | ||
| 239 | |||
| 240 | test "@intCast to u0 and use the result" { | ||
| 241 | const S = struct { | ||
| 242 | fn doTheTest(zero: u1, one: u1, bigzero: i32) !void { | ||
| 243 | try expect((one << @intCast(u0, bigzero)) == 1); | ||
| 244 | try expect((zero << @intCast(u0, bigzero)) == 0); | ||
| 245 | } | ||
| 246 | }; | ||
| 247 | try S.doTheTest(0, 1, 0); | ||
| 248 | comptime try S.doTheTest(0, 1, 0); | ||
| 249 | } | ||
| 250 | |||
| 251 | test "peer result null and comptime_int" { | ||
| 252 | const S = struct { | ||
| 253 | fn blah(n: i32) ?i32 { | ||
| 254 | if (n == 0) { | ||
| 255 | return null; | ||
| 256 | } else if (n < 0) { | ||
| 257 | return -1; | ||
| 258 | } else { | ||
| 259 | return 1; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | }; | ||
| 263 | |||
| 264 | try expect(S.blah(0) == null); | ||
| 265 | comptime try expect(S.blah(0) == null); | ||
| 266 | try expect(S.blah(10).? == 1); | ||
| 267 | comptime try expect(S.blah(10).? == 1); | ||
| 268 | try expect(S.blah(-10).? == -1); | ||
| 269 | comptime try expect(S.blah(-10).? == -1); | ||
| 270 | } | ||
| 271 | |||
| 272 | test "*const ?[*]const T to [*c]const [*c]const T" { | ||
| 273 | var array = [_]u8{ 'o', 'k' }; | ||
| 274 | const opt_array_ptr: ?[*]const u8 = &array; | ||
| 275 | const a: *const ?[*]const u8 = &opt_array_ptr; | ||
| 276 | const b: [*c]const [*c]const u8 = a; | ||
| 277 | try expect(b.*[0] == 'o'); | ||
| 278 | try expect(b[0][1] == 'k'); | ||
| 279 | } |
test/behavior/cast_stage1.zig+2-150| ... | @@ -5,33 +5,6 @@ const maxInt = std.math.maxInt; | ... | @@ -5,33 +5,6 @@ const maxInt = std.math.maxInt; |
| 5 | const Vector = std.meta.Vector; | 5 | const Vector = std.meta.Vector; |
| 6 | const native_endian = @import("builtin").target.cpu.arch.endian(); | 6 | const native_endian = @import("builtin").target.cpu.arch.endian(); |
| 7 | 7 | ||
| 8 | test "implicitly cast indirect pointer to maybe-indirect pointer" { | ||
| 9 | const S = struct { | ||
| 10 | const Self = @This(); | ||
| 11 | x: u8, | ||
| 12 | fn constConst(p: *const *const Self) u8 { | ||
| 13 | return p.*.x; | ||
| 14 | } | ||
| 15 | fn maybeConstConst(p: ?*const *const Self) u8 { | ||
| 16 | return p.?.*.x; | ||
| 17 | } | ||
| 18 | fn constConstConst(p: *const *const *const Self) u8 { | ||
| 19 | return p.*.*.x; | ||
| 20 | } | ||
| 21 | fn maybeConstConstConst(p: ?*const *const *const Self) u8 { | ||
| 22 | return p.?.*.*.x; | ||
| 23 | } | ||
| 24 | }; | ||
| 25 | const s = S{ .x = 42 }; | ||
| 26 | const p = &s; | ||
| 27 | const q = &p; | ||
| 28 | const r = &q; | ||
| 29 | try expect(42 == S.constConst(q)); | ||
| 30 | try expect(42 == S.maybeConstConst(q)); | ||
| 31 | try expect(42 == S.constConstConst(r)); | ||
| 32 | try expect(42 == S.maybeConstConstConst(r)); | ||
| 33 | } | ||
| 34 | |||
| 35 | test "explicit cast from integer to error type" { | 8 | test "explicit cast from integer to error type" { |
| 36 | try testCastIntToErr(error.ItBroke); | 9 | try testCastIntToErr(error.ItBroke); |
| 37 | comptime try testCastIntToErr(error.ItBroke); | 10 | comptime try testCastIntToErr(error.ItBroke); |
| ... | @@ -175,7 +148,7 @@ fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 { | ... | @@ -175,7 +148,7 @@ fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 { |
| 175 | return slice[0..1]; | 148 | return slice[0..1]; |
| 176 | } | 149 | } |
| 177 | 150 | ||
| 178 | test "implicit cast from &const [N]T to []const T" { | 151 | test "implicit cast from *const [N]T to []const T" { |
| 179 | try testCastConstArrayRefToConstSlice(); | 152 | try testCastConstArrayRefToConstSlice(); |
| 180 | comptime try testCastConstArrayRefToConstSlice(); | 153 | comptime try testCastConstArrayRefToConstSlice(); |
| 181 | } | 154 | } |
| ... | @@ -258,7 +231,7 @@ fn cast128Float(x: u128) f128 { | ... | @@ -258,7 +231,7 @@ fn cast128Float(x: u128) f128 { |
| 258 | return @bitCast(f128, x); | 231 | return @bitCast(f128, x); |
| 259 | } | 232 | } |
| 260 | 233 | ||
| 261 | test "single-item pointer of array to slice and to unknown length pointer" { | 234 | test "single-item pointer of array to slice to unknown length pointer" { |
| 262 | try testCastPtrOfArrayToSliceAndPtr(); | 235 | try testCastPtrOfArrayToSliceAndPtr(); |
| 263 | comptime try testCastPtrOfArrayToSliceAndPtr(); | 236 | comptime try testCastPtrOfArrayToSliceAndPtr(); |
| 264 | } | 237 | } |
| ... | @@ -290,35 +263,6 @@ test "cast *[1][*]const u8 to [*]const ?[*]const u8" { | ... | @@ -290,35 +263,6 @@ test "cast *[1][*]const u8 to [*]const ?[*]const u8" { |
| 290 | try expect(mem.eql(u8, std.mem.spanZ(@ptrCast([*:0]const u8, x[0].?)), "window name")); | 263 | try expect(mem.eql(u8, std.mem.spanZ(@ptrCast([*:0]const u8, x[0].?)), "window name")); |
| 291 | } | 264 | } |
| 292 | 265 | ||
| 293 | test "@intCast comptime_int" { | ||
| 294 | const result = @intCast(i32, 1234); | ||
| 295 | try expect(@TypeOf(result) == i32); | ||
| 296 | try expect(result == 1234); | ||
| 297 | } | ||
| 298 | |||
| 299 | test "@floatCast comptime_int and comptime_float" { | ||
| 300 | { | ||
| 301 | const result = @floatCast(f16, 1234); | ||
| 302 | try expect(@TypeOf(result) == f16); | ||
| 303 | try expect(result == 1234.0); | ||
| 304 | } | ||
| 305 | { | ||
| 306 | const result = @floatCast(f16, 1234.0); | ||
| 307 | try expect(@TypeOf(result) == f16); | ||
| 308 | try expect(result == 1234.0); | ||
| 309 | } | ||
| 310 | { | ||
| 311 | const result = @floatCast(f32, 1234); | ||
| 312 | try expect(@TypeOf(result) == f32); | ||
| 313 | try expect(result == 1234.0); | ||
| 314 | } | ||
| 315 | { | ||
| 316 | const result = @floatCast(f32, 1234.0); | ||
| 317 | try expect(@TypeOf(result) == f32); | ||
| 318 | try expect(result == 1234.0); | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | test "vector casts" { | 266 | test "vector casts" { |
| 323 | const S = struct { | 267 | const S = struct { |
| 324 | fn doTheTest() !void { | 268 | fn doTheTest() !void { |
| ... | @@ -369,23 +313,6 @@ test "@floatCast cast down" { | ... | @@ -369,23 +313,6 @@ test "@floatCast cast down" { |
| 369 | } | 313 | } |
| 370 | } | 314 | } |
| 371 | 315 | ||
| 372 | test "implicit cast undefined to optional" { | ||
| 373 | try expect(MakeType(void).getNull() == null); | ||
| 374 | try expect(MakeType(void).getNonNull() != null); | ||
| 375 | } | ||
| 376 | |||
| 377 | fn MakeType(comptime T: type) type { | ||
| 378 | return struct { | ||
| 379 | fn getNull() ?T { | ||
| 380 | return null; | ||
| 381 | } | ||
| 382 | |||
| 383 | fn getNonNull() ?T { | ||
| 384 | return @as(T, undefined); | ||
| 385 | } | ||
| 386 | }; | ||
| 387 | } | ||
| 388 | |||
| 389 | test "implicit cast from *[N]T to ?[*]T" { | 316 | test "implicit cast from *[N]T to ?[*]T" { |
| 390 | var x: ?[*]u16 = null; | 317 | var x: ?[*]u16 = null; |
| 391 | var y: [4]u16 = [4]u16{ 0, 1, 2, 3 }; | 318 | var y: [4]u16 = [4]u16{ 0, 1, 2, 3 }; |
| ... | @@ -397,16 +324,6 @@ test "implicit cast from *[N]T to ?[*]T" { | ... | @@ -397,16 +324,6 @@ test "implicit cast from *[N]T to ?[*]T" { |
| 397 | try expect(std.mem.eql(u16, x.?[0..4], y[0..4])); | 324 | try expect(std.mem.eql(u16, x.?[0..4], y[0..4])); |
| 398 | } | 325 | } |
| 399 | 326 | ||
| 400 | test "implicit cast from *[N]T to [*c]T" { | ||
| 401 | var x: [4]u16 = [4]u16{ 0, 1, 2, 3 }; | ||
| 402 | var y: [*c]u16 = &x; | ||
| 403 | |||
| 404 | try expect(std.mem.eql(u16, x[0..4], y[0..4])); | ||
| 405 | x[0] = 8; | ||
| 406 | y[3] = 6; | ||
| 407 | try expect(std.mem.eql(u16, x[0..4], y[0..4])); | ||
| 408 | } | ||
| 409 | |||
| 410 | test "implicit cast from *T to ?*c_void" { | 327 | test "implicit cast from *T to ?*c_void" { |
| 411 | var a: u8 = 1; | 328 | var a: u8 = 1; |
| 412 | incrementVoidPtrValue(&a); | 329 | incrementVoidPtrValue(&a); |
| ... | @@ -417,50 +334,6 @@ fn incrementVoidPtrValue(value: ?*c_void) void { | ... | @@ -417,50 +334,6 @@ fn incrementVoidPtrValue(value: ?*c_void) void { |
| 417 | @ptrCast(*u8, value.?).* += 1; | 334 | @ptrCast(*u8, value.?).* += 1; |
| 418 | } | 335 | } |
| 419 | 336 | ||
| 420 | test "*usize to *void" { | ||
| 421 | var i = @as(usize, 0); | ||
| 422 | var v = @ptrCast(*void, &i); | ||
| 423 | v.* = {}; | ||
| 424 | } | ||
| 425 | |||
| 426 | test "compile time int to ptr of function" { | ||
| 427 | try foobar(FUNCTION_CONSTANT); | ||
| 428 | } | ||
| 429 | |||
| 430 | pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize)); | ||
| 431 | pub const PFN_void = fn (*c_void) callconv(.C) void; | ||
| 432 | |||
| 433 | fn foobar(func: PFN_void) !void { | ||
| 434 | try std.testing.expect(@ptrToInt(func) == maxInt(usize)); | ||
| 435 | } | ||
| 436 | |||
| 437 | test "implicit ptr to *c_void" { | ||
| 438 | var a: u32 = 1; | ||
| 439 | var ptr: *align(@alignOf(u32)) c_void = &a; | ||
| 440 | var b: *u32 = @ptrCast(*u32, ptr); | ||
| 441 | try expect(b.* == 1); | ||
| 442 | var ptr2: ?*align(@alignOf(u32)) c_void = &a; | ||
| 443 | var c: *u32 = @ptrCast(*u32, ptr2.?); | ||
| 444 | try expect(c.* == 1); | ||
| 445 | } | ||
| 446 | |||
| 447 | test "@intToEnum passed a comptime_int to an enum with one item" { | ||
| 448 | const E = enum { A }; | ||
| 449 | const x = @intToEnum(E, 0); | ||
| 450 | try expect(x == E.A); | ||
| 451 | } | ||
| 452 | |||
| 453 | test "@intCast to u0 and use the result" { | ||
| 454 | const S = struct { | ||
| 455 | fn doTheTest(zero: u1, one: u1, bigzero: i32) !void { | ||
| 456 | try expect((one << @intCast(u0, bigzero)) == 1); | ||
| 457 | try expect((zero << @intCast(u0, bigzero)) == 0); | ||
| 458 | } | ||
| 459 | }; | ||
| 460 | try S.doTheTest(0, 1, 0); | ||
| 461 | comptime try S.doTheTest(0, 1, 0); | ||
| 462 | } | ||
| 463 | |||
| 464 | test "peer type resolution: unreachable, null, slice" { | 337 | test "peer type resolution: unreachable, null, slice" { |
| 465 | const S = struct { | 338 | const S = struct { |
| 466 | fn doTheTest(num: usize, word: []const u8) !void { | 339 | fn doTheTest(num: usize, word: []const u8) !void { |
| ... | @@ -639,27 +512,6 @@ test "return u8 coercing into ?u32 return type" { | ... | @@ -639,27 +512,6 @@ test "return u8 coercing into ?u32 return type" { |
| 639 | comptime try S.doTheTest(); | 512 | comptime try S.doTheTest(); |
| 640 | } | 513 | } |
| 641 | 514 | ||
| 642 | test "peer result null and comptime_int" { | ||
| 643 | const S = struct { | ||
| 644 | fn blah(n: i32) ?i32 { | ||
| 645 | if (n == 0) { | ||
| 646 | return null; | ||
| 647 | } else if (n < 0) { | ||
| 648 | return -1; | ||
| 649 | } else { | ||
| 650 | return 1; | ||
| 651 | } | ||
| 652 | } | ||
| 653 | }; | ||
| 654 | |||
| 655 | try expect(S.blah(0) == null); | ||
| 656 | comptime try expect(S.blah(0) == null); | ||
| 657 | try expect(S.blah(10).? == 1); | ||
| 658 | comptime try expect(S.blah(10).? == 1); | ||
| 659 | try expect(S.blah(-10).? == -1); | ||
| 660 | comptime try expect(S.blah(-10).? == -1); | ||
| 661 | } | ||
| 662 | |||
| 663 | test "peer type resolution implicit cast to return type" { | 515 | test "peer type resolution implicit cast to return type" { |
| 664 | const S = struct { | 516 | const S = struct { |
| 665 | fn doTheTest() !void { | 517 | fn doTheTest() !void { |