diff --git a/doc/docgen.zig b/doc/docgen.zig index fed4bb8ebab0..c8acef6c0751 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -51,14 +51,7 @@ pub fn main() !void { var toc = try genToc(allocator, &tokenizer); try os.makePath(allocator, tmp_dir_name); - defer { - // TODO issue #709 - // disabled to pass CI tests, but obviously we want to implement this - // and then remove this workaround - if (builtin.os != builtin.Os.windows) { - os.deleteTree(allocator, tmp_dir_name) catch {}; - } - } + defer os.deleteTree(allocator, tmp_dir_name) catch {}; try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe); try buffered_out_stream.flush(); } @@ -300,6 +293,7 @@ const Link = struct { const Node = union(enum) { Content: []const u8, Nav, + Builtin, HeaderOpen: HeaderOpen, SeeAlso: []const SeeAlsoItem, Code: Code, @@ -356,6 +350,9 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { _ = try eatToken(tokenizer, Token.Id.BracketClose); try nodes.append(Node.Nav); + } else if (mem.eql(u8, tag_name, "builtin")) { + _ = try eatToken(tokenizer, Token.Id.BracketClose); + try nodes.append(Node.Builtin); } else if (mem.eql(u8, tag_name, "header_open")) { _ = try eatToken(tokenizer, Token.Id.Separator); const content_token = try eatToken(tokenizer, Token.Id.TagContent); @@ -690,6 +687,9 @@ fn termColor(allocator: *mem.Allocator, input: []const u8) ![]u8 { fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var, zig_exe: []const u8) !void { var code_progress_index: usize = 0; + + const builtin_code = try escapeHtml(allocator, try getBuiltinCode(allocator, zig_exe)); + for (toc.nodes) |node| { switch (node) { Node.Content => |data| { @@ -704,6 +704,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var Node.Nav => { try out.write(toc.toc); }, + Node.Builtin => { + try out.print("
{}
", builtin_code); + }, Node.HeaderOpen => |info| { try out.print("{}\n", info.n, info.url, info.name, info.n); }, @@ -1060,3 +1063,11 @@ fn exec(allocator: *mem.Allocator, args: []const []const u8) !os.ChildProcess.Ex } return result; } + +fn getBuiltinCode(allocator: *mem.Allocator, zig_exe: []const u8) ![]const u8 { + const result = try exec(allocator, []const []const u8{ + zig_exe, + "builtin", + }); + return result.stdout; +} diff --git a/doc/langref.html.in b/doc/langref.html.in index 4359cadb58f0..6a1f1c3102ea 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3845,9 +3845,6 @@ pub fn printValue(self: *OutStream, value: var) !void { return self.printInt(T, value); } else if (@isFloat(T)) { return self.printFloat(T, value); - } else if (@canImplicitCast([]const u8, value)) { - const casted_value = ([]const u8)(value); - return self.write(casted_value); } else { @compileError("Unable to print type '" ++ @typeName(T) ++ "'"); } @@ -4102,12 +4099,6 @@ comptime {

{#see_also|Import from C Header File|@cImport|@cDefine|@cInclude#} {#header_close#} - {#header_open|@canImplicitCast#} -
@canImplicitCast(comptime T: type, value) bool
-

- Returns whether a value can be implicitly casted to a given type. -

- {#header_close#} {#header_open|@clz#}
@clz(x: T) U

@@ -5474,425 +5465,7 @@ const separator = if (builtin.os == builtin.Os.windows) '\\' else '/';

Example of what is imported with @import("builtin"):

- {#code_begin|syntax#} -pub const StackTrace = struct { - index: usize, - instruction_addresses: []usize, -}; - -pub const Os = enum { - freestanding, - ananas, - cloudabi, - dragonfly, - freebsd, - fuchsia, - ios, - kfreebsd, - linux, - lv2, - macosx, - netbsd, - openbsd, - solaris, - windows, - haiku, - minix, - rtems, - nacl, - cnk, - aix, - cuda, - nvcl, - amdhsa, - ps4, - elfiamcu, - tvos, - watchos, - mesa3d, - contiki, - amdpal, - zen, -}; - -pub const Arch = enum { - armv8_3a, - armv8_2a, - armv8_1a, - armv8, - armv8r, - armv8m_baseline, - armv8m_mainline, - armv7, - armv7em, - armv7m, - armv7s, - armv7k, - armv7ve, - armv6, - armv6m, - armv6k, - armv6t2, - armv5, - armv5te, - armv4t, - armebv8_3a, - armebv8_2a, - armebv8_1a, - armebv8, - armebv8r, - armebv8m_baseline, - armebv8m_mainline, - armebv7, - armebv7em, - armebv7m, - armebv7s, - armebv7k, - armebv7ve, - armebv6, - armebv6m, - armebv6k, - armebv6t2, - armebv5, - armebv5te, - armebv4t, - aarch64, - aarch64_be, - arc, - avr, - bpfel, - bpfeb, - hexagon, - mips, - mipsel, - mips64, - mips64el, - msp430, - nios2, - powerpc, - powerpc64, - powerpc64le, - r600, - amdgcn, - riscv32, - riscv64, - sparc, - sparcv9, - sparcel, - s390x, - tce, - tcele, - thumb, - thumbeb, - i386, - x86_64, - xcore, - nvptx, - nvptx64, - le32, - le64, - amdil, - amdil64, - hsail, - hsail64, - spir, - spir64, - kalimbav3, - kalimbav4, - kalimbav5, - shave, - lanai, - wasm32, - wasm64, - renderscript32, - renderscript64, -}; - -pub const Environ = enum { - unknown, - gnu, - gnuabin32, - gnuabi64, - gnueabi, - gnueabihf, - gnux32, - code16, - eabi, - eabihf, - android, - musl, - musleabi, - musleabihf, - msvc, - itanium, - cygnus, - amdopencl, - coreclr, - opencl, - simulator, -}; - -pub const ObjectFormat = enum { - unknown, - coff, - elf, - macho, - wasm, -}; - -pub const GlobalLinkage = enum { - Internal, - Strong, - Weak, - LinkOnce, -}; - -pub const AtomicOrder = enum { - Unordered, - Monotonic, - Acquire, - Release, - AcqRel, - SeqCst, -}; - -pub const AtomicRmwOp = enum { - Xchg, - Add, - Sub, - And, - Nand, - Or, - Xor, - Max, - Min, -}; - -pub const Mode = enum { - Debug, - ReleaseSafe, - ReleaseFast, - ReleaseSmall, -}; - -pub const TypeId = enum { - Type, - Void, - Bool, - NoReturn, - Int, - Float, - Pointer, - Array, - Struct, - ComptimeFloat, - ComptimeInt, - Undefined, - Null, - Nullable, - ErrorUnion, - ErrorSet, - Enum, - Union, - Fn, - Namespace, - Block, - BoundFn, - ArgTuple, - Opaque, - Promise, -}; - -pub const TypeInfo = union(TypeId) { - Type: void, - Void: void, - Bool: void, - NoReturn: void, - Int: Int, - Float: Float, - Pointer: Pointer, - Array: Array, - Struct: Struct, - ComptimeFloat: void, - ComptimeInt: void, - Undefined: void, - Null: void, - Nullable: Nullable, - ErrorUnion: ErrorUnion, - ErrorSet: ErrorSet, - Enum: Enum, - Union: Union, - Fn: Fn, - Namespace: void, - Block: void, - BoundFn: Fn, - ArgTuple: void, - Opaque: void, - Promise: Promise, - - - pub const Int = struct { - is_signed: bool, - bits: u8, - }; - - pub const Float = struct { - bits: u8, - }; - - pub const Pointer = struct { - is_const: bool, - is_volatile: bool, - alignment: u32, - child: type, - }; - - pub const Array = struct { - len: usize, - child: type, - }; - - pub const ContainerLayout = enum { - Auto, - Extern, - Packed, - }; - - pub const StructField = struct { - name: []const u8, - offset: ?usize, - field_type: type, - }; - - pub const Struct = struct { - layout: ContainerLayout, - fields: []StructField, - defs: []Definition, - }; - - pub const Nullable = struct { - child: type, - }; - - pub const ErrorUnion = struct { - error_set: type, - payload: type, - }; - - pub const Error = struct { - name: []const u8, - value: usize, - }; - - pub const ErrorSet = struct { - errors: []Error, - }; - - pub const EnumField = struct { - name: []const u8, - value: usize, - }; - - pub const Enum = struct { - layout: ContainerLayout, - tag_type: type, - fields: []EnumField, - defs: []Definition, - }; - - pub const UnionField = struct { - name: []const u8, - enum_field: ?EnumField, - field_type: type, - }; - - pub const Union = struct { - layout: ContainerLayout, - tag_type: type, - fields: []UnionField, - defs: []Definition, - }; - - pub const CallingConvention = enum { - Unspecified, - C, - Cold, - Naked, - Stdcall, - Async, - }; - - pub const FnArg = struct { - is_generic: bool, - is_noalias: bool, - arg_type: type, - }; - - pub const Fn = struct { - calling_convention: CallingConvention, - is_generic: bool, - is_var_args: bool, - return_type: type, - async_allocator_type: type, - args: []FnArg, - }; - - pub const Promise = struct { - child: type, - }; - - pub const Definition = struct { - name: []const u8, - is_pub: bool, - data: Data, - - pub const Data = union(enum) { - Type: type, - Var: type, - Fn: FnDef, - - pub const FnDef = struct { - fn_type: type, - inline_type: Inline, - calling_convention: CallingConvention, - is_var_args: bool, - is_extern: bool, - is_export: bool, - lib_name: ?[]const u8, - return_type: type, - arg_names: [][] const u8, - - pub const Inline = enum { - Auto, - Always, - Never, - }; - }; - }; - }; -}; - -pub const FloatMode = enum { - Optimized, - Strict, -}; - -pub const Endian = enum { - Big, - Little, -}; - -pub const endian = Endian.Little; -pub const is_test = true; -pub const os = Os.linux; -pub const arch = Arch.x86_64; -pub const environ = Environ.gnu; -pub const object_format = ObjectFormat.elf; -pub const mode = Mode.Debug; -pub const link_libc = false; -pub const have_error_return_tracing = true; -pub const __zig_test_fn_slice = {}; // overwritten later - {#code_end#} + {#builtin#} {#see_also|Build Mode#} {#header_close#} {#header_open|Root Source File#} @@ -6053,8 +5626,7 @@ pub fn build(b: *Builder) void { b.default_step.dependOn(&exe.step); } {#code_end#} - {#header_close#} - {#header_open|Terminal#} +

terminal

$ zig build
 $ ./test
 all your base are belong to us
@@ -6555,7 +6127,7 @@ hljs.registerLanguage("zig", function(t) { a = t.IR + "\\s*\\(", c = { keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union break return try catch test continue unreachable comptime and or asm defer errdefer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong", - built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic canImplicitCast ptrCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall", + built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src/all_types.hpp b/src/all_types.hpp index 3b2ea02b710c..c671682363de 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -583,6 +583,7 @@ enum CastOp { CastOpNumLitToConcrete, CastOpErrSet, CastOpBitCast, + CastOpPtrOfArrayToSlice, }; struct AstNodeFnCallExpr { @@ -1037,6 +1038,10 @@ struct TypeTableEntryStruct { // whether we've finished resolving it bool complete; + // whether any of the fields require comptime + // the value is not valid until zero_bits_known == true + bool requires_comptime; + bool zero_bits_loop_flag; bool zero_bits_known; uint32_t abi_alignment; // also figured out with zero_bits pass @@ -1105,6 +1110,10 @@ struct TypeTableEntryUnion { // whether we've finished resolving it bool complete; + // whether any of the fields require comptime + // the value is not valid until zero_bits_known == true + bool requires_comptime; + bool zero_bits_loop_flag; bool zero_bits_known; uint32_t abi_alignment; // also figured out with zero_bits pass @@ -1346,7 +1355,6 @@ enum BuiltinFnId { BuiltinFnIdSetRuntimeSafety, BuiltinFnIdSetFloatMode, BuiltinFnIdTypeName, - BuiltinFnIdCanImplicitCast, BuiltinFnIdPanic, BuiltinFnIdPtrCast, BuiltinFnIdBitCast, @@ -2057,7 +2065,6 @@ enum IrInstructionId { IrInstructionIdCheckSwitchProngs, IrInstructionIdCheckStatementIsVoid, IrInstructionIdTypeName, - IrInstructionIdCanImplicitCast, IrInstructionIdDeclRef, IrInstructionIdPanic, IrInstructionIdTagName, @@ -2850,13 +2857,6 @@ struct IrInstructionTypeName { IrInstruction *type_value; }; -struct IrInstructionCanImplicitCast { - IrInstruction base; - - IrInstruction *type_value; - IrInstruction *target_value; -}; - struct IrInstructionDeclRef { IrInstruction base; diff --git a/src/analyze.cpp b/src/analyze.cpp index 93373f6ec2bb..84f1473ea186 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2533,6 +2533,10 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) { continue; } + if (type_requires_comptime(field_type)) { + struct_type->data.structure.requires_comptime = true; + } + if (!type_has_bits(field_type)) continue; @@ -2724,6 +2728,11 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) { } union_field->type_entry = field_type; + if (type_requires_comptime(field_type)) { + union_type->data.unionation.requires_comptime = true; + } + + if (field_node->data.struct_field.value != nullptr && !decl_node->data.container_decl.auto_enum) { ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.value, buf_sprintf("non-enum union field assignment")); @@ -3752,14 +3761,24 @@ static bool is_container(TypeTableEntry *type_entry) { zig_unreachable(); } +bool is_ref(TypeTableEntry *type_entry) { + return type_entry->id == TypeTableEntryIdPointer && type_entry->data.pointer.ptr_len == PtrLenSingle; +} + +bool is_array_ref(TypeTableEntry *type_entry) { + TypeTableEntry *array = is_ref(type_entry) ? + type_entry->data.pointer.child_type : type_entry; + return array->id == TypeTableEntryIdArray; +} + bool is_container_ref(TypeTableEntry *type_entry) { - return (type_entry->id == TypeTableEntryIdPointer && type_entry->data.pointer.ptr_len == PtrLenSingle) ? + return is_ref(type_entry) ? is_container(type_entry->data.pointer.child_type) : is_container(type_entry); } TypeTableEntry *container_ref_type(TypeTableEntry *type_entry) { assert(is_container_ref(type_entry)); - return (type_entry->id == TypeTableEntryIdPointer && type_entry->data.pointer.ptr_len == PtrLenSingle) ? + return is_ref(type_entry) ? type_entry->data.pointer.child_type : type_entry; } @@ -4944,17 +4963,29 @@ bool type_requires_comptime(TypeTableEntry *type_entry) { case TypeTableEntryIdArgTuple: return true; case TypeTableEntryIdArray: + return type_requires_comptime(type_entry->data.array.child_type); case TypeTableEntryIdStruct: + assert(type_has_zero_bits_known(type_entry)); + return type_entry->data.structure.requires_comptime; case TypeTableEntryIdUnion: + assert(type_has_zero_bits_known(type_entry)); + return type_entry->data.unionation.requires_comptime; case TypeTableEntryIdMaybe: + return type_requires_comptime(type_entry->data.maybe.child_type); case TypeTableEntryIdErrorUnion: + return type_requires_comptime(type_entry->data.error_union.payload_type); + case TypeTableEntryIdPointer: + if (type_entry->data.pointer.child_type->id == TypeTableEntryIdOpaque) { + return false; + } else { + return type_requires_comptime(type_entry->data.pointer.child_type); + } case TypeTableEntryIdEnum: case TypeTableEntryIdErrorSet: case TypeTableEntryIdFn: case TypeTableEntryIdBool: case TypeTableEntryIdInt: case TypeTableEntryIdFloat: - case TypeTableEntryIdPointer: case TypeTableEntryIdVoid: case TypeTableEntryIdUnreachable: case TypeTableEntryIdPromise: diff --git a/src/analyze.hpp b/src/analyze.hpp index 25bda198d69d..88e06b2390e9 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -70,6 +70,8 @@ TypeUnionField *find_union_type_field(TypeTableEntry *type_entry, Buf *name); TypeEnumField *find_enum_field_by_tag(TypeTableEntry *enum_type, const BigInt *tag); TypeUnionField *find_union_field_by_tag(TypeTableEntry *type_entry, const BigInt *tag); +bool is_ref(TypeTableEntry *type_entry); +bool is_array_ref(TypeTableEntry *type_entry); bool is_container_ref(TypeTableEntry *type_entry); void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node); void scan_import(CodeGen *g, ImportTableEntry *import); diff --git a/src/codegen.cpp b/src/codegen.cpp index 7f95f335d12a..fab2ad659e0a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2530,7 +2530,7 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, assert(wanted_type->data.structure.is_slice); assert(actual_type->id == TypeTableEntryIdArray); - TypeTableEntry *wanted_pointer_type = wanted_type->data.structure.fields[0].type_entry; + TypeTableEntry *wanted_pointer_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; TypeTableEntry *wanted_child_type = wanted_pointer_type->data.pointer.child_type; @@ -2576,6 +2576,29 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, return expr_val; case CastOpBitCast: return LLVMBuildBitCast(g->builder, expr_val, wanted_type->type_ref, ""); + case CastOpPtrOfArrayToSlice: { + assert(cast_instruction->tmp_ptr); + assert(actual_type->id == TypeTableEntryIdPointer); + TypeTableEntry *array_type = actual_type->data.pointer.child_type; + assert(array_type->id == TypeTableEntryIdArray); + + LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, cast_instruction->tmp_ptr, + slice_ptr_index, ""); + LLVMValueRef indices[] = { + LLVMConstNull(g->builtin_types.entry_usize->type_ref), + LLVMConstInt(g->builtin_types.entry_usize->type_ref, 0, false), + }; + LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, expr_val, indices, 2, ""); + gen_store_untyped(g, slice_start_ptr, ptr_field_ptr, 0, false); + + LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, cast_instruction->tmp_ptr, + slice_len_index, ""); + LLVMValueRef len_value = LLVMConstInt(g->builtin_types.entry_usize->type_ref, + array_type->data.array.len, false); + gen_store_untyped(g, len_value, len_field_ptr, 0, false); + + return cast_instruction->tmp_ptr; + } } zig_unreachable(); } @@ -3815,7 +3838,6 @@ static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInst } else { end_val = LLVMConstInt(g->builtin_types.entry_usize->type_ref, array_type->data.array.len, false); } - if (want_runtime_safety) { add_bounds_check(g, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val); if (instruction->end) { @@ -4625,7 +4647,6 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdCheckSwitchProngs: case IrInstructionIdCheckStatementIsVoid: case IrInstructionIdTypeName: - case IrInstructionIdCanImplicitCast: case IrInstructionIdDeclRef: case IrInstructionIdSwitchVar: case IrInstructionIdOffsetOf: @@ -6277,7 +6298,6 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdCImport, "cImport", 1); create_builtin_fn(g, BuiltinFnIdErrName, "errorName", 1); create_builtin_fn(g, BuiltinFnIdTypeName, "typeName", 1); - create_builtin_fn(g, BuiltinFnIdCanImplicitCast, "canImplicitCast", 2); create_builtin_fn(g, BuiltinFnIdEmbedFile, "embedFile", 1); create_builtin_fn(g, BuiltinFnIdCmpxchgWeak, "cmpxchgWeak", 6); create_builtin_fn(g, BuiltinFnIdCmpxchgStrong, "cmpxchgStrong", 6); @@ -6335,13 +6355,7 @@ static const char *build_mode_to_str(BuildMode build_mode) { zig_unreachable(); } -static void define_builtin_compile_vars(CodeGen *g) { - if (g->std_package == nullptr) - return; - - const char *builtin_zig_basename = "builtin.zig"; - Buf *builtin_zig_path = buf_alloc(); - os_path_join(g->cache_dir, buf_create_from_str(builtin_zig_basename), builtin_zig_path); +Buf *codegen_generate_builtin_source(CodeGen *g) { Buf *contents = buf_alloc(); // Modifications to this struct must be coordinated with code that does anything with @@ -6707,6 +6721,19 @@ static void define_builtin_compile_vars(CodeGen *g) { buf_appendf(contents, "pub const __zig_test_fn_slice = {}; // overwritten later\n"); + + return contents; +} + +static void define_builtin_compile_vars(CodeGen *g) { + if (g->std_package == nullptr) + return; + + const char *builtin_zig_basename = "builtin.zig"; + Buf *builtin_zig_path = buf_alloc(); + os_path_join(g->cache_dir, buf_create_from_str(builtin_zig_basename), builtin_zig_path); + + Buf *contents = codegen_generate_builtin_source(g); ensure_cache_dir(g); os_write_file(builtin_zig_path, contents); diff --git a/src/codegen.hpp b/src/codegen.hpp index a7a4b748c4a2..b5f3374ec401 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -59,5 +59,7 @@ void codegen_add_object(CodeGen *g, Buf *object_path); void codegen_translate_c(CodeGen *g, Buf *path); +Buf *codegen_generate_builtin_source(CodeGen *g); + #endif diff --git a/src/ir.cpp b/src/ir.cpp index 3486e8c04758..4766bff5e74a 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -108,6 +108,7 @@ static IrInstruction *ir_get_var_ptr(IrAnalyze *ira, IrInstruction *instruction, static TypeTableEntry *ir_resolve_atomic_operand_type(IrAnalyze *ira, IrInstruction *op); static IrInstruction *ir_lval_wrap(IrBuilder *irb, Scope *scope, IrInstruction *value, LVal lval); static TypeTableEntry *adjust_ptr_align(CodeGen *g, TypeTableEntry *ptr_type, uint32_t new_align); +static TypeTableEntry *adjust_slice_align(CodeGen *g, TypeTableEntry *slice_type, uint32_t new_align); ConstExprValue *const_ptr_pointee(CodeGen *g, ConstExprValue *const_val) { assert(const_val->type->id == TypeTableEntryIdPointer); @@ -585,10 +586,6 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionTypeName *) { return IrInstructionIdTypeName; } -static constexpr IrInstructionId ir_instruction_id(IrInstructionCanImplicitCast *) { - return IrInstructionIdCanImplicitCast; -} - static constexpr IrInstructionId ir_instruction_id(IrInstructionDeclRef *) { return IrInstructionIdDeclRef; } @@ -2348,20 +2345,6 @@ static IrInstruction *ir_build_type_name(IrBuilder *irb, Scope *scope, AstNode * return &instruction->base; } -static IrInstruction *ir_build_can_implicit_cast(IrBuilder *irb, Scope *scope, AstNode *source_node, - IrInstruction *type_value, IrInstruction *target_value) -{ - IrInstructionCanImplicitCast *instruction = ir_build_instruction( - irb, scope, source_node); - instruction->type_value = type_value; - instruction->target_value = target_value; - - ir_ref_instruction(type_value, irb->current_basic_block); - ir_ref_instruction(target_value, irb->current_basic_block); - - return &instruction->base; -} - static IrInstruction *ir_build_decl_ref(IrBuilder *irb, Scope *scope, AstNode *source_node, Tld *tld, LVal lval) { @@ -4132,21 +4115,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo IrInstruction *type_name = ir_build_type_name(irb, scope, node, arg0_value); return ir_lval_wrap(irb, scope, type_name, lval); } - case BuiltinFnIdCanImplicitCast: - { - AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); - if (arg0_value == irb->codegen->invalid_instruction) - return arg0_value; - - AstNode *arg1_node = node->data.fn_call_expr.params.at(1); - IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); - if (arg1_value == irb->codegen->invalid_instruction) - return arg1_value; - - IrInstruction *can_implicit_cast = ir_build_can_implicit_cast(irb, scope, node, arg0_value, arg1_value); - return ir_lval_wrap(irb, scope, can_implicit_cast, lval); - } case BuiltinFnIdPanic: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -8057,6 +8025,33 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira, } } + // implicit *[N]T to [*]T + if (expected_type->id == TypeTableEntryIdPointer && + expected_type->data.pointer.ptr_len == PtrLenUnknown && + actual_type->id == TypeTableEntryIdPointer && + actual_type->data.pointer.ptr_len == PtrLenSingle && + actual_type->data.pointer.child_type->id == TypeTableEntryIdArray && + types_match_const_cast_only(ira, expected_type->data.pointer.child_type, + actual_type->data.pointer.child_type->data.array.child_type, source_node).id == ConstCastResultIdOk) + { + return ImplicitCastMatchResultYes; + } + + // implicit *[N]T to []T + if (is_slice(expected_type) && + actual_type->id == TypeTableEntryIdPointer && + actual_type->data.pointer.ptr_len == PtrLenSingle && + actual_type->data.pointer.child_type->id == TypeTableEntryIdArray) + { + TypeTableEntry *slice_ptr_type = expected_type->data.structure.fields[slice_ptr_index].type_entry; + assert(slice_ptr_type->id == TypeTableEntryIdPointer); + if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, + actual_type->data.pointer.child_type->data.array.child_type, source_node).id == ConstCastResultIdOk) + { + return ImplicitCastMatchResultYes; + } + } + // implicit [N]T to ?[]const T if (expected_type->id == TypeTableEntryIdMaybe && is_slice(expected_type->data.maybe.child_type) && @@ -8732,6 +8727,7 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, zig_unreachable(); case CastOpErrSet: case CastOpBitCast: + case CastOpPtrOfArrayToSlice: zig_panic("TODO"); case CastOpNoop: { @@ -8819,6 +8815,63 @@ static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_inst } } +static IrInstruction *ir_resolve_ptr_of_array_to_unknown_len_ptr(IrAnalyze *ira, IrInstruction *source_instr, + IrInstruction *value, TypeTableEntry *wanted_type) +{ + assert(value->value.type->id == TypeTableEntryIdPointer); + wanted_type = adjust_ptr_align(ira->codegen, wanted_type, value->value.type->data.pointer.alignment); + + if (instr_is_comptime(value)) { + ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &value->value); + if (pointee->special != ConstValSpecialRuntime) { + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, wanted_type); + result->value.type = wanted_type; + result->value.data.x_ptr.special = ConstPtrSpecialBaseArray; + result->value.data.x_ptr.mut = value->value.data.x_ptr.mut; + result->value.data.x_ptr.data.base_array.array_val = pointee; + result->value.data.x_ptr.data.base_array.elem_index = 0; + result->value.data.x_ptr.data.base_array.is_cstr = false; + return result; + } + } + + IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, + wanted_type, value, CastOpBitCast); + result->value.type = wanted_type; + return result; +} + +static IrInstruction *ir_resolve_ptr_of_array_to_slice(IrAnalyze *ira, IrInstruction *source_instr, + IrInstruction *value, TypeTableEntry *wanted_type) +{ + wanted_type = adjust_slice_align(ira->codegen, wanted_type, value->value.type->data.pointer.alignment); + + if (instr_is_comptime(value)) { + ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &value->value); + if (pointee->special != ConstValSpecialRuntime) { + assert(value->value.type->id == TypeTableEntryIdPointer); + TypeTableEntry *array_type = value->value.type->data.pointer.child_type; + assert(is_slice(wanted_type)); + bool is_const = wanted_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const; + + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, wanted_type); + init_const_slice(ira->codegen, &result->value, pointee, 0, array_type->data.array.len, is_const); + result->value.data.x_struct.fields[slice_ptr_index].data.x_ptr.mut = + value->value.data.x_ptr.mut; + result->value.type = wanted_type; + return result; + } + } + + IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, + wanted_type, value, CastOpPtrOfArrayToSlice); + result->value.type = wanted_type; + ir_add_alloca(ira, result, wanted_type); + return result; +} + static bool is_container(TypeTableEntry *type) { return type->id == TypeTableEntryIdStruct || type->id == TypeTableEntryIdEnum || @@ -9970,6 +10023,35 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } + // explicit *[N]T to [*]T + if (wanted_type->id == TypeTableEntryIdPointer && + wanted_type->data.pointer.ptr_len == PtrLenUnknown && + actual_type->id == TypeTableEntryIdPointer && + actual_type->data.pointer.ptr_len == PtrLenSingle && + actual_type->data.pointer.child_type->id == TypeTableEntryIdArray && + actual_type->data.pointer.alignment >= wanted_type->data.pointer.alignment && + types_match_const_cast_only(ira, wanted_type->data.pointer.child_type, + actual_type->data.pointer.child_type->data.array.child_type, source_node).id == ConstCastResultIdOk) + { + return ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value, wanted_type); + } + + // explicit *[N]T to []T + if (is_slice(wanted_type) && + actual_type->id == TypeTableEntryIdPointer && + actual_type->data.pointer.ptr_len == PtrLenSingle && + actual_type->data.pointer.child_type->id == TypeTableEntryIdArray) + { + TypeTableEntry *slice_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; + assert(slice_ptr_type->id == TypeTableEntryIdPointer); + if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, + actual_type->data.pointer.child_type->data.array.child_type, source_node).id == ConstCastResultIdOk) + { + return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type); + } + } + + // explicit cast from child type of maybe type to maybe type if (wanted_type->id == TypeTableEntryIdMaybe) { TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type; @@ -11624,61 +11706,6 @@ static TypeTableEntry *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructi zig_unreachable(); } -enum VarClassRequired { - VarClassRequiredAny, - VarClassRequiredConst, - VarClassRequiredIllegal, -}; - -static VarClassRequired get_var_class_required(TypeTableEntry *type_entry) { - switch (type_entry->id) { - case TypeTableEntryIdInvalid: - zig_unreachable(); - case TypeTableEntryIdUnreachable: - return VarClassRequiredIllegal; - case TypeTableEntryIdBool: - case TypeTableEntryIdInt: - case TypeTableEntryIdFloat: - case TypeTableEntryIdVoid: - case TypeTableEntryIdErrorSet: - case TypeTableEntryIdFn: - case TypeTableEntryIdPromise: - return VarClassRequiredAny; - case TypeTableEntryIdComptimeFloat: - case TypeTableEntryIdComptimeInt: - case TypeTableEntryIdUndefined: - case TypeTableEntryIdBlock: - case TypeTableEntryIdNull: - case TypeTableEntryIdOpaque: - case TypeTableEntryIdMetaType: - case TypeTableEntryIdNamespace: - case TypeTableEntryIdBoundFn: - case TypeTableEntryIdArgTuple: - return VarClassRequiredConst; - - case TypeTableEntryIdPointer: - if (type_entry->data.pointer.child_type->id == TypeTableEntryIdOpaque) { - return VarClassRequiredAny; - } else { - return get_var_class_required(type_entry->data.pointer.child_type); - } - case TypeTableEntryIdArray: - return get_var_class_required(type_entry->data.array.child_type); - case TypeTableEntryIdMaybe: - return get_var_class_required(type_entry->data.maybe.child_type); - case TypeTableEntryIdErrorUnion: - return get_var_class_required(type_entry->data.error_union.payload_type); - - case TypeTableEntryIdStruct: - case TypeTableEntryIdEnum: - case TypeTableEntryIdUnion: - // TODO check the fields of these things and make sure that they don't recursively - // contain any of the other variable classes - return VarClassRequiredAny; - } - zig_unreachable(); -} - static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstructionDeclVar *decl_var_instruction) { VariableTableEntry *var = decl_var_instruction->var; @@ -11713,36 +11740,41 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc if (type_is_invalid(result_type)) { result_type = ira->codegen->builtin_types.entry_invalid; } else { - switch (get_var_class_required(result_type)) { - case VarClassRequiredIllegal: + type_ensure_zero_bits_known(ira->codegen, result_type); + if (type_is_invalid(result_type)) { + result_type = ira->codegen->builtin_types.entry_invalid; + } + } + + if (!type_is_invalid(result_type)) { + if (result_type->id == TypeTableEntryIdUnreachable || + result_type->id == TypeTableEntryIdOpaque) + { + ir_add_error_node(ira, source_node, + buf_sprintf("variable of type '%s' not allowed", buf_ptr(&result_type->name))); + result_type = ira->codegen->builtin_types.entry_invalid; + } else if (type_requires_comptime(result_type)) { + var_class_requires_const = true; + if (!var->src_is_const && !is_comptime_var) { ir_add_error_node(ira, source_node, - buf_sprintf("variable of type '%s' not allowed", buf_ptr(&result_type->name))); + buf_sprintf("variable of type '%s' must be const or comptime", + buf_ptr(&result_type->name))); result_type = ira->codegen->builtin_types.entry_invalid; - break; - case VarClassRequiredConst: + } + } else { + if (casted_init_value->value.special == ConstValSpecialStatic && + casted_init_value->value.type->id == TypeTableEntryIdFn && + casted_init_value->value.data.x_ptr.data.fn.fn_entry->fn_inline == FnInlineAlways) + { var_class_requires_const = true; if (!var->src_is_const && !is_comptime_var) { - ir_add_error_node(ira, source_node, - buf_sprintf("variable of type '%s' must be const or comptime", - buf_ptr(&result_type->name))); + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("functions marked inline must be stored in const or comptime var")); + AstNode *proto_node = casted_init_value->value.data.x_ptr.data.fn.fn_entry->proto_node; + add_error_note(ira->codegen, msg, proto_node, buf_sprintf("declared here")); result_type = ira->codegen->builtin_types.entry_invalid; } - break; - case VarClassRequiredAny: - if (casted_init_value->value.special == ConstValSpecialStatic && - casted_init_value->value.type->id == TypeTableEntryIdFn && - casted_init_value->value.data.x_ptr.data.fn.fn_entry->fn_inline == FnInlineAlways) - { - var_class_requires_const = true; - if (!var->src_is_const && !is_comptime_var) { - ErrorMsg *msg = ir_add_error_node(ira, source_node, - buf_sprintf("functions marked inline must be stored in const or comptime var")); - AstNode *proto_node = casted_init_value->value.data.x_ptr.data.fn.fn_entry->proto_node; - add_error_note(ira->codegen, msg, proto_node, buf_sprintf("declared here")); - result_type = ira->codegen->builtin_types.entry_invalid; - } - } - break; + } } } @@ -12623,6 +12655,10 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal inst_fn_type_id.return_type = specified_return_type; } + type_ensure_zero_bits_known(ira->codegen, specified_return_type); + if (type_is_invalid(specified_return_type)) + return ira->codegen->builtin_types.entry_invalid; + if (type_requires_comptime(specified_return_type)) { // Throw out our work and call the function as if it were comptime. return ir_analyze_fn_call(ira, call_instruction, fn_entry, fn_type, fn_ref, first_arg_ptr, true, FnInlineAuto); @@ -13229,6 +13265,13 @@ static TypeTableEntry *adjust_ptr_align(CodeGen *g, TypeTableEntry *ptr_type, ui ptr_type->data.pointer.bit_offset, ptr_type->data.pointer.unaligned_bit_count); } +static TypeTableEntry *adjust_slice_align(CodeGen *g, TypeTableEntry *slice_type, uint32_t new_align) { + assert(is_slice(slice_type)); + TypeTableEntry *ptr_type = adjust_ptr_align(g, slice_type->data.structure.fields[slice_ptr_index].type_entry, + new_align); + return get_slice_type(g, ptr_type); +} + static TypeTableEntry *adjust_ptr_len(CodeGen *g, TypeTableEntry *ptr_type, PtrLen ptr_len) { assert(ptr_type->id == TypeTableEntryIdPointer); return get_pointer_to_type_extra(g, @@ -13803,10 +13846,14 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru ir_link_new_instruction(result, &field_ptr_instruction->base); return result->value.type; } - } else if (container_type->id == TypeTableEntryIdArray) { + } else if (is_array_ref(container_type)) { if (buf_eql_str(field_name, "len")) { ConstExprValue *len_val = create_const_vals(1); - init_const_usize(ira->codegen, len_val, container_type->data.array.len); + if (container_type->id == TypeTableEntryIdPointer) { + init_const_usize(ira->codegen, len_val, container_type->data.pointer.child_type->data.array.len); + } else { + init_const_usize(ira->codegen, len_val, container_type->data.array.len); + } TypeTableEntry *usize = ira->codegen->builtin_types.entry_usize; bool ptr_is_const = true; @@ -18451,30 +18498,6 @@ static TypeTableEntry *ir_analyze_instruction_check_statement_is_void(IrAnalyze return ira->codegen->builtin_types.entry_void; } -static TypeTableEntry *ir_analyze_instruction_can_implicit_cast(IrAnalyze *ira, - IrInstructionCanImplicitCast *instruction) -{ - IrInstruction *type_value = instruction->type_value->other; - TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); - if (type_is_invalid(type_entry)) - return ira->codegen->builtin_types.entry_invalid; - - IrInstruction *target_value = instruction->target_value->other; - if (type_is_invalid(target_value->value.type)) - return ira->codegen->builtin_types.entry_invalid; - - ImplicitCastMatchResult result = ir_types_match_with_implicit_cast(ira, type_entry, target_value->value.type, - target_value); - - if (result == ImplicitCastMatchResultReportedError) { - zig_panic("TODO refactor implicit cast tester to return bool without reporting errors"); - } - - ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); - out_val->data.x_bool = (result == ImplicitCastMatchResultYes); - return ira->codegen->builtin_types.entry_bool; -} - static TypeTableEntry *ir_analyze_instruction_panic(IrAnalyze *ira, IrInstructionPanic *instruction) { IrInstruction *msg = instruction->msg->other; if (type_is_invalid(msg->value.type)) @@ -19808,8 +19831,6 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_check_switch_prongs(ira, (IrInstructionCheckSwitchProngs *)instruction); case IrInstructionIdCheckStatementIsVoid: return ir_analyze_instruction_check_statement_is_void(ira, (IrInstructionCheckStatementIsVoid *)instruction); - case IrInstructionIdCanImplicitCast: - return ir_analyze_instruction_can_implicit_cast(ira, (IrInstructionCanImplicitCast *)instruction); case IrInstructionIdDeclRef: return ir_analyze_instruction_decl_ref(ira, (IrInstructionDeclRef *)instruction); case IrInstructionIdPanic: @@ -20089,7 +20110,6 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdIntToEnum: case IrInstructionIdIntToErr: case IrInstructionIdErrToInt: - case IrInstructionIdCanImplicitCast: case IrInstructionIdDeclRef: case IrInstructionIdErrName: case IrInstructionIdTypeName: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 3c177a8bbf57..776ef64566ec 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -913,14 +913,6 @@ static void ir_print_tag_name(IrPrint *irp, IrInstructionTagName *instruction) { ir_print_other_instruction(irp, instruction->target); } -static void ir_print_can_implicit_cast(IrPrint *irp, IrInstructionCanImplicitCast *instruction) { - fprintf(irp->f, "@canImplicitCast("); - ir_print_other_instruction(irp, instruction->type_value); - fprintf(irp->f, ","); - ir_print_other_instruction(irp, instruction->target_value); - fprintf(irp->f, ")"); -} - static void ir_print_ptr_type(IrPrint *irp, IrInstructionPtrType *instruction) { fprintf(irp->f, "&"); if (instruction->align_value != nullptr) { @@ -1524,9 +1516,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdTagName: ir_print_tag_name(irp, (IrInstructionTagName *)instruction); break; - case IrInstructionIdCanImplicitCast: - ir_print_can_implicit_cast(irp, (IrInstructionCanImplicitCast *)instruction); - break; case IrInstructionIdPtrType: ir_print_ptr_type(irp, (IrInstructionPtrType *)instruction); break; diff --git a/src/main.cpp b/src/main.cpp index 9c36f9b09176..c63a143bff46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ static int usage(const char *arg0) { " build-exe [source] create executable from source or object files\n" " build-lib [source] create library from source or object files\n" " build-obj [source] create object from source or assembly\n" + " builtin show the source code of that @import(\"builtin\")\n" " run [source] create executable and run immediately\n" " translate-c [source] convert c code to zig code\n" " targets list available compilation targets\n" @@ -214,6 +215,7 @@ static Buf *resolve_zig_lib_dir(void) { enum Cmd { CmdInvalid, CmdBuild, + CmdBuiltin, CmdRun, CmdTest, CmdVersion, @@ -664,6 +666,8 @@ int main(int argc, char **argv) { out_type = OutTypeExe; } else if (strcmp(arg, "targets") == 0) { cmd = CmdTargets; + } else if (strcmp(arg, "builtin") == 0) { + cmd = CmdBuiltin; } else { fprintf(stderr, "Unrecognized command: %s\n", arg); return usage(arg0); @@ -681,6 +685,7 @@ int main(int argc, char **argv) { return usage(arg0); } break; + case CmdBuiltin: case CmdVersion: case CmdZen: case CmdTargets: @@ -727,6 +732,16 @@ int main(int argc, char **argv) { } switch (cmd) { + case CmdBuiltin: { + Buf *zig_lib_dir_buf = resolve_zig_lib_dir(); + CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, zig_lib_dir_buf); + Buf *builtin_source = codegen_generate_builtin_source(g); + if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) { + fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout))); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } case CmdRun: case CmdBuild: case CmdTranslateC: diff --git a/std/json.zig b/std/json.zig index 6cf83eef1aec..03b19a7fa411 100644 --- a/std/json.zig +++ b/std/json.zig @@ -324,7 +324,9 @@ pub const StreamingParser = struct { p.complete = true; p.state = State.TopLevelEnd; }, - else => {}, + else => { + p.state = State.ValueEnd; + }, } token.* = Token.initMarker(Token.Id.ObjectEnd); @@ -348,7 +350,9 @@ pub const StreamingParser = struct { p.complete = true; p.state = State.TopLevelEnd; }, - else => {}, + else => { + p.state = State.ValueEnd; + }, } token.* = Token.initMarker(Token.Id.ArrayEnd); @@ -970,7 +974,7 @@ pub fn validate(s: []const u8) bool { var token1: ?Token = undefined; var token2: ?Token = undefined; - p.feed(c, *token1, *token2) catch |err| { + p.feed(c, &token1, &token2) catch |err| { return false; }; } @@ -978,6 +982,10 @@ pub fn validate(s: []const u8) bool { return p.complete; } +test "json validate" { + debug.assert(validate("{}")); +} + const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const ArrayList = std.ArrayList; @@ -1230,7 +1238,7 @@ pub const Parser = struct { _ = p.stack.pop(); p.state = State.ObjectKey; }, - else => { + Token.Id.ObjectEnd, Token.Id.ArrayEnd => { unreachable; }, } @@ -1270,7 +1278,7 @@ pub const Parser = struct { Token.Id.Null => { try array.append(Value.Null); }, - else => { + Token.Id.ObjectEnd => { unreachable; }, } diff --git a/std/json_test.zig b/std/json_test.zig index cb054d8e4ebf..8c8862441aa2 100644 --- a/std/json_test.zig +++ b/std/json_test.zig @@ -17,6 +17,16 @@ fn any(comptime s: []const u8) void { std.debug.assert(true); } +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Additional tests not part of test JSONTestSuite. + +test "y_trailing_comma_after_empty" { + ok( + \\{"1":[],"2":{},"3":"4"} + ); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// test "y_array_arraysWithSpaces" { diff --git a/std/os/index.zig b/std/os/index.zig index fe5ecc38bae2..57a181bb2acf 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -37,6 +37,18 @@ pub const path = @import("path.zig"); pub const File = @import("file.zig").File; pub const time = @import("time.zig"); +pub const DirData = switch (builtin.os) { + Os.windows => FileSearchData, + Os.macosx, Os.ios => struct { + fd: FileHandle, + darwin_seek: i64, + }, + Os.linux => struct { + fd: FileHandle, + }, + else => @compileError("Unsupported OS"), +}; + pub const FileMode = switch (builtin.os) { Os.windows => void, else => u32, @@ -61,6 +73,11 @@ pub const windowsLoadDll = windows_util.windowsLoadDll; pub const windowsUnloadDll = windows_util.windowsUnloadDll; pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; +pub const FileSearchData = windows_util.FileSearchData; +pub const FindFirstFile = windows_util.windowsFindFirstFile; +pub const FindClose = windows_util.windowsFindClose; +pub const FindNextFile = windows_util.windowsFindNextFile; + pub const WindowsWaitError = windows_util.WaitError; pub const WindowsOpenError = windows_util.OpenError; pub const WindowsWriteError = windows_util.WriteError; @@ -734,7 +751,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: } } -pub fn deleteFile(allocator: *Allocator, file_path: []const u8) !void { +pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteTreeError!void { if (builtin.os == Os.windows) { return deleteFileWindows(allocator, file_path); } else { @@ -1028,21 +1045,32 @@ pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) !void { mem.copy(u8, path_buf, dir_path); path_buf[dir_path.len] = 0; - const err = posix.getErrno(posix.rmdir(path_buf.ptr)); - if (err > 0) { - return switch (err) { - posix.EACCES, posix.EPERM => error.AccessDenied, - posix.EBUSY => error.FileBusy, - posix.EFAULT, posix.EINVAL => unreachable, - posix.ELOOP => error.SymLinkLoop, - posix.ENAMETOOLONG => error.NameTooLong, - posix.ENOENT => error.FileNotFound, - posix.ENOMEM => error.SystemResources, - posix.ENOTDIR => error.NotDir, - posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty, - posix.EROFS => error.ReadOnlyFileSystem, - else => unexpectedErrorPosix(err), - }; + switch (builtin.os) { + Os.windows => { + if (!try windows_util.windowsPathIsDirectoryEmpty(allocator, dir_path)) { + return error.DirNotEmpty; + } + _ = try windows_util.windowsRemoveDirectory(allocator, dir_path); + }, + Os.linux, Os.macosx, Os.ios => { + const err = posix.getErrno(posix.rmdir(path_buf.ptr)); + if (err > 0) { + return switch (err) { + posix.EACCES, posix.EPERM => error.AccessDenied, + posix.EBUSY => error.FileBusy, + posix.EFAULT, posix.EINVAL => unreachable, + posix.ELOOP => error.SymLinkLoop, + posix.ENAMETOOLONG => error.NameTooLong, + posix.ENOENT => error.FileNotFound, + posix.ENOMEM => error.SystemResources, + posix.ENOTDIR => error.NotDir, + posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty, + posix.EROFS => error.ReadOnlyFileSystem, + else => unexpectedErrorPosix(err), + }; + } + }, + else => @compileError("Dir.open is not supported for this platform"), } } @@ -1072,6 +1100,7 @@ const DeleteTreeError = error{ DirNotEmpty, Unexpected, }; + pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { start_over: while (true) { var got_access_denied = false; @@ -1083,41 +1112,17 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! error.IsDir => {}, error.AccessDenied => got_access_denied = true, - error.OutOfMemory, - error.SymLinkLoop, - error.NameTooLong, - error.SystemResources, - error.ReadOnlyFileSystem, - error.NotDir, - error.FileSystem, - error.FileBusy, - error.Unexpected, - => return err, + else => return err, } { var dir = Dir.open(allocator, full_path) catch |err| switch (err) { - error.NotDir => { + error.NotDir, error.PathNotFound => { if (got_access_denied) { return error.AccessDenied; } continue :start_over; }, - - error.OutOfMemory, - error.AccessDenied, - error.FileTooBig, - error.IsDir, - error.SymLinkLoop, - error.ProcessFdQuotaExceeded, - error.NameTooLong, - error.SystemFdQuotaExceeded, - error.NoDevice, - error.PathNotFound, - error.SystemResources, - error.NoSpaceLeft, - error.PathAlreadyExists, - error.Unexpected, - => return err, + else => return err, }; defer dir.close(); @@ -1139,18 +1144,12 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! } pub const Dir = struct { - fd: i32, - darwin_seek: darwin_seek_t, + data: DirData, allocator: *Allocator, buf: []u8, index: usize, end_index: usize, - const darwin_seek_t = switch (builtin.os) { - Os.macosx, Os.ios => i64, - else => void, - }; - pub const Entry = struct { name: []const u8, kind: Kind, @@ -1168,26 +1167,26 @@ pub const Dir = struct { }; }; - pub fn open(allocator: *Allocator, dir_path: []const u8) !Dir { - const fd = switch (builtin.os) { - Os.windows => @compileError("TODO support Dir.open for windows"), - Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0), - Os.macosx, Os.ios => try posixOpen( - allocator, - dir_path, - posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, - 0, - ), + pub fn open(allocator: *Allocator, dir_path: []const u8) PosixOpenError!Dir { + const dirData = switch (builtin.os) { + Os.windows => try FindFirstFile(allocator, dir_path), + Os.linux => DirData { + .fd = try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0), + }, + Os.macosx, Os.ios => DirData { + .fd = try posixOpen( + allocator, + dir_path, + posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, + 0, + ), + .darwin_seek = 0, + }, else => @compileError("Dir.open is not supported for this platform"), }; - const darwin_seek_init = switch (builtin.os) { - Os.macosx, Os.ios => 0, - else => {}, - }; return Dir{ .allocator = allocator, - .fd = fd, - .darwin_seek = darwin_seek_init, + .data = dirData, .index = 0, .end_index = 0, .buf = []u8{}, @@ -1195,8 +1194,16 @@ pub const Dir = struct { } pub fn close(self: *Dir) void { - self.allocator.free(self.buf); - os.close(self.fd); + switch (builtin.os) { + Os.windows => { + self.allocator.free(self.buf); + _ = FindClose(&self.data); + }, + else => { + os.close(self.data.fd); + self.allocator.free(self.buf); + } + } } /// Memory such as file names referenced in this returned entry becomes invalid @@ -1218,7 +1225,7 @@ pub const Dir = struct { } while (true) { - const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len, &self.darwin_seek); + const result = posix.getdirentries64(self.data.fd, self.buf.ptr, self.buf.len, &self.data.darwin_seek); const err = posix.getErrno(result); if (err > 0) { switch (err) { @@ -1266,7 +1273,52 @@ pub const Dir = struct { } fn nextWindows(self: *Dir) !?Entry { - @compileError("TODO support Dir.next for windows"); + if (self.data.end) return null; + + if (self.buf.len == 0) { + if (windows.TCHAR == u16) { + self.buf = try self.allocator.alloc(u8, windows.MAX_PATH * 2); + } else { + self.buf = try self.allocator.alloc(u8, windows.MAX_PATH); + } + } + + // I don't think it can be anything else? + // May be wrong though + const kind = switch (self.data.data.dwFileAttributes) { + windows.FILE_ATTRIBUTE_NORMAL => Entry.Kind.File, + windows.FILE_ATTRIBUTE_DIRECTORY => Entry.Kind.Directory, + windows.FILE_ATTRIBUTE_REPARSE_POINT => Entry.Kind.SymLink, + else => Entry.Kind.Unknown, + }; + + var bufCount: usize = 0; + + if (windows.TCHAR == u16) { + // Keeping in mind that W_CHAR states it is only ever one 'codepoint' byte + // so we don't have to check for that, however it could convert into + // just a single utf8 byte or two so we have to properly encode it. + var count: usize = 0; + while (self.data.data.cFileName[count] != 0) : (count += 1) { + // Check codepoint + const codepoint = self.data.data.cFileName[count]; + if (codepoint > 0x10FFFF) return error.InvalidUtf16; + bufCount += std.unicode.utf8Encode(u32(codepoint), self.buf[bufCount..]); + } + } else { + var slice = self.data.data.cFileName[0..]; + mem.copy(u8, self.buf, slice[0..slice.len-1]); + bufCount = slice.len; + } + + const entry = Entry { + .name = self.buf[0..bufCount], + .kind = kind, + }; + + _ = FindNextFile(&self.data); + + return entry; } fn nextLinux(self: *Dir) !?Entry { @@ -1277,7 +1329,7 @@ pub const Dir = struct { } while (true) { - const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len); + const result = posix.getdents(self.data.fd, self.buf.ptr, self.buf.len); const err = posix.getErrno(result); if (err > 0) { switch (err) { diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index 0934c3fd900f..5d19432bbc8c 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -146,6 +146,10 @@ pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) DWORD; +pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCTSTR) BOOL; + +pub extern "shlwapi" stdcallcc fn PathIsDirectoryEmptyA(pszPath: LPCTSTR) BOOL; + pub extern "kernel32" stdcallcc fn WriteFile( in_hFile: HANDLE, in_lpBuffer: *const c_void, @@ -163,6 +167,12 @@ pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lp pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL; +pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: LPWIN32_FIND_DATA) HANDLE; + +pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL; + +pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: LPWIN32_FIND_DATA) BOOL; + pub const PROV_RSA_FULL = 1; pub const BOOL = c_int; @@ -180,6 +190,7 @@ pub const LPBYTE = *BYTE; pub const LPCH = *CHAR; pub const LPCSTR = [*]const CHAR; pub const LPCTSTR = [*]const TCHAR; +pub const LPWIN32_FIND_DATA = *WIN32_FIND_DATA; pub const LPCVOID = *const c_void; pub const LPDWORD = *DWORD; pub const LPSTR = [*]CHAR; @@ -298,8 +309,10 @@ pub const FILE_ATTRIBUTE_HIDDEN = 0x2; pub const FILE_ATTRIBUTE_NORMAL = 0x80; pub const FILE_ATTRIBUTE_OFFLINE = 0x1000; pub const FILE_ATTRIBUTE_READONLY = 0x1; +pub const FILE_ATTRIBUTE_DIRECTORY = 0x10; pub const FILE_ATTRIBUTE_SYSTEM = 0x4; pub const FILE_ATTRIBUTE_TEMPORARY = 0x100; +pub const FILE_ATTRIBUTE_REPARSE_POINT = 0x400; pub const PROCESS_INFORMATION = extern struct { hProcess: HANDLE, @@ -308,6 +321,19 @@ pub const PROCESS_INFORMATION = extern struct { dwThreadId: DWORD, }; +pub const WIN32_FIND_DATA = extern struct { + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + dwReserved0: DWORD, + dwReserved1: DWORD, + cFileName: [MAX_PATH]TCHAR, + cAlternateFileName: [14]TCHAR, +}; + pub const STARTUPINFOA = extern struct { cb: DWORD, lpReserved: ?LPSTR, diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index 7170346108f8..9463f683c48a 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -118,6 +118,60 @@ pub fn windowsOpen( return result; } +pub const FileSearchData = struct { + fd: windows.HANDLE, + data: windows.WIN32_FIND_DATA, + end: bool, +}; + +pub fn windowsPathIsDirectoryEmpty(allocator: *mem.Allocator, dir_path: []const u8) !bool { + const path_with_null = try cstr.addNullByte(allocator, dir_path); + defer allocator.free(path_with_null); + return windows.PathIsDirectoryEmptyA(path_with_null.ptr) == 1; +} + +pub fn windowsRemoveDirectory(allocator: *mem.Allocator, dir_path: []const u8) !bool { + const path_with_null = try cstr.addNullByte(allocator, dir_path); + defer allocator.free(path_with_null); + return windows.RemoveDirectoryA(path_with_null.ptr) == 1; +} + +pub fn windowsFindFirstFile(allocator: *mem.Allocator, dir_path: []const u8) !FileSearchData { + const path_with_wild_and_null = try allocator.alloc(u8, dir_path.len + 3); + mem.copy(u8, path_with_wild_and_null, dir_path); + mem.copy(u8, path_with_wild_and_null[dir_path.len..], "\\*"); + path_with_wild_and_null[dir_path.len + 2] = 0; + defer allocator.free(path_with_wild_and_null); + + var data : FileSearchData = undefined; + data.end = false; + data.fd = windows.FindFirstFileA(path_with_wild_and_null.ptr, &data.data); + + if (data.fd == windows.INVALID_HANDLE_VALUE) { + const err = windows.GetLastError(); + return switch (err) { + // May return more, docs not clear enough + windows.ERROR.FILE_NOT_FOUND => error.PathNotFound, + else => os.unexpectedErrorWindows(err), + }; + } + + return data; +} + +pub fn windowsFindNextFile(data: *FileSearchData) bool { + if (windows.FindNextFileA(data.fd, &data.data) != 1 or data.fd == windows.INVALID_HANDLE_VALUE) { + data.end = true; + return false; + } + + return true; +} + +pub fn windowsFindClose(data: *FileSearchData) bool { + return windows.FindClose(data.fd) == 1; +} + /// Caller must free result. pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u8 { // count bytes needed diff --git a/test/cases/array.zig b/test/cases/array.zig index ef919b27bd88..b481261b4f21 100644 --- a/test/cases/array.zig +++ b/test/cases/array.zig @@ -116,6 +116,15 @@ test "array len property" { assert(@typeOf(x).len == 5); } +test "array len field" { + var arr = [4]u8{ 0, 0, 0, 0 }; + var ptr = &arr; + assert(arr.len == 4); + comptime assert(arr.len == 4); + assert(ptr.len == 4); + comptime assert(ptr.len == 4); +} + test "single-item pointer to array indexing and slicing" { testSingleItemPtrArrayIndexSlice(); comptime testSingleItemPtrArrayIndexSlice(); @@ -143,4 +152,3 @@ fn testImplicitCastSingleItemPtr() void { slice[0] += 1; assert(byte == 101); } - diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 7358a4ffd851..c3ef24cd7891 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -384,3 +384,19 @@ test "const slice widen cast" { assert(@bitCast(u32, bytes) == 0x12121212); } + +test "single-item pointer of array to slice and to unknown length pointer" { + testCastPtrOfArrayToSliceAndPtr(); + comptime testCastPtrOfArrayToSliceAndPtr(); +} + +fn testCastPtrOfArrayToSliceAndPtr() void { + var array = "ao" ++ "eu"; // TODO https://github.com/ziglang/zig/issues/1076 + const x: [*]u8 = &array; + x[0] += 1; + assert(mem.eql(u8, array[0..], "boeu")); + const y: []u8 = &array; + y[0] += 1; + assert(mem.eql(u8, array[0..], "coeu")); +} + diff --git a/test/cases/eval.zig b/test/cases/eval.zig index 461408afeacc..9612466a8624 100644 --- a/test/cases/eval.zig +++ b/test/cases/eval.zig @@ -610,3 +610,16 @@ test "slice of type" { } } } + +const Wrapper = struct { + T: type, +}; + +fn wrap(comptime T: type) Wrapper { + return Wrapper{ .T = T }; +} + +test "function which returns struct with type field causes implicit comptime" { + const ty = wrap(i32).T; + assert(ty == i32); +} diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 9450cf5e6ece..369d8e5cf3b5 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -523,14 +523,6 @@ test "@typeId" { } } -test "@canImplicitCast" { - comptime { - assert(@canImplicitCast(i64, i32(3))); - assert(!@canImplicitCast(i32, f32(1.234))); - assert(@canImplicitCast([]const u8, "aoeu")); - } -} - test "@typeName" { const Struct = struct {}; const Union = union { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index c995cd679e47..102c4e428d02 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3329,7 +3329,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { ".tmp_source.zig:9:4: error: variable of type 'comptime_float' must be const or comptime", ".tmp_source.zig:10:4: error: variable of type '(block)' must be const or comptime", ".tmp_source.zig:11:4: error: variable of type '(null)' must be const or comptime", - ".tmp_source.zig:12:4: error: variable of type 'Opaque' must be const or comptime", + ".tmp_source.zig:12:4: error: variable of type 'Opaque' not allowed", ".tmp_source.zig:13:4: error: variable of type 'type' must be const or comptime", ".tmp_source.zig:14:4: error: variable of type '(namespace)' must be const or comptime", ".tmp_source.zig:15:4: error: variable of type '(bound fn(*const Foo) void)' must be const or comptime",