Browse Source

Merge branch 'master' of https://github.com/odin-lang/Odin

gingerBill 2 months ago
parent
commit
002b50360c
56 changed files with 1068 additions and 497 deletions
  1. 1 0
      base/intrinsics/intrinsics.odin
  2. 9 0
      base/runtime/core_builtin.odin
  3. 74 0
      base/sanitizer/memory.odin
  4. 3 3
      core/encoding/cbor/cbor.odin
  5. 36 0
      core/encoding/cbor/marshal.odin
  6. 25 0
      core/encoding/cbor/unmarshal.odin
  7. 4 4
      core/encoding/json/marshal.odin
  8. 2 1
      core/encoding/xml/xml_reader.odin
  9. 4 4
      core/fmt/fmt.odin
  10. 8 8
      core/io/util.odin
  11. 1 1
      core/math/big/private.odin
  12. 10 5
      core/math/fixed/fixed.odin
  13. 1 1
      core/net/url.odin
  14. 1 1
      core/os/os.odin
  15. 19 14
      core/os/os2/errors.odin
  16. 1 0
      core/os/os2/file_linux.odin
  17. 1 1
      core/os/os2/file_util.odin
  18. 133 63
      core/os/os2/user.odin
  19. 183 0
      core/os/os2/user_posix.odin
  20. 79 0
      core/os/os2/user_windows.odin
  21. 3 3
      core/os/os_freebsd.odin
  22. 3 3
      core/os/os_haiku.odin
  23. 4 4
      core/os/os_linux.odin
  24. 3 3
      core/os/os_netbsd.odin
  25. 3 3
      core/os/os_openbsd.odin
  26. 0 46
      core/sort/sort.odin
  27. 15 15
      core/strconv/decimal/decimal.odin
  28. 38 0
      core/strconv/deprecated.odin
  29. 7 7
      core/strconv/generic_float.odin
  30. 15 15
      core/strconv/integers.odin
  31. 139 139
      core/strconv/strconv.odin
  32. 6 6
      core/strings/builder.odin
  33. 82 64
      core/strings/strings.odin
  34. 0 1
      core/sys/darwin/Foundation/NSApplication.odin
  35. 1 1
      core/sys/linux/sys.odin
  36. 0 8
      core/sys/windows/util.odin
  37. 3 3
      core/sys/windows/ws2_32.odin
  38. 1 6
      core/time/timezone/tzif.odin
  39. 2 0
      src/check_builtin.cpp
  40. 1 0
      src/check_decl.cpp
  41. 14 0
      src/check_stmt.cpp
  42. 9 3
      src/checker.cpp
  43. 1 0
      src/checker.hpp
  44. 2 0
      src/checker_builtin_procs.hpp
  45. 1 0
      src/entity.cpp
  46. 11 0
      src/llvm_backend_const.cpp
  47. 1 1
      src/llvm_backend_proc.cpp
  48. 52 52
      src/main.cpp
  49. 2 2
      src/parser.cpp
  50. 2 2
      src/timings.cpp
  51. 28 0
      tests/core/encoding/cbor/test_core_cbor.odin
  52. 1 1
      tests/core/math/test_core_math.odin
  53. 1 0
      tests/issues/run.bat
  54. 1 0
      tests/issues/run.sh
  55. 18 0
      tests/issues/test_issue_4364.odin
  56. 3 3
      vendor/directx/dxc/dxcapi.odin

+ 1 - 0
base/intrinsics/intrinsics.odin

@@ -169,6 +169,7 @@ type_is_union            :: proc($T: typeid) -> bool ---
 type_is_enum             :: proc($T: typeid) -> bool ---
 type_is_proc             :: proc($T: typeid) -> bool ---
 type_is_bit_set          :: proc($T: typeid) -> bool ---
+type_is_bit_field        :: proc($T: typeid) -> bool ---
 type_is_simd_vector      :: proc($T: typeid) -> bool ---
 type_is_matrix           :: proc($T: typeid) -> bool ---
 

+ 9 - 0
base/runtime/core_builtin.odin

@@ -648,6 +648,9 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
 
 @builtin
 inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(index >= 0, "Index must be positive.", loc)
+	}
 	if array == nil {
 		return
 	}
@@ -666,6 +669,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
 
 @builtin
 inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(index >= 0, "Index must be positive.", loc)
+	}
 	if array == nil {
 		return
 	}
@@ -689,6 +695,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
 
 @builtin
 inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(index >= 0, "Index must be positive.", loc)
+	}
 	if array == nil {
 		return
 	}

+ 74 - 0
base/sanitizer/memory.odin

@@ -0,0 +1,74 @@
+#+no-instrumentation
+package sanitizer
+
+@(private="file")
+MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS
+
+@(private="file")
+@(default_calling_convention="system")
+foreign {
+	__msan_unpoison :: proc(addr: rawptr, size: uint) ---
+}
+
+/*
+Marks a slice as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access any
+address within the slice as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
+	when MSAN_ENABLED {
+		__msan_unpoison(raw_data(region),  size_of(E) * len(region))
+	}
+}
+
+/*
+Marks a pointer as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within the region the pointer points to as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
+	when MSAN_ENABLED {
+		__msan_unpoison(ptr, size_of(T))
+	}
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within this range as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
+	when MSAN_ENABLED {
+		__msan_unpoison(ptr, uint(len))
+	}
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within this range as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+	when MSAN_ENABLED {
+		__msan_unpoison(ptr, len)
+	}
+}
+
+memory_unpoison :: proc {
+	memory_unpoison_slice,
+	memory_unpoison_ptr,
+	memory_unpoison_rawptr,
+	memory_unpoison_rawptr_uint,
+}

+ 3 - 3
core/encoding/cbor/cbor.odin

@@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i
 	// which we want for the diagnostic format.
 	case f16:
 		buf: [64]byte
-		str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
+		str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		io.write_string(w, str) or_return
 	case f32:
 		buf: [128]byte
-		str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
+		str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		io.write_string(w, str) or_return
 	case f64:
 		buf: [256]byte
-		str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
+		str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		io.write_string(w, str) or_return
 

+ 36 - 0
core/encoding/cbor/marshal.odin

@@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
 		case:
 			panic("unknown bit_size size")
 		}
+	case runtime.Type_Info_Matrix:
+		count := info.column_count * info.elem_stride
+		err_conv(_encode_u64(e, u64(count), .Array)) or_return
+
+		if impl, ok := _tag_implementations_type[info.elem.id]; ok {
+			for i in 0..<count {
+				data := uintptr(v.data) + uintptr(i*info.elem_size)
+				impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
+			}
+			return
+		}
+
+		elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
+		for i in 0..<count {
+			data := uintptr(v.data) + uintptr(i*info.elem_size)
+			_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
+		}
+		return
+
+	case runtime.Type_Info_Simd_Vector:
+		err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
+
+		if impl, ok := _tag_implementations_type[info.elem.id]; ok {
+			for i in 0..<info.count {
+				data := uintptr(v.data) + uintptr(i*info.elem_size)
+				impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
+			}
+			return
+		}
+
+		elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
+		for i in 0..<info.count {
+			data := uintptr(v.data) + uintptr(i*info.elem_size)
+			_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
+		}
+		return
 	}
 
 	return _unsupported(v.id, nil)

+ 25 - 0
core/encoding/cbor/unmarshal.odin

@@ -591,6 +591,31 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 		if out_of_space { return _unsupported(v, hdr) }
 		return
 
+	case reflect.Type_Info_Matrix:
+		count := t.column_count * t.elem_stride
+		length, _ := err_conv(_decode_len_container(d, add)) or_return
+		if length > count {
+			return _unsupported(v, hdr)
+		}
+
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
+
+		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
+		if out_of_space { return _unsupported(v, hdr) }
+		return
+
+	case reflect.Type_Info_Simd_Vector:
+		length, _ := err_conv(_decode_len_container(d, add)) or_return
+		if length > t.count {
+			return _unsupported(v, hdr)
+		}
+
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
+
+		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
+		if out_of_space { return _unsupported(v, hdr) }
+		return
+
 	case: return _unsupported(v, hdr)
 	}
 }

+ 4 - 4
core/encoding/json/marshal.odin

@@ -108,13 +108,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 		if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
 			switch i in a {
 			case u8, u16, u32, u64, u128:
-				s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
+				s = strconv.write_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
 
 			case:
-				s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
+				s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
 			}
 		} else {
-			s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
+			s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
 		}
 
 		io.write_string(w, s) or_return
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 						case runtime.Type_Info_Integer:
 							buf: [40]byte
 							u := cast_any_int_to_u128(ka)
-							name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
+							name = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
 							
 							opt_write_key(w, opt, name) or_return
 						case: return .Unsupported_Type

+ 2 - 1
core/encoding/xml/xml_reader.odin

@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 		data = bytes.clone(data)
 	}
 
-	t := &Tokenizer{}
+	t := new(Tokenizer)
 	init(t, string(data), path, error_handler)
 
 	doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
 	}
 	delete(doc.strings_to_free)
 
+	free(doc.tokenizer)
 	free(doc)
 }
 

+ 4 - 4
core/fmt/fmt.odin

@@ -1122,7 +1122,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
 	flags: strconv.Int_Flags
 	if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
 	if fi.plus                           { flags += {.Plus}   }
-	s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
+	s := strconv.write_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
 	prev_zero := fi.zero
 	defer fi.zero = prev_zero
 	fi.zero = false
@@ -1207,7 +1207,7 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
 	flags: strconv.Int_Flags
 	if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
 	if fi.plus                           { flags += {.Plus}   }
-	s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
+	s := strconv.write_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
 
 	if fi.hash && fi.zero && fi.indent == 0 {
 		c: byte = 0
@@ -1272,7 +1272,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
 	}
 
 	buf: [256]byte
-	str := strconv.append_float(buf[:], amt, 'f', prec, 64)
+	str := strconv.write_float(buf[:], amt, 'f', prec, 64)
 
 	// Add the unit at the end.
 	copy(buf[len(str):], units[off:off+unit_len])
@@ -1424,7 +1424,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b
 	buf: [386]byte
 
 	// Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>".
-	str := strconv.append_float(buf[:], v, float_fmt, prec, bit_size)
+	str := strconv.write_float(buf[:], v, float_fmt, prec, bit_size)
 
 	if !fi.plus {
 		// Strip sign from "+<value>" but not "+Inf".

+ 8 - 8
core/io/util.odin

@@ -22,12 +22,12 @@ write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64, n_wri
 
 write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [32]byte
-	s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
+	s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
 	return write_string(w, s, n_written)
 }
 write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [32]byte
-	s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
+	s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
 	return write_string(w, s, n_written)
 }
 
@@ -40,18 +40,18 @@ write_int :: proc(w: Writer, i: int, base: int = 10, n_written: ^int = nil) -> (
 
 write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [39]byte
-	s := strconv.append_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
+	s := strconv.write_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
 	return write_string(w, s, n_written)
 }
 write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [40]byte
-	s := strconv.append_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
+	s := strconv.write_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
 	return write_string(w, s, n_written)
 }
 write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [386]byte
 
-	str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
+	str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
 	s := buf[:len(str)+1]
 	if s[1] == '+' || s[1] == '-' {
 		s = s[1:]
@@ -67,7 +67,7 @@ write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: E
 write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [386]byte
 
-	str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
+	str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
 	s := buf[:len(str)+1]
 	if s[1] == '+' || s[1] == '-' {
 		s = s[1:]
@@ -83,7 +83,7 @@ write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: E
 write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [386]byte
 
-	str := strconv.append_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
+	str := strconv.write_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
 	s := buf[:len(str)+1]
 	if s[1] == '+' || s[1] == '-' {
 		s = s[1:]
@@ -130,7 +130,7 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^
 			write_string(w, `\x`, &n) or_return
 			
 			buf: [2]byte
-			s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
+			s := strconv.write_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			case 0: 
 				write_string(w, "00", &n) or_return

+ 1 - 1
core/math/big/private.odin

@@ -1370,8 +1370,8 @@ _private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator :=
 
 /*
 	Slower bit-bang division... also smaller.
+	Prefer `_int_div_school` for speed.
 */
-@(deprecated="Use `_int_div_school`, it's 3.5x faster.")
 _private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) {
 
 	ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{}

+ 10 - 5
core/math/fixed/fixed.odin

@@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing {
 }
 
 @(require_results)
-append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
+write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
 	Integer_Width :: 8*size_of(Backing) - Fraction_Width
 
 	x := x
@@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
 
 		when size_of(Backing) < 16 {
 			T :: u64
-			append_uint :: strconv.append_uint
+			write_uint :: strconv.write_uint
 		} else {
 			T :: u128
-			append_uint :: strconv.append_u128
+			write_uint :: strconv.write_u128
 		}
 
 		integer := T(x.i) >> Fraction_Width
 		fraction := T(x.i) & (1<<Fraction_Width - 1)
 
-		s := append_uint(buf[i:], integer, 10)
+		s := write_uint(buf[i:], integer, 10)
 		i += len(s)
 		if fraction != 0 {
 			buf[i] = '.'
@@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
 @(require_results)
 to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
 	buf: [48]byte
-	s := append(buf[:], x)
+	s := write(buf[:], x)
 	str := make([]byte, len(s), allocator)
 	copy(str, s)
 	return string(str)
@@ -294,3 +294,8 @@ _power_of_two_table := [129]string{
 	"85070591730234615865843651857942052864",
 	"170141183460469231731687303715884105728",
 }
+
+@(deprecated="Use write instead")
+append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
+	return write(dst, x)
+}

+ 1 - 1
core/net/url.odin

@@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string {
 			bytes, n := utf8.encode_rune(ch)
 			for byte in bytes[:n] {
 				buf: [2]u8 = ---
-				t := strconv.append_int(buf[:], i64(byte), 16)
+				t := strconv.write_int(buf[:], i64(byte), 16)
 				strings.write_rune(&b, '%')
 				strings.write_string(&b, t)
 			}

+ 1 - 1
core/os/os.odin

@@ -57,7 +57,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
 		if r < 32 {
 			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			b: [2]byte
-			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
+			s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
 			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }

+ 19 - 14
core/os/os2/errors.odin

@@ -27,6 +27,9 @@ General_Error :: enum u32 {
 
 	Pattern_Has_Separator,
 
+	No_HOME_Variable,
+	Wordexp_Failed,
+
 	Unsupported,
 }
 
@@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string {
 	case General_Error:
 		switch e {
 		case .None: return ""
-		case .Permission_Denied: return "permission denied"
-		case .Exist:             return "file already exists"
-		case .Not_Exist:         return "file does not exist"
-		case .Closed:            return "file already closed"
-		case .Timeout:           return "i/o timeout"
-		case .Broken_Pipe:       return "Broken pipe"
-		case .No_Size:           return "file has no definite size"
-		case .Invalid_File:      return "invalid file"
-		case .Invalid_Dir:       return "invalid directory"
-		case .Invalid_Path:      return "invalid path"
-		case .Invalid_Callback:  return "invalid callback"
-		case .Invalid_Command:   return "invalid command"
-		case .Unsupported:       return "unsupported"
-		case .Pattern_Has_Separator: return "pattern has separator"
+		case .Permission_Denied:      return "permission denied"
+		case .Exist:                  return "file already exists"
+		case .Not_Exist:              return "file does not exist"
+		case .Closed:                 return "file already closed"
+		case .Timeout:                return "i/o timeout"
+		case .Broken_Pipe:            return "Broken pipe"
+		case .No_Size:                return "file has no definite size"
+		case .Invalid_File:           return "invalid file"
+		case .Invalid_Dir:            return "invalid directory"
+		case .Invalid_Path:           return "invalid path"
+		case .Invalid_Callback:       return "invalid callback"
+		case .Invalid_Command:        return "invalid command"
+		case .Unsupported:            return "unsupported"
+		case .Pattern_Has_Separator:  return "pattern has separator"
+		case .No_HOME_Variable:       return "no $HOME variable"
+		case .Wordexp_Failed:         return "posix.wordexp was unable to expand"
 		}
 	case io.Error:
 		switch e {

+ 1 - 0
core/os/os2/file_linux.odin

@@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
 	return
 }
 
+@(no_sanitize_memory)
 _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 	// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
 	//       eliminate the need for the _read_entire_pseudo_file procs.

+ 1 - 1
core/os/os2/file_util.odin

@@ -59,7 +59,7 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
 		if r < 32 {
 			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			b: [2]byte
-			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
+			s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
 			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }

+ 133 - 63
core/os/os2/user.odin

@@ -2,78 +2,148 @@ package os2
 
 import "base:runtime"
 
+// ```
+// Windows:  C:\Users\Alice
+// macOS:    /Users/Alice
+// Linux:    /home/alice
+// ```
+@(require_results)
+user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_home_dir(allocator)
+}
+
+// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches
+//
+// Sometimes deleted for system maintenance
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local
+// macOS:    /Users/Alice/Library/Caches
+// Linux:    /home/alice/.cache
+// ```
 @(require_results)
 user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	return _user_cache_dir(allocator)
+}
+
+// User-hidden application data
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
+// macOS:    /Users/Alice/Library/Application Support
+// Linux:    /home/alice/.local/share
+// ```
+//
+// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
+@(require_results)
+user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
+	return _user_data_dir(allocator, roaming)
+}
 
-	#partial switch ODIN_OS {
-	case .Windows:
-		dir = get_env("LocalAppData", temp_allocator)
-		if dir != "" {
-			dir = clone_string(dir, temp_allocator) or_return
-		}
-	case .Darwin:
-		dir = get_env("HOME", temp_allocator)
-		if dir != "" {
-			dir = concatenate({dir, "/Library/Caches"}, temp_allocator) or_return
-		}
-	case: // All other UNIX systems
-		dir = get_env("XDG_CACHE_HOME", allocator)
-		if dir == "" {
-			dir = get_env("HOME", temp_allocator)
-			if dir == "" {
-				return
-			}
-			dir = concatenate({dir, "/.cache"}, temp_allocator) or_return
-		}
-	}
-	if dir == "" {
-		err = .Invalid_Path
-	}
-	return
+// Non-essential application data, e.g. history, ui layout state
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local
+// macOS:    /Users/Alice/Library/Application Support
+// Linux:    /home/alice/.local/state
+// ```
+@(require_results)
+user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_state_dir(allocator)
 }
 
+// Application log files
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local
+// macOS:    /Users/Alice/Library/Logs
+// Linux:    /home/alice/.local/state
+// ```
 @(require_results)
-user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_log_dir(allocator)
+}
 
-	#partial switch ODIN_OS {
-	case .Windows:
-		dir = get_env("AppData", temp_allocator)
-		if dir != "" {
-			dir = clone_string(dir, allocator) or_return
-		}
-	case .Darwin:
-		dir = get_env("HOME", temp_allocator)
-		if dir != "" {
-			dir = concatenate({dir, "/.config"}, allocator) or_return
-		}
-	case: // All other UNIX systems
-		dir = get_env("XDG_CONFIG_HOME", allocator)
-		if dir == "" {
-			dir = get_env("HOME", temp_allocator)
-			if dir == "" {
-				return
-			}
-			dir = concatenate({dir, "/.config"}, allocator) or_return
-		}
-	}
-	if dir == "" {
-		err = .Invalid_Path
-	}
-	return
+// Application settings/preferences
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
+// macOS:    /Users/Alice/Library/Application Support
+// Linux:    /home/alice/.config
+// ```
+//
+// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
+@(require_results)
+user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
+	return _user_config_dir(allocator, roaming)
 }
 
+// ```
+// Windows:  C:\Users\Alice\Music
+// macOS:    /Users/Alice/Music
+// Linux:    /home/alice/Music
+// ```
 @(require_results)
-user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	env := "HOME"
-	#partial switch ODIN_OS {
-	case .Windows:
-		env = "USERPROFILE"
-	}
-	if v := get_env(env, allocator); v != "" {
-		return v, nil
-	}
-	return "", .Invalid_Path
+user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_music_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Desktop
+// macOS:    /Users/Alice/Desktop
+// Linux:    /home/alice/Desktop
+// ```
+@(require_results)
+user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_desktop_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Documents
+// macOS:    /Users/Alice/Documents
+// Linux:    /home/alice/Documents
+// ```
+@(require_results)
+user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_documents_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Downloads
+// macOS:    /Users/Alice/Downloads
+// Linux:    /home/alice/Downloads
+// ```
+@(require_results)
+user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_downloads_dir(allocator)
 }
 
+// ```
+// Windows:  C:\Users\Alice\Pictures
+// macOS:    /Users/Alice/Pictures
+// Linux:    /home/alice/Pictures
+// ```
+@(require_results)
+user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_pictures_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Public
+// macOS:    /Users/Alice/Public
+// Linux:    /home/alice/Public
+// ```
+@(require_results)
+user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_public_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Videos
+// macOS:    /Users/Alice/Movies
+// Linux:    /home/alice/Videos
+// ```
+@(require_results)
+user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_videos_dir(allocator)
+}

+ 183 - 0
core/os/os2/user_posix.odin

@@ -0,0 +1,183 @@
+#+build !windows
+package os2
+
+import "base:runtime"
+import "core:encoding/ini"
+import "core:strings"
+import "core:sys/posix"
+
+_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Caches", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator)
+	}
+}
+
+_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Application Support", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator)
+	}
+}
+
+_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Application Support", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
+	}
+}
+
+_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Logs", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
+	}
+}
+
+_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Application Support", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator)
+	}
+}
+
+_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Music", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator)
+	}
+}
+
+_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Desktop", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator)
+	}
+}
+
+_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Documents", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator)
+	}
+}
+
+_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Downloads", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator)
+	}
+}
+
+_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Pictures", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator)
+	}
+}
+
+_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Public", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator)
+	}
+}
+
+_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Movies", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator)
+	}
+}
+
+_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	if v := get_env("HOME", allocator); v != "" {
+		return v, nil
+	}
+	err = .No_HOME_Variable
+	return
+}
+
+_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	temp_allocator  := TEMP_ALLOCATOR_GUARD({ allocator })
+
+	if xdg_key == "" { // Darwin doesn't have XDG paths.
+		dir = get_env("HOME", temp_allocator)
+		if dir == "" {
+			err = .No_HOME_Variable
+			return
+		}
+		return concatenate({dir, fallback_suffix}, allocator)
+	} else {
+		if strings.ends_with(xdg_key, "_DIR") {
+			dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return
+		} else {
+			dir = get_env(xdg_key, allocator)
+		}
+
+		if dir == "" {
+			dir = get_env("HOME", temp_allocator)
+			if dir == "" {
+				err = .No_HOME_Variable
+				return
+			}
+			dir = concatenate({dir, fallback_suffix}, allocator) or_return
+		}
+		return
+	}
+}
+
+// If `<config-dir>/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""`
+_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	temp_allocator  := TEMP_ALLOCATOR_GUARD({ allocator })
+	config_dir      := user_config_dir(temp_allocator) or_return
+	user_dirs_path  := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return
+	content         := read_entire_file(user_dirs_path, temp_allocator) or_return
+
+	it := ini.Iterator{
+		section = "",
+		_src    = string(content),
+		options = ini.Options{
+			comment        = "#",
+			key_lower_case = false,
+		},
+	}
+
+	for k, v in ini.iterate(&it) {
+		if k == xdg_key {
+			we: posix.wordexp_t
+			defer posix.wordfree(&we)
+
+			if _err := posix.wordexp(strings.clone_to_cstring(v, temp_allocator), &we, nil); _err != nil || we.we_wordc != 1 {
+				return "", .Wordexp_Failed
+			}
+
+			return strings.clone_from_cstring(we.we_wordv[0], allocator)
+		}
+	}
+	return
+}

+ 79 - 0
core/os/os2/user_windows.odin

@@ -0,0 +1,79 @@
+package os2
+
+import "base:runtime"
+@(require) import win32 "core:sys/windows"
+
+_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_LocalAppData
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_LocalAppData
+	if roaming {
+		guid = win32.FOLDERID_RoamingAppData
+	}
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_config_dir :: _local_appdata_or_roaming
+_user_data_dir :: _local_appdata_or_roaming
+
+_user_state_dir :: _local_appdata
+_user_log_dir :: _local_appdata
+_user_cache_dir :: _local_appdata
+
+_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Profile
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Music
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Desktop
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Documents
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Downloads
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Pictures
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Public
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Videos
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
+	// See also `known_folders.odin` in `core:sys/windows` for the GUIDs.
+	path_w: win32.LPWSTR
+	res  := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w)
+	defer win32.CoTaskMemFree(path_w)
+
+	if res != 0 {
+		return "", .Invalid_Path
+	}
+
+	dir, _ = win32.wstring_to_utf8(path_w, -1, allocator)
+	return
+}

+ 3 - 3
core/os/os_freebsd.odin

@@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	return File_Time(modified), nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)

+ 3 - 3
core/os/os_haiku.odin

@@ -325,7 +325,7 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +339,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +353,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---

+ 4 - 4
core/os/os_linux.odin

@@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	return i64(res), nil
 }
 
-@(require_results)
+@(require_results, no_sanitize_memory)
 file_size :: proc(fd: Handle) -> (i64, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
@@ -794,7 +794,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
 	return File_Time(modified), nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---

+ 3 - 3
core/os/os_netbsd.odin

@@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
 	return File_Time(modified), nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)

+ 3 - 3
core/os/os_openbsd.odin

@@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
 	return File_Time(modified), nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---

+ 0 - 46
core/sort/sort.odin

@@ -30,14 +30,6 @@ sort :: proc(it: Interface) {
 	_quick_sort(it, 0, n, max_depth(n))
 }
 
-
-@(deprecated="use slice.sort")
-slice :: proc(array: $T/[]$E) where ORD(E) {
-	_slice.sort(array)
-	// s := array;
-	// sort(slice_interface(&s));
-}
-
 slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) {
 	return Interface{
 		collection = rawptr(s),
@@ -80,31 +72,6 @@ reverse_sort :: proc(it: Interface) {
 	sort(reverse_interface(&it))
 }
 
-@(deprecated="use slice.reverse")
-reverse_slice :: proc(array: $T/[]$E) where ORD(E) {
-	_slice.reverse(array)
-	/*
-	s := array;
-	sort(Interface{
-		collection = rawptr(&s),
-		len = proc(it: Interface) -> int {
-			s := (^T)(it.collection);
-			return len(s^);
-		},
-		less = proc(it: Interface, i, j: int) -> bool {
-			s := (^T)(it.collection);
-			return s[j] < s[i]; // manual set up
-		},
-		swap = proc(it: Interface, i, j: int) {
-			s := (^T)(it.collection);
-			s[i], s[j] = s[j], s[i];
-		},
-	});
-	*/
-}
-
-
-
 is_sorted :: proc(it: Interface) -> bool {
 	n := it->len()
 	for i := n-1; i > 0; i -= 1 {
@@ -294,11 +261,6 @@ _insertion_sort :: proc(it: Interface, a, b: int) {
 	}
 }
 
-
-
-
-
-// @(deprecated="use sort.sort or slice.sort_by")
 bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	assert(f != nil)
 	count := len(array)
@@ -327,7 +289,6 @@ bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	}
 }
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	count := len(array)
 
@@ -355,7 +316,6 @@ bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	}
 }
 
-// @(deprecated="use sort.sort or slice.sort_by")
 quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	assert(f != nil)
 	a := array
@@ -384,7 +344,6 @@ quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	quick_sort_proc(a[i:n], f)
 }
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	a := array
 	n := len(a)
@@ -420,7 +379,6 @@ _log2 :: proc(x: int) -> int {
 	return res
 }
 
-// @(deprecated="use sort.sort or slice.sort_by")
 merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) {
 		s, m := start, mid
@@ -462,7 +420,6 @@ merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	internal_sort(array, 0, len(array)-1, f)
 }
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	merge :: proc(a: A, start, mid, end: int) {
 		s, m := start, mid
@@ -504,8 +461,6 @@ merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	internal_sort(array, 0, len(array)-1)
 }
 
-
-// @(deprecated="use sort.sort or slice.sort_by")
 heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check {
 		p := pi
@@ -540,7 +495,6 @@ heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	}
 }
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	sift :: proc(a: A, pi: int, n: int) #no_bounds_check {
 		p := pi

+ 15 - 15
core/strconv/decimal/decimal.odin

@@ -12,11 +12,11 @@ Decimal :: struct {
 Sets a Decimal from a given string `s`. The string is expected to represent a float. Stores parsed number in the given Decimal structure.
 If parsing fails, the Decimal will be left in an undefined state.
 
-**Inputs**  
+**Inputs**
 - d: Pointer to a Decimal struct where the parsed result will be stored
 - s: The input string representing the floating-point number
 
-**Returns**  
+**Returns**
 - ok: A boolean indicating whether the parsing was successful
 */
 set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
@@ -104,11 +104,11 @@ set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
 /*
 Converts a Decimal to a string representation, using the provided buffer as storage.
 
-**Inputs**  
+**Inputs**
 - buf: A byte slice buffer to hold the resulting string
 - a: The struct to be converted to a string
 
-**Returns**  
+**Returns**
 - A string representation of the Decimal
 */
 decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
@@ -150,7 +150,7 @@ decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
 /*
 Trims trailing zeros in the given Decimal, updating the count and decimal_point values as needed.
 
-**Inputs**  
+**Inputs**
 - a: Pointer to the Decimal struct to be trimmed
 */
 trim :: proc(a: ^Decimal) {
@@ -166,7 +166,7 @@ Converts a given u64 integer `idx` to its Decimal representation in the provided
 
 **Used for internal Decimal Operations.**
 
-**Inputs**  
+**Inputs**
 - a: Where the result will be stored
 - idx: The value to be assigned to the Decimal
 */
@@ -190,11 +190,11 @@ assign :: proc(a: ^Decimal, idx: u64) {
 	trim(a)
 }
 /*
-Shifts the Decimal value to the right by k positions. 
+Shifts the Decimal value to the right by k positions.
 
 **Used for internal Decimal Operations.**
 
-**Inputs**  
+**Inputs**
 - a: The Decimal struct to be shifted
 - k: The number of positions to shift right
 */
@@ -344,7 +344,7 @@ Shifts the decimal of the input value to the left by `k` places
 
 WARNING: asserts `k < 61`
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - k: The number of places to shift the decimal to the left
 */
@@ -405,7 +405,7 @@ shift_left :: proc(a: ^Decimal, k: uint) #no_bounds_check {
 /*
 Shifts the decimal of the input value by the specified number of places
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - i: The number of places to shift the decimal (positive for left shift, negative for right shift)
 */
@@ -435,7 +435,7 @@ shift :: proc(a: ^Decimal, i: int) {
 /*
 Determines if the Decimal can be rounded up at the given digit index
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to check
 - nd: The digit index to consider for rounding up
 
@@ -455,7 +455,7 @@ can_round_up :: proc(a: ^Decimal, nd: int) -> bool {
 /*
 Rounds the Decimal at the given digit index
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - nd: The digit index to round
 */
@@ -470,7 +470,7 @@ round :: proc(a: ^Decimal, nd: int) {
 /*
 Rounds the Decimal up at the given digit index
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - nd: The digit index to round up
 */
@@ -493,7 +493,7 @@ round_up :: proc(a: ^Decimal, nd: int) {
 /*
 Rounds down the decimal value to the specified number of decimal places
 
-**Inputs**  
+**Inputs**
 - a: The Decimal value to be rounded down
 - nd: The number of decimal places to round down to
 
@@ -522,7 +522,7 @@ round_down :: proc(a: ^Decimal, nd: int) {
 /*
 Extracts the rounded integer part of a decimal value
 
-**Inputs**  
+**Inputs**
 - a: A pointer to the Decimal value to extract the rounded integer part from
 
 WARNING: There are no guarantees about overflow.

+ 38 - 0
core/strconv/deprecated.odin

@@ -0,0 +1,38 @@
+package strconv
+
+// (2025-06-05) These procedures are to be removed at a later release.
+
+@(deprecated="Use write_bits instead")
+append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
+	return write_bits(buf, x, base, is_signed, bit_size, digits, flags)
+}
+
+@(deprecated="Use write_bits_128 instead")
+append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
+	return write_bits_128(buf, x, base, is_signed, bit_size, digits, flags)
+}
+
+@(deprecated="Use write_bool instead")
+append_bool :: proc(buf: []byte, b: bool) -> string {
+	return write_bool(buf, b)
+}
+
+@(deprecated="Use write_uint instead")
+append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
+	return write_uint(buf, u, base)
+}
+
+@(deprecated="Use write_int instead")
+append_int :: proc(buf: []byte, i: i64, base: int) -> string {
+	return write_int(buf, i, base)
+}
+
+@(deprecated="Use write_u128 instead")
+append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
+	return write_u128(buf, u, base)
+}
+
+@(deprecated="Use write_float instead")
+append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
+	return write_float(buf, f, fmt, prec, bit_size)
+}

+ 7 - 7
core/strconv/generic_float.odin

@@ -23,7 +23,7 @@ _f64_info := Float_Info{52, 11, -1023}
 /*
 Converts a floating-point number to a string with the specified format and precision.
 
-**Inputs**  
+**Inputs**
 
 buf: A byte slice to store the resulting string
 val: The floating-point value to be converted
@@ -40,7 +40,7 @@ Example:
 	bit_size := 64
 	result := strconv.generic_ftoa(buf[:], val, fmt, precision, bit_size) -> "3.14"
 
-**Returns**  
+**Returns**
 - A byte slice containing the formatted string
 */
 generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) -> []byte {
@@ -122,7 +122,7 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int)
 /*
 Converts a decimal floating-point number into a byte buffer with the given format
 
-**Inputs**  
+**Inputs**
 - buf: The byte buffer to store the formatted number
 - shortest: If true, generates the shortest representation of the number
 - neg: If true, the number is negative
@@ -130,7 +130,7 @@ Converts a decimal floating-point number into a byte buffer with the given forma
 - precision: The number of digits after the decimal point
 - fmt: The format specifier (accepted values: 'f', 'F', 'e', 'E', 'g', 'G')
 
-**Returns**  
+**Returns**
 - A byte slice containing the formatted decimal floating-point number
 */
 format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slice, precision: int, fmt: byte) -> []byte {
@@ -256,7 +256,7 @@ format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slic
 /*
 Rounds the given decimal number to its shortest representation, considering the provided floating-point format
 
-**Inputs**  
+**Inputs**
 - d: The decimal number to round
 - mant: The mantissa of the floating-point number
 - exp: The exponent of the floating-point number
@@ -331,11 +331,11 @@ round_shortest :: proc(d: ^decimal.Decimal, mant: u64, exp: int, flt: ^Float_Inf
 /*
 Converts a decimal number to its floating-point representation with the given format and returns the resulting bits
 
-**Inputs**  
+**Inputs**
 - d: Pointer to the decimal number to convert
 - info: Pointer to the Float_Info structure containing information about the floating-point format
 
-**Returns**  
+**Returns**
 - b: The bits representing the floating-point number
 - overflow: A boolean indicating whether an overflow occurred during conversion
 */

+ 15 - 15
core/strconv/integers.odin

@@ -12,12 +12,12 @@ digits := "0123456789abcdefghijklmnopqrstuvwxyz"
 /*
 Determines whether the given unsigned 64-bit integer is a negative value by interpreting it as a signed integer with the specified bit size.
 
-**Inputs**  
+**Inputs**
 - x: The unsigned 64-bit integer to check for negativity
 - is_signed: A boolean indicating if the input should be treated as a signed integer
 - bit_size: The bit size of the signed integer representation (8, 16, 32, or 64)
 
-**Returns**  
+**Returns**
 - u: The absolute value of the input integer
 - neg: A boolean indicating whether the input integer is negative
 */
@@ -48,9 +48,9 @@ is_integer_negative :: proc(x: u64, is_signed: bool, bit_size: int) -> (u: u64,
 	return
 }
 /*
-Appends the string representation of an integer to a buffer with specified base, flags, and digit set.
+Writes the string representation of an integer to a buffer with specified base, flags, and digit set.
 
-**Inputs**  
+**Inputs**
 - buf: The buffer to append the integer representation to
 - x: The integer value to convert
 - base: The base for the integer representation (2 <= base <= MAX_BASE)
@@ -59,12 +59,12 @@ Appends the string representation of an integer to a buffer with specified base,
 - digits: The digit set used for the integer representation
 - flags: The Int_Flags bit set to control integer formatting
 
-**Returns**  
+**Returns**
 - The string containing the integer representation appended to the buffer
 */
-append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
+write_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
 	if base < 2 || base > MAX_BASE {
-		panic("strconv: illegal base passed to append_bits")
+		panic("strconv: illegal base passed to write_bits")
 	}
 
 	a: [129]byte
@@ -106,12 +106,12 @@ append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: i
 /*
 Determines whether the given unsigned 128-bit integer is a negative value by interpreting it as a signed integer with the specified bit size.
 
-**Inputs**  
+**Inputs**
 - x: The unsigned 128-bit integer to check for negativity
 - is_signed: A boolean indicating if the input should be treated as a signed integer
 - bit_size: The bit size of the signed integer representation (8, 16, 32, 64, or 128)
 
-**Returns**  
+**Returns**
 - u: The absolute value of the input integer
 - neg: A boolean indicating whether the input integer is negative
 */
@@ -146,9 +146,9 @@ is_integer_negative_128 :: proc(x: u128, is_signed: bool, bit_size: int) -> (u:
 	return
 }
 /*
-Appends the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set.
+Writes the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set.
 
-**Inputs**  
+**Inputs**
 - buf: The buffer to append the integer representation to
 - x: The 128-bit integer value to convert
 - base: The base for the integer representation (2 <= base <= MAX_BASE)
@@ -157,12 +157,12 @@ Appends the string representation of a 128-bit integer to a buffer with specifie
 - digits: The digit set used for the integer representation
 - flags: The Int_Flags bit set to control integer formatting
 
-**Returns**  
-- The string containing the integer representation appended to the buffer
+**Returns**
+- The string containing the integer representation written to the buffer
 */
-append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
+write_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
 	if base < 2 || base > MAX_BASE {
-		panic("strconv: illegal base passed to append_bits")
+		panic("strconv: illegal base passed to write_bits")
 	}
 
 	a: [140]byte

+ 139 - 139
core/strconv/strconv.odin

@@ -5,8 +5,8 @@ import "decimal"
 /*
 Parses a boolean value from the input string
 
-**Inputs**  
-- s: The input string  
+**Inputs**
+- s: The input string
 	- true: "1", "t", "T", "true", "TRUE", "True"
 	- false: "0", "f", "F", "false", "FALSE", "False"
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -386,7 +386,7 @@ Parses an unsigned integer value from the input string, using the specified base
 	- If base is not 0, it will be used for parsing regardless of any prefix in the input string
 
 Example:
-	
+
 	import "core:fmt"
 	import "core:strconv"
 	parse_uint_example :: proc() {
@@ -399,14 +399,14 @@ Example:
 		n, ok = strconv.parse_uint("0xffff") // with prefix and inferred base
 		fmt.println(n,ok)
 	}
-	
+
 Output:
 
 	1234 true
 	65535 true
 	65535 true
 
-**Returns**  
+**Returns**
 
 value: The parsed uint value
 ok: `false` if no appropriate value could be found; the value was negative; he input string contained more than just the number
@@ -423,7 +423,7 @@ parse_uint :: proc(s: string, base := 0, n: ^int = nil) -> (value: uint, ok: boo
 /*
 Parses an integer value from a string in the given base, without any prefix
 
-**Inputs**  
+**Inputs**
 - str: The input string containing the integer value
 - base: The base (radix) to use for parsing the integer (1-16)
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -436,12 +436,12 @@ Example:
 		n, ok := strconv.parse_i128_of_base("-1234eeee", 10)
 		fmt.println(n,ok)
 	}
-	
+
 Output:
 
 	-1234 false
 
-**Returns**  
+**Returns**
 - value: The parsed i128 value
 - ok: false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number.
 */
@@ -491,7 +491,7 @@ parse_i128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i12
 /*
 Parses an integer value from a string in base 10, unless there's a prefix
 
-**Inputs**  
+**Inputs**
 - str: The input string containing the integer value
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
 
@@ -506,13 +506,13 @@ Example:
 		n, ok = strconv.parse_i128_maybe_prefixed("0xeeee")
 		fmt.println(n, ok)
 	}
-	
+
 Output:
 
 	1234 true
 	61166 true
-	
-**Returns**  
+
+**Returns**
 - value: The parsed i128 value
 - ok: `false` if a valid integer could not be found, or if the input string contained more than just the number.
 */
@@ -574,7 +574,7 @@ parse_i128 :: proc{parse_i128_maybe_prefixed, parse_i128_of_base}
 /*
 Parses an unsigned integer value from a string in the given base, without any prefix
 
-**Inputs**  
+**Inputs**
 - str: The input string containing the integer value
 - base: The base (radix) to use for parsing the integer (1-16)
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
@@ -590,13 +590,13 @@ Example:
 		n, ok = strconv.parse_u128_of_base("5678eeee", 16)
 		fmt.println(n, ok)
 	}
-	
+
 Output:
 
 	1234 false
 	1450766062 true
-	
-**Returns**  
+
+**Returns**
 - value: The parsed u128 value
 - ok: `false` if no numeric value of the appropriate base could be found, or if the input string contained more than just the number.
 */
@@ -634,7 +634,7 @@ parse_u128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u12
 /*
 Parses an unsigned integer value from a string in base 10, unless there's a prefix
 
-**Inputs**  
+**Inputs**
 - str: The input string containing the integer value
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil)
 
@@ -649,13 +649,13 @@ Example:
 		n, ok = strconv.parse_u128_maybe_prefixed("5678eeee")
 		fmt.println(n, ok)
 	}
-	
+
 Output:
 
 	1234 true
 	5678 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed u128 value
 - ok: false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number.
 */
@@ -706,10 +706,10 @@ parse_u128 :: proc{parse_u128_maybe_prefixed, parse_u128_of_base}
 /*
 Converts a byte to lowercase
 
-**Inputs**  
+**Inputs**
 - ch: A byte character to be converted to lowercase.
 
-**Returns**  
+**Returns**
 - A lowercase byte character.
 */
 @(private)
@@ -717,7 +717,7 @@ lower :: #force_inline proc "contextless" (ch: byte) -> byte { return ('a' - 'A'
 /*
 Parses a 32-bit floating point number from a string
 
-**Inputs**  
+**Inputs**
 - s: The input string containing a 32-bit floating point number.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -732,13 +732,13 @@ Example:
 		n, ok = strconv.parse_f32("5678e2")
 		fmt.printfln("%.3f %v", n, ok)
 	}
-	
+
 Output:
 
 	0.000 false
 	567800.000 true
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 32-bit floating point number.
 - ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number.
 */
@@ -750,7 +750,7 @@ parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) {
 /*
 Parses a 64-bit floating point number from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 64-bit floating point number.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -765,13 +765,13 @@ Example:
 		n, ok = strconv.parse_f64("5678e2")
 		fmt.printfln("%.3f %v", n, ok)
 	}
-	
+
 Output:
 
 	0.000 false
 	567800.000 true
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 64-bit floating point number.
 - ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number.
 */
@@ -787,7 +787,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 /*
 Parses a 32-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 32-bit floating point number.
 
 Example:
@@ -801,14 +801,14 @@ Example:
 		n, _, ok = strconv.parse_f32_prefix("5678e2")
 		fmt.printfln("%.3f %v", n, ok)
 	}
-	
+
 Output:
 
 	0.000 false
 	567800.000 true
-	
 
-**Returns**  
+
+**Returns**
 - value: The parsed 32-bit floating point number.
 - nr: The length of the parsed substring.
 - ok: A boolean indicating whether the parsing was successful.
@@ -822,7 +822,7 @@ parse_f32_prefix :: proc(str: string) -> (value: f32, nr: int, ok: bool) {
 /*
 Parses a 64-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 64-bit floating point number.
 
 Example:
@@ -846,7 +846,7 @@ Output:
 	1234.000 true
 	13.370 true
 
-**Returns**  
+**Returns**
 - value: The parsed 64-bit floating point number.
 - nr: The length of the parsed substring.
 - ok: `false` if a base 10 float could not be found
@@ -1184,7 +1184,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 /*
 Parses a 128-bit complex number from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 128-bit complex number.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -1200,13 +1200,13 @@ Example:
 		c, ok = strconv.parse_complex128("5+7i hellope", &n)
 		fmt.printfln("%v %i %t", c, n, ok)
 	}
-	
+
 Output:
 
 	3+1i 4 true
 	5+7i 4 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 128-bit complex number.
 - ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
 */
@@ -1232,12 +1232,12 @@ parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok:
 	}
 
 	value = complex(real_value, imag_value)
-	return 
+	return
 }
 /*
 Parses a 64-bit complex number from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 64-bit complex number.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -1253,13 +1253,13 @@ Example:
 		c, ok = strconv.parse_complex64("5+7i hellope", &n)
 		fmt.printfln("%v %i %t", c, n, ok)
 	}
-	
+
 Output:
 
 	3+1i 4 true
 	5+7i 4 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 64-bit complex number.
 - ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
 */
@@ -1271,7 +1271,7 @@ parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bo
 /*
 Parses a 32-bit complex number from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 32-bit complex number.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -1287,13 +1287,13 @@ Example:
 		c, ok = strconv.parse_complex32("5+7i hellope", &n)
 		fmt.printfln("%v %i %t", c, n, ok)
 	}
-	
+
 Output:
 
 	3+1i 4 true
 	5+7i 4 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 32-bit complex number.
 - ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
 */
@@ -1305,7 +1305,7 @@ parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bo
 /*
 Parses a 256-bit quaternion from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 256-bit quaternion.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -1321,13 +1321,13 @@ Example:
 		q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n)
 		fmt.printfln("%v %i %t", q, n, ok)
 	}
-	
+
 Output:
 
 	1+2i+3j+4k 10 true
 	1+2i+3j+4k 10 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 256-bit quaternion.
 - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
 */
@@ -1385,7 +1385,7 @@ parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256
 /*
 Parses a 128-bit quaternion from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 128-bit quaternion.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -1401,13 +1401,13 @@ Example:
 		q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n)
 		fmt.printfln("%v %i %t", q, n, ok)
 	}
-	
+
 Output:
 
 	1+2i+3j+4k 10 true
 	1+2i+3j+4k 10 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 128-bit quaternion.
 - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
 */
@@ -1419,7 +1419,7 @@ parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128
 /*
 Parses a 64-bit quaternion from a string
 
-**Inputs**  
+**Inputs**
 - str: The input string containing a 64-bit quaternion.
 - n: An optional pointer to an int to store the length of the parsed substring (default: nil).
 
@@ -1435,13 +1435,13 @@ Example:
 		q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n)
 		fmt.printfln("%v %i %t", q, n, ok)
 	}
-	
+
 Output:
 
 	1+2i+3j+4k 10 true
 	1+2i+3j+4k 10 false
-	
-**Returns**  
+
+**Returns**
 - value: The parsed 64-bit quaternion.
 - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
 */
@@ -1450,20 +1450,20 @@ parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64,
 	v, ok = parse_quaternion256(str, n)
 	return cast(quaternion64)v, ok
 }
-/* 
-Appends a boolean value as a string to the given buffer
+/*
+Writes a boolean value as a string to the given buffer
 
-**Inputs**  
-- buf: The buffer to append the boolean value to
-- b: The boolean value to be appended
+**Inputs**
+- buf: The buffer to write the boolean value to
+- b: The boolean value to be written
 
 Example:
 
 	import "core:fmt"
 	import "core:strconv"
-	append_bool_example :: proc() {
+	write_bool_example :: proc() {
 		buf: [6]byte
-		result := strconv.append_bool(buf[:], true)
+		result := strconv.write_bool(buf[:], true)
 		fmt.println(result, buf)
 	}
 
@@ -1471,10 +1471,10 @@ Output:
 
 	true [116, 114, 117, 101, 0, 0]
 
-**Returns**  
-- The resulting string after appending the boolean value
+**Returns**
+- The resulting string after writing the boolean value
 */
-append_bool :: proc(buf: []byte, b: bool) -> string {
+write_bool :: proc(buf: []byte, b: bool) -> string {
 	n := 0
 	if b {
 		n = copy(buf, "true")
@@ -1483,21 +1483,21 @@ append_bool :: proc(buf: []byte, b: bool) -> string {
 	}
 	return string(buf[:n])
 }
-/* 
-Appends an unsigned integer value as a string to the given buffer with the specified base
+/*
+Writes an unsigned integer value as a string to the given buffer with the specified base
 
-**Inputs**  
-- buf: The buffer to append the unsigned integer value to
-- u: The unsigned integer value to be appended
+**Inputs**
+- buf: The buffer to write the unsigned integer value to
+- u: The unsigned integer value to be written
 - base: The base to use for converting the integer value
 
 Example:
 
 	import "core:fmt"
 	import "core:strconv"
-	append_uint_example :: proc() {
+	write_uint_example :: proc() {
 		buf: [4]byte
-		result := strconv.append_uint(buf[:], 42, 16)
+		result := strconv.write_uint(buf[:], 42, 16)
 		fmt.println(result, buf)
 	}
 
@@ -1505,27 +1505,27 @@ Output:
 
 	2a [50, 97, 0, 0]
 
-**Returns**  
-- The resulting string after appending the unsigned integer value
+**Returns**
+- The resulting string after writing the unsigned integer value
 */
-append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
-	return append_bits(buf, u, base, false, 8*size_of(uint), digits, nil)
+write_uint :: proc(buf: []byte, u: u64, base: int) -> string {
+	return write_bits(buf, u, base, false, 8*size_of(uint), digits, nil)
 }
-/* 
-Appends a signed integer value as a string to the given buffer with the specified base
+/*
+Writes a signed integer value as a string to the given buffer with the specified base
 
-**Inputs**  
-- buf: The buffer to append the signed integer value to
-- i: The signed integer value to be appended
+**Inputs**
+- buf: The buffer to write the signed integer value to
+- i: The signed integer value to be written
 - base: The base to use for converting the integer value
 
 Example:
 
 	import "core:fmt"
 	import "core:strconv"
-	append_int_example :: proc() {
+	write_int_example :: proc() {
 		buf: [4]byte
-		result := strconv.append_int(buf[:], -42, 10)
+		result := strconv.write_int(buf[:], -42, 10)
 		fmt.println(result, buf)
 	}
 
@@ -1533,23 +1533,23 @@ Output:
 
 	-42 [45, 52, 50, 0]
 
-**Returns**  
-- The resulting string after appending the signed integer value
+**Returns**
+- The resulting string after writing the signed integer value
 */
-append_int :: proc(buf: []byte, i: i64, base: int) -> string {
-	return append_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil)
+write_int :: proc(buf: []byte, i: i64, base: int) -> string {
+	return write_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil)
 }
 
 
 
-append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
-	return append_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil)
+write_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
+	return write_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil)
 }
 
-/* 
+/*
 Converts an integer value to a string and stores it in the given buffer
 
-**Inputs**  
+**Inputs**
 - buf: The buffer to store the resulting string
 - i: The integer value to be converted
 
@@ -1567,16 +1567,16 @@ Output:
 
 	42 [52, 50, 0, 0]
 
-**Returns**  
+**Returns**
 - The resulting string after converting the integer value
 */
 itoa :: proc(buf: []byte, i: int) -> string {
-	return append_int(buf, i64(i), 10)
+	return write_int(buf, i64(i), 10)
 }
 /*
 Converts a string to an integer value
 
-**Inputs**  
+**Inputs**
 - s: The string to be converted
 
 Example:
@@ -1591,17 +1591,17 @@ Output:
 
 	42
 
-**Returns**  
+**Returns**
 - The resulting integer value
 */
 atoi :: proc(s: string) -> int {
 	v, _ := parse_int(s)
 	return v
 }
-/* 
+/*
 Converts a string to a float64 value
 
-**Inputs**  
+**Inputs**
 - s: The string to be converted
 
 Example:
@@ -1616,21 +1616,21 @@ Output:
 
 	3.140
 
-**Returns**  
+**Returns**
 - The resulting float64 value after converting the string
 */
 atof :: proc(s: string) -> f64 {
 	v, _  := parse_f64(s)
 	return v
 }
-// Alias to `append_float`
-ftoa :: append_float
-/* 
-Appends a float64 value as a string to the given buffer with the specified format and precision
-
-**Inputs**  
-- buf: The buffer to append the float64 value to
-- f: The float64 value to be appended
+// Alias to `write_float`
+ftoa :: write_float
+/*
+Writes a float64 value as a string to the given buffer with the specified format and precision
+
+**Inputs**
+- buf: The buffer to write the float64 value to
+- f: The float64 value to be written
 - fmt: The byte specifying the format to use for the conversion
 - prec: The precision to use for the conversion
 - bit_size: The size of the float in bits (32 or 64)
@@ -1639,9 +1639,9 @@ Example:
 
 	import "core:fmt"
 	import "core:strconv"
-	append_float_example :: proc() {
+	write_float_example :: proc() {
 		buf: [8]byte
-		result := strconv.append_float(buf[:], 3.14159, 'f', 2, 64)
+		result := strconv.write_float(buf[:], 3.14159, 'f', 2, 64)
 		fmt.println(result, buf)
 	}
 
@@ -1649,20 +1649,20 @@ Output:
 
 	+3.14 [43, 51, 46, 49, 52, 0, 0, 0]
 
-**Returns**  
-- The resulting string after appending the float
+**Returns**
+- The resulting string after writing the float
 */
-append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
+write_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
 	return string(generic_ftoa(buf, f, fmt, prec, bit_size))
 }
 /*
-Appends a quoted string representation of the input string to a given byte slice and returns the result as a string
+Writes a quoted string representation of the input string to a given byte slice and returns the result as a string
 
-**Inputs**  
-- buf: The byte slice to which the quoted string will be appended
+**Inputs**
+- buf: The byte slice to which the quoted string will be written
 - str: The input string to be quoted
 
-!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected  
+!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected
 
 Example:
 
@@ -1678,8 +1678,8 @@ Output:
 
 	"'h''e''l''l''o'" [34, 39, 104, 39, 39, 101, 39, 39, 108, 39, 39, 108, 39, 39, 111, 39, 34, 0, 0, 0]
 
-**Returns**  
-- The resulting string after appending the quoted string representation
+**Returns**
+- The resulting string after writing the quoted string representation
 */
 quote :: proc(buf: []byte, str: string) -> string {
 	write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1719,10 +1719,10 @@ quote :: proc(buf: []byte, str: string) -> string {
 	return string(buf[:i])
 }
 /*
-Appends a quoted rune representation of the input rune to a given byte slice and returns the result as a string
+Writes a quoted rune representation of the input rune to a given byte slice and returns the result as a string
 
-**Inputs**  
-- buf: The byte slice to which the quoted rune will be appended
+**Inputs**
+- buf: The byte slice to which the quoted rune will be written
 - r: The input rune to be quoted
 
 Example:
@@ -1739,8 +1739,8 @@ Output:
 
 	'A' [39, 65, 39, 0]
 
-**Returns**  
-- The resulting string after appending the quoted rune representation
+**Returns**
+- The resulting string after writing the quoted rune representation
 */
 quote_rune :: proc(buf: []byte, r: rune) -> string {
 	write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1783,7 +1783,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string {
 		if r < 32 {
 			write_string(buf, &i, "\\x")
 			b: [2]byte
-			s := append_bits(b[:], u64(r), 16, true, 64, digits, nil)
+			s := write_bits(b[:], u64(r), 16, true, 64, digits, nil)
 			switch len(s) {
 			case 0: write_string(buf, &i, "00")
 			case 1: write_rune(buf, &i, '0')
@@ -1800,11 +1800,11 @@ quote_rune :: proc(buf: []byte, r: rune) -> string {
 /*
 Unquotes a single character from the input string, considering the given quote character
 
-**Inputs**  
+**Inputs**
 - str: The input string containing the character to unquote
 - quote: The quote character to consider (e.g., '"')
 
-Example:  
+Example:
 
 	import "core:fmt"
 	import "core:strconv"
@@ -1815,12 +1815,12 @@ Example:
 		fmt.printf("r: <%v>, multiple_bytes:%v, tail_string:<%s>, success:%v\n",r, multiple_bytes, tail_string, success)
 	}
 
-Output:  
+Output:
 
 	Source: 'The' raven
 	r: <'>, multiple_bytes:false, tail_string:<The' raven>, success:true
 
-**Returns**  
+**Returns**
 - r: The unquoted rune
 - multiple_bytes: A boolean indicating if the rune has multiple bytes
 - tail_string: The remaining portion of the input string after unquoting the character
@@ -1923,13 +1923,13 @@ unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool
 /*
 Unquotes the input string considering any type of quote character and returns the unquoted string
 
-**Inputs**  
+**Inputs**
 - lit: The input string to unquote
 - allocator: (default: context.allocator)
 
 WARNING: This procedure gives unexpected results if the quotes are not the first and last characters.
 
-Example:  
+Example:
 
 	import "core:fmt"
 	import "core:strconv"
@@ -1947,10 +1947,10 @@ Example:
 		src="The raven \'Huginn\' is black."
 		s, allocated, ok = strconv.unquote_string(src) // Will produce undesireable results
 		fmt.println(src)
-		fmt.printf("Unquoted: <%s>, alloc:%v, ok:%v\n", s, allocated, ok) 
+		fmt.printf("Unquoted: <%s>, alloc:%v, ok:%v\n", s, allocated, ok)
 	}
 
-Output:  
+Output:
 
 	"The raven Huginn is black."
 	Unquoted: <The raven Huginn is black.>, alloc:false, ok:true
@@ -1961,7 +1961,7 @@ Output:
 	The raven 'Huginn' is black.
 	Unquoted: <he raven 'Huginn' is black>, alloc:false, ok:true
 
-**Returns**  
+**Returns**
 - res: The resulting unquoted string
 - allocated: A boolean indicating if the resulting string was allocated using the provided allocator
 - success: A boolean indicating whether the unquoting was successful
@@ -2002,7 +2002,7 @@ unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: str
 			return s, false, true
 		}
 	}
-	
+
 	context.allocator = allocator
 
 	buf_len := 3*len(s) / 2

+ 6 - 6
core/strings/builder.odin

@@ -675,7 +675,7 @@ Returns:
 */
 write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
 	buf: [384]byte
-	s := strconv.append_float(buf[:], f, fmt, prec, bit_size)
+	s := strconv.write_float(buf[:], f, fmt, prec, bit_size)
 	// If the result starts with a `+` then unless we always want signed results,
 	// we skip it unless it's followed by an `I` (because of +Inf).
 	if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
@@ -699,7 +699,7 @@ Returns:
 */
 write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
 	buf: [384]byte
-	s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
+	s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
 	if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
 		s = s[1:]
 	}
@@ -739,7 +739,7 @@ Output:
 */
 write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
 	buf: [384]byte
-	s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
+	s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
 	if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
 		s = s[1:]
 	}
@@ -761,7 +761,7 @@ Returns:
 */
 write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
 	buf: [384]byte
-	s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
+	s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
 	if !always_signed && (buf[0] == '+' && buf[1] != 'I') {
 		s = s[1:]
 	}
@@ -782,7 +782,7 @@ Returns:
 */
 write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
 	buf: [32]byte
-	s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
+	s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
 	return write_string(b, s)
 }
 /*
@@ -800,7 +800,7 @@ Returns:
 */
 write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
 	buf: [32]byte
-	s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
+	s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
 	return write_string(b, s)
 }
 /*

+ 82 - 64
core/strings/strings.odin

@@ -28,24 +28,7 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location
 	copy(c, s)
 	return string(c), nil
 }
-/*
-Clones a string safely (returns early with an allocation error on failure)
-
-*Allocates Using Provided Allocator*
-
-Inputs:
-- s: The string to be cloned
-- allocator: (default: context.allocator)
-- loc: The caller location for debugging purposes (default: #caller_location)
 
-Returns:
-- res: The cloned string
-- err: An allocator error if one occured, `nil` otherwise
-*/
-@(deprecated="Prefer clone. It now returns an optional allocator error")
-clone_safe :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) {
-	return clone(s, allocator, loc)
-}
 /*
 Clones a string and appends a null-byte to make it a cstring
 
@@ -66,6 +49,7 @@ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #call
 	c[len(s)] = 0
 	return cstring(&c[0]), nil
 }
+
 /*
 Transmutes a raw pointer into a string. Non-allocating.
 
@@ -81,6 +65,7 @@ Returns:
 string_from_ptr :: proc(ptr: ^byte, len: int) -> (res: string) {
 	return transmute(string)mem.Raw_String{ptr, len}
 }
+
 /*
 Transmutes a raw pointer (null-terminated) into a string. Non-allocating. Searches for a null-byte from `0..<len`, otherwise `len` will be the end size
 
@@ -99,20 +84,7 @@ string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) -
 	s = truncate_to_byte(s, 0)
 	return s
 }
-/*
-Gets the raw byte pointer for the start of a string `str`
 
-Inputs:
-- str: The input string
-
-Returns:
-- res: A pointer to the start of the string's bytes
-*/
-@(deprecated="Prefer the builtin raw_data.")
-ptr_from_string :: proc(str: string) -> (res: ^byte) {
-	d := transmute(mem.Raw_String)str
-	return d.data
-}
 /*
 Converts a string `str` to a cstring
 
@@ -128,6 +100,7 @@ unsafe_string_to_cstring :: proc(str: string) -> (res: cstring) {
 	d := transmute(mem.Raw_String)str
 	return cstring(d.data)
 }
+
 /*
 Truncates a string `str` at the first occurrence of char/byte `b`
 
@@ -147,6 +120,7 @@ truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) {
 	}
 	return str[:n]
 }
+
 /*
 Truncates a string `str` at the first occurrence of rune `r` as a slice of the original, entire string if not found
 
@@ -164,6 +138,7 @@ truncate_to_rune :: proc(str: string, r: rune) -> (res: string) {
 	}
 	return str[:n]
 }
+
 /*
 Clones a byte array `s` and appends a null-byte
 
@@ -184,6 +159,7 @@ clone_from_bytes :: proc(s: []byte, allocator := context.allocator, loc := #call
 	c[len(s)] = 0
 	return string(c[:len(s)]), nil
 }
+
 /*
 Clones a cstring `s` as a string
 
@@ -201,6 +177,7 @@ Returns:
 clone_from_cstring :: proc(s: cstring, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error {
 	return clone(string(s), allocator, loc)
 }
+
 /*
 Clones a string from a byte pointer `ptr` and a byte length `len`
 
@@ -222,6 +199,7 @@ clone_from_ptr :: proc(ptr: ^byte, len: int, allocator := context.allocator, loc
 	s := string_from_ptr(ptr, len)
 	return clone(s, allocator, loc)
 }
+
 // Overloaded procedure to clone from a string, `[]byte`, `cstring` or a `^byte` + length
 clone_from :: proc{
 	clone,
@@ -229,6 +207,7 @@ clone_from :: proc{
 	clone_from_cstring,
 	clone_from_ptr,
 }
+
 /*
 Clones a string from a null-terminated cstring `ptr` and a byte length `len`
 
@@ -251,6 +230,7 @@ clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context.
 	s = truncate_to_byte(s, 0)
 	return clone(s, allocator, loc)
 }
+
 /*
 Compares two strings, returning a value representing which one comes first lexicographically.
 -1 for `lhs`; 1 for `rhs`, or 0 if they are equal.
@@ -265,6 +245,7 @@ Returns:
 compare :: proc "contextless" (lhs, rhs: string) -> (result: int) {
 	return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs)
 }
+
 /*
 Checks if rune `r` in the string `s`
 
@@ -283,6 +264,7 @@ contains_rune :: proc(s: string, r: rune) -> (result: bool) {
 	}
 	return false
 }
+
 /*
 Returns true when the string `substr` is contained inside the string `s`
 
@@ -314,6 +296,7 @@ Output:
 contains :: proc(s, substr: string) -> (res: bool) {
 	return index(s, substr) >= 0
 }
+
 /*
 Returns `true` when the string `s` contains any of the characters inside the string `chars`
 
@@ -386,6 +369,7 @@ Output:
 rune_count :: proc(s: string) -> (res: int) {
 	return utf8.rune_count_in_string(s)
 }
+
 /*
 Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings
 Works with UTF-8 string content
@@ -508,6 +492,7 @@ prefix_length :: proc "contextless" (a, b: string) -> (n: int) {
 	}
 	return
 }
+
 /*
 Returns the common prefix between strings `a` and `b`
 
@@ -540,6 +525,7 @@ Output:
 common_prefix :: proc(a, b: string) -> string {
 	return a[:prefix_length(a, b)]
 }
+
 /*
 Determines if a string `s` starts with a given `prefix`
 
@@ -661,24 +647,7 @@ join :: proc(a: []string, sep: string, allocator := context.allocator, loc := #c
 	}
 	return string(b), nil
 }
-/*
-Joins a slice of strings `a` with a `sep` string, returns an error on allocation failure
-
-*Allocates Using Provided Allocator*
 
-Inputs:
-- a: A slice of strings to join
-- sep: The separator string
-- allocator: (default is context.allocator)
-
-Returns:
-- str: A combined string from the slice of strings `a` separated with the `sep` string
-- err: An allocator error if one occured, `nil` otherwise
-*/
-@(deprecated="Prefer join. It now returns an optional allocator error")
-join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) {
-	return join(a, sep, allocator)
-}
 /*
 Returns a combined string from the slice of strings `a` without a separator
 
@@ -723,22 +692,6 @@ concatenate :: proc(a: []string, allocator := context.allocator, loc := #caller_
 	}
 	return string(b), nil
 }
-/*
-Returns a combined string from the slice of strings `a` without a separator, or an error if allocation fails
-
-*Allocates Using Provided Allocator*
-
-Inputs:
-- a: A slice of strings to concatenate
-- allocator: (default is context.allocator)
-
-Returns:
-The concatenated string, and an error if allocation fails
-*/
-@(deprecated="Prefer concatenate. It now returns an optional allocator error")
-concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) {
-	return concatenate(a, allocator)
-}
 
 /*
 Returns a substring of the input string `s` with the specified rune offset and length
@@ -901,6 +854,7 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato
 
 	return res[:i+1], nil
 }
+
 /*
 Splits a string into parts based on a separator.
 
@@ -936,6 +890,7 @@ Output:
 split :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
 	return _split(s, sep, 0, -1, allocator)
 }
+
 /*
 Splits a string into parts based on a separator. If n < count of seperators, the remainder of the string is returned in the last entry.
 
@@ -972,6 +927,7 @@ Output:
 split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
 	return _split(s, sep, 0, n, allocator)
 }
+
 /*
 Splits a string into parts after the separator, retaining it in the substrings.
 
@@ -1007,6 +963,7 @@ Output:
 split_after :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
 	return _split(s, sep, len(sep), -1, allocator)
 }
+
 /*
 Splits a string into a total of `n` parts after the separator.
 
@@ -1043,6 +1000,7 @@ Output:
 split_after_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error {
 	return _split(s, sep, len(sep), n, allocator)
 }
+
 /*
 Searches for the first occurrence of `sep` in the given string and returns the substring
 up to (but not including) the separator, as well as a boolean indicating success.
@@ -1083,6 +1041,7 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string,
 	}
 	return
 }
+
 /*
 Splits the input string by the byte separator in an iterator fashion.
 
@@ -1129,6 +1088,7 @@ split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) {
 	}
 	return
 }
+
 /*
 Splits the input string by the separator string in an iterator fashion.
 
@@ -1164,6 +1124,7 @@ Output:
 split_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) {
 	return _split_iterator(s, sep, 0)
 }
+
 /*
 Splits the input string after every separator string in an iterator fashion.
 
@@ -1199,6 +1160,7 @@ Output:
 split_after_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) {
 	return _split_iterator(s, sep, len(sep))
 }
+
 /*
 Trims the carriage return character from the end of the input string.
 
@@ -1220,6 +1182,7 @@ _trim_cr :: proc(s: string) -> (res: string) {
 	}
 	return s
 }
+
 /*
 Splits the input string at every line break `\n`.
 
@@ -1257,6 +1220,7 @@ split_lines :: proc(s: string, allocator := context.allocator) -> (res: []string
 	}
 	return lines, nil
 }
+
 /*
 Splits the input string at every line break `\n` for `n` parts.
 
@@ -1297,6 +1261,7 @@ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> (res
 	}
 	return lines, nil
 }
+
 /*
 Splits the input string at every line break `\n` leaving the `\n` in the resulting strings.
 
@@ -1336,6 +1301,7 @@ split_lines_after :: proc(s: string, allocator := context.allocator) -> (res: []
 	}
 	return lines, nil
 }
+
 /*
 Splits the input string at every line break `\n` leaving the `\n` in the resulting strings.
 Only runs for n parts.
@@ -1377,6 +1343,7 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -
 	}
 	return lines, nil
 }
+
 /*
 Splits the input string at every line break `\n`.
 Returns the current split string every iteration until the string is consumed.
@@ -1411,6 +1378,7 @@ split_lines_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
 	line = _split_iterator(s, sep, 0) or_return
 	return _trim_cr(line), true
 }
+
 /*
 Splits the input string at every line break `\n`.
 Returns the current split string with line breaks included every iteration until the string is consumed.
@@ -1448,6 +1416,7 @@ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) {
 	line = _split_iterator(s, sep, len(sep)) or_return
 	return _trim_cr(line), true
 }
+
 /*
 Returns the byte offset of the first byte `c` in the string s it finds, -1 when not found.
 NOTE: Can't find UTF-8 based runes.
@@ -1482,6 +1451,7 @@ Output:
 index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
 	return #force_inline bytes.index_byte(transmute([]u8)s, c)
 }
+
 /*
 Returns the byte offset of the last byte `c` in the string `s`, -1 when not found.
 
@@ -1517,6 +1487,7 @@ Output:
 last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) {
 	return #force_inline bytes.last_index_byte(transmute([]u8)s, c)
 }
+
 /*
 Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found.
 Invalid runes return -1
@@ -1657,6 +1628,7 @@ index :: proc "contextless" (s, substr: string) -> (res: int) {
 	}
 	return -1
 }
+
 /*
 Returns the last byte offset of the string `substr` in the string `s`, -1 when not found.
 
@@ -1734,6 +1706,7 @@ last_index :: proc(s, substr: string) -> (res: int) {
 	}
 	return -1
 }
+
 /*
 Returns the index of any first char of `chars` found in `s`, -1 if not found.
 
@@ -1797,6 +1770,7 @@ index_any :: proc(s, chars: string) -> (res: int) {
 	}
 	return -1
 }
+
 /*
 Finds the last occurrence of any character in `chars` within `s`. Iterates in reverse.
 
@@ -1878,6 +1852,7 @@ last_index_any :: proc(s, chars: string) -> (res: int) {
 	}
 	return -1
 }
+
 /*
 Finds the first occurrence of any substring in `substrs` within `s`
 
@@ -1919,6 +1894,7 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) {
 	}
 	return
 }
+
 /*
 Counts the number of non-overlapping occurrences of `substr` in `s`
 
@@ -1985,6 +1961,7 @@ count :: proc(s, substr: string) -> (res: int) {
 	}
 	return n
 }
+
 /*
 Repeats the string `s` `count` times, concatenating the result
 
@@ -2030,6 +2007,7 @@ repeat :: proc(s: string, count: int, allocator := context.allocator, loc := #ca
 	}
 	return string(b), nil
 }
+
 /*
 Replaces all occurrences of `old` in `s` with `new`
 
@@ -2063,9 +2041,11 @@ Output:
 	zzzz true
 
 */
+
 replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) {
 	return replace(s, old, new, -1, allocator)
 }
+
 /*
 Replaces n instances of old in the string s with the new string
 
@@ -2144,6 +2124,7 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator, loc
 	output = string(t[0:w])
 	return
 }
+
 /*
 Removes the key string `n` times from the `s` string
 
@@ -2182,6 +2163,7 @@ Output:
 remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) {
 	return replace(s, key, "", n, allocator)
 }
+
 /*
 Removes all the `key` string instances from the `s` string
 
@@ -2217,6 +2199,7 @@ Output:
 remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) {
 	return remove(s, key, -1, allocator)
 }
+
 // Returns true if is an ASCII space character ('\t', '\n', '\v', '\f', '\r', ' ')
 @(private) _ascii_space := [256]bool{'\t' = true, '\n' = true, '\v' = true, '\f' = true, '\r' = true, ' ' = true}
 
@@ -2320,6 +2303,7 @@ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int
 	}
 	return -1
 }
+
 // Same as `index_proc`, but the procedure p takes a raw pointer for state
 index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) {
 	for r, i in s {
@@ -2329,6 +2313,7 @@ index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: r
 	}
 	return -1
 }
+
 // Finds the index of the *last* rune in the string s for which the procedure p returns the same value as truth
 last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int) {
 	// TODO(bill): Probably use Rabin-Karp Search
@@ -2341,6 +2326,7 @@ last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res
 	}
 	return -1
 }
+
 // Same as `index_proc_with_state`, runs through the string in reverse
 last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) {
 	// TODO(bill): Probably use Rabin-Karp Search
@@ -2353,6 +2339,7 @@ last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta
 	}
 	return -1
 }
+
 /*
 Trims the input string `s` from the left until the procedure `p` returns false
 
@@ -2387,6 +2374,7 @@ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> (res: string) {
 	}
 	return s[i:]
 }
+
 /*
 Trims the input string `s` from the left until the procedure `p` with state returns false
 
@@ -2405,6 +2393,7 @@ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, stat
 	}
 	return s[i:]
 }
+
 /*
 Trims the input string `s` from the right until the procedure `p` returns `false`
 
@@ -2442,6 +2431,7 @@ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> (res: string) {
 	}
 	return s[0:i]
 }
+
 /*
 Trims the input string `s` from the right until the procedure `p` with state returns `false`
 
@@ -2463,6 +2453,7 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta
 	}
 	return s[0:i]
 }
+
 // Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison
 is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) {
 	cutset := (^string)(state)^
@@ -2473,6 +2464,7 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) {
 	}
 	return false
 }
+
 /*
 Trims the cutset string from the `s` string
 
@@ -2490,6 +2482,7 @@ trim_left :: proc(s: string, cutset: string) -> (res: string) {
 	state := cutset
 	return trim_left_proc_with_state(s, is_in_cutset, &state)
 }
+
 /*
 Trims the cutset string from the `s` string from the right
 
@@ -2507,6 +2500,7 @@ trim_right :: proc(s: string, cutset: string) -> (res: string) {
 	state := cutset
 	return trim_right_proc_with_state(s, is_in_cutset, &state)
 }
+
 /*
 Trims the cutset string from the `s` string, both from left and right
 
@@ -2520,6 +2514,7 @@ Returns:
 trim :: proc(s: string, cutset: string) -> (res: string) {
 	return trim_right(trim_left(s, cutset), cutset)
 }
+
 /*
 Trims until a valid non-space rune from the left, "\t\txyz\t\t" -> "xyz\t\t"
 
@@ -2532,6 +2527,7 @@ Returns:
 trim_left_space :: proc(s: string) -> (res: string) {
 	return trim_left_proc(s, is_space)
 }
+
 /*
 Trims from the right until a valid non-space rune, "\t\txyz\t\t" -> "\t\txyz"
 
@@ -2544,6 +2540,7 @@ Returns:
 trim_right_space :: proc(s: string) -> (res: string) {
 	return trim_right_proc(s, is_space)
 }
+
 /*
 Trims from both sides until a valid non-space rune, "\t\txyz\t\t" -> "xyz"
 
@@ -2556,6 +2553,7 @@ Returns:
 trim_space :: proc(s: string) -> (res: string) {
 	return trim_right_space(trim_left_space(s))
 }
+
 /*
 Trims null runes from the left, "\x00\x00testing\x00\x00" -> "testing\x00\x00"
 
@@ -2568,6 +2566,7 @@ Returns:
 trim_left_null :: proc(s: string) -> (res: string) {
 	return trim_left_proc(s, is_null)
 }
+
 /*
 Trims null runes from the right, "\x00\x00testing\x00\x00" -> "\x00\x00testing"
 
@@ -2580,6 +2579,7 @@ Returns:
 trim_right_null :: proc(s: string) -> (res: string) {
 	return trim_right_proc(s, is_null)
 }
+
 /*
 Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing"
 
@@ -2591,6 +2591,7 @@ Returns:
 trim_null :: proc(s: string) -> (res: string) {
 	return trim_right_null(trim_left_null(s))
 }
+
 /*
 Trims a `prefix` string from the start of the `s` string and returns the trimmed string
 
@@ -2623,6 +2624,7 @@ trim_prefix :: proc(s, prefix: string) -> (res: string) {
 	}
 	return s
 }
+
 /*
 Trims a `suffix` string from the end of the `s` string and returns the trimmed string
 
@@ -2655,6 +2657,7 @@ trim_suffix :: proc(s, suffix: string) -> (res: string) {
 	}
 	return s
 }
+
 /*
 Splits the input string `s` by all possible `substrs` and returns an allocated array of strings
 
@@ -2727,6 +2730,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator
 	assert(len(results) == n)
 	return results[:], nil
 }
+
 /*
 Splits the input string `s` by all possible `substrs` in an iterator fashion. The full string is returned if no match.
 
@@ -2786,6 +2790,7 @@ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok:
 	ok = true
 	return
 }
+
 /*
 Replaces invalid UTF-8 characters in the input string with a specified replacement string. Adjacent invalid bytes are only replaced once.
 
@@ -2846,6 +2851,7 @@ scrub :: proc(s: string, replacement: string, allocator := context.allocator) ->
 
 	return to_string(b), nil
 }
+
 /*
 Reverses the input string `s`
 
@@ -2889,6 +2895,7 @@ reverse :: proc(s: string, allocator := context.allocator, loc := #caller_locati
 	}
 	return string(buf), nil
 }
+
 /*
 Expands the input string by replacing tab characters with spaces to align to a specified tab size
 
@@ -2920,6 +2927,7 @@ Output:
 	abc1    abc2    abc3
 
 */
+
 expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error {
 	if tab_size <= 0 {
 		panic("tab size must be positive")
@@ -2961,6 +2969,7 @@ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) ->
 
 	return to_string(b), nil
 }
+
 /*
 Splits the input string `str` by the separator `sep` string and returns 3 parts. The values are slices of the original string.
 
@@ -3011,8 +3020,10 @@ partition :: proc(str, sep: string) -> (head, match, tail: string) {
 	tail = str[i+len(sep):]
 	return
 }
+
 // Alias for centre_justify
 center_justify :: centre_justify // NOTE(bill): Because Americans exist
+
 /*
 Centers the input string within a field of specified length by adding pad string on both sides, if its length is less than the target length.
 
@@ -3048,6 +3059,7 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte
 
 	return to_string(b), nil
 }
+
 /*
 Left-justifies the input string within a field of specified length by adding pad string on the right side, if its length is less than the target length.
 
@@ -3082,6 +3094,7 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context
 
 	return to_string(b), nil
 }
+
 /*
 Right-justifies the input string within a field of specified length by adding pad string on the left side, if its length is less than the target length.
 
@@ -3116,6 +3129,7 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex
 
 	return to_string(b), nil
 }
+
 /*
 Writes a given pad string a specified number of times to an `io.Writer`
 
@@ -3142,6 +3156,7 @@ write_pad_string :: proc(w: io.Writer, pad: string, pad_len, remains: int) {
 		p = p[width:]
 	}
 }
+
 /*
 Splits a string into a slice of substrings at each instance of one or more consecutive white space characters, as defined by `unicode.is_space`
 
@@ -3203,6 +3218,7 @@ fields :: proc(s: string, allocator := context.allocator, loc := #caller_locatio
 	}
 	return a, nil
 }
+
 /*
 Splits a string into a slice of substrings at each run of unicode code points `r` satisfying the predicate `f(r)`
 
@@ -3245,6 +3261,7 @@ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.alloc
 
 	return substrings[:], nil
 }
+
 /*
 Retrieves the first non-space substring from a mutable string reference and advances the reference. `s` is advanced from any space after the substring, or be an empty string if the substring was the remaining characters
 
@@ -3283,6 +3300,7 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) {
 	s^ = s[len(s):]
 	return
 }
+
 /*
 Computes the Levenshtein edit distance between two strings
 
@@ -3460,4 +3478,4 @@ substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) {
 	}
 
 	return internal_substring(s, -1, rune_end)
-}
+}

+ 0 - 1
core/sys/darwin/Foundation/NSApplication.odin

@@ -82,7 +82,6 @@ Application_setActivationPolicy :: proc "c" (self: ^Application, activationPolic
 // NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.)
 // and has no clear alternative although `activate` is what Apple tells you to use,
 // that does not work the same way.
-// @(deprecated="Use NSApplication method activate instead.")
 @(objc_type=Application, objc_name="activateIgnoringOtherApps")
 Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) {
 	msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps)

+ 1 - 1
core/sys/linux/sys.odin

@@ -1413,7 +1413,7 @@ umask :: proc "contextless" (mask: Mode) -> Mode {
 	Available since Linux 1.0.
 */
 gettimeofday :: proc "contextless" (tv: ^Time_Val) -> (Errno) {
-	ret := syscall(SYS_gettimeofday, tv)
+	ret := syscall(SYS_gettimeofday, tv, rawptr(nil))
 	return Errno(-ret)
 }
 

+ 0 - 8
core/sys/windows/util.odin

@@ -75,14 +75,6 @@ LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID {
 	return LANGID(lcid)
 }
 
-// this one gave me trouble as it do not mask the values.
-// the _ in the name is also off comparing to the c code
-// i can't find any usage in the odin repo
-@(deprecated = "use MAKEWORD")
-MAKE_WORD :: #force_inline proc "contextless" (x, y: WORD) -> WORD {
-	return x << 8 | y
-}
-
 utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
 	if len(s) < 1 {
 		return nil

+ 3 - 3
core/sys/windows/ws2_32.odin

@@ -243,9 +243,9 @@ foreign ws2_32 {
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-ntohs)
 	ntohs :: proc(netshort: c_ushort) -> c_ushort ---
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-htonl)
-	@(deprecated="Use endian specific integers instead, https://odin-lang.org/docs/overview/#basic-types")
+	// Prefer using endian-specific integers instead, https://odin-lang.org/docs/overview/#basic-types
 	htonl :: proc(hostlong: c_ulong) -> c_ulong ---
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-htons)
-	@(deprecated="Use endian specific integers instead, https://odin-lang.org/docs/overview/#basic-types")
+	// Prefer using endian-specific integers instead, https://odin-lang.org/docs/overview/#basic-types
 	htons :: proc(hostshort: c_ushort) -> c_ushort ---
-}
+}

+ 1 - 6
core/time/timezone/tzif.odin

@@ -577,12 +577,7 @@ parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allo
 	footer_str := string(buffer[:end_idx])
 
 	// UTC is a special case, we don't need to alloc
-	if len(local_time_types) == 1 {
-		name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:]))
-		if name != "UTC" {
-			return
-		}
-
+	if len(local_time_types) == 1 && local_time_types[0].utoff == 0 {
 		return nil, true
 	}
 

+ 2 - 0
src/check_builtin.cpp

@@ -41,6 +41,7 @@ gb_global BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_bool
 	is_type_enum,
 	is_type_proc,
 	is_type_bit_set,
+	is_type_bit_field,
 	is_type_simd_vector,
 	is_type_matrix,
 
@@ -6081,6 +6082,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 	case BuiltinProc_type_is_enum:
 	case BuiltinProc_type_is_proc:
 	case BuiltinProc_type_is_bit_set:
+	case BuiltinProc_type_is_bit_field:
 	case BuiltinProc_type_is_simd_vector:
 	case BuiltinProc_type_is_matrix:
 	case BuiltinProc_type_is_specialized_polymorphic_record:

+ 1 - 0
src/check_decl.cpp

@@ -1370,6 +1370,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 	e->Procedure.has_instrumentation = has_instrumentation;
 
 	e->Procedure.no_sanitize_address = ac.no_sanitize_address;
+	e->Procedure.no_sanitize_memory  = ac.no_sanitize_memory;
 
 	e->deprecated_message = ac.deprecated_message;
 	e->warning_message = ac.warning_message;

+ 14 - 0
src/check_stmt.cpp

@@ -1593,6 +1593,20 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 			error_line("\tSuggestion: Was '#partial switch' wanted?\n");
 		}
 	}
+
+	if (build_context.strict_style) {
+		Token stok = ss->token;
+		for_array(i, bs->stmts) {
+			Ast *stmt = bs->stmts[i];
+			if (stmt->kind != Ast_CaseClause) {
+				continue;
+			}
+			Token ctok = stmt->CaseClause.token;
+			if (ctok.pos.column > stok.pos.column) {
+				error(ctok, "With '-strict-style', 'case' statements must share the same column as the 'switch' token");
+			}
+		}
+	}
 }
 
 gb_internal void check_block_stmt_for_errors(CheckerContext *ctx, Ast *body)  {

+ 9 - 3
src/checker.cpp

@@ -2054,7 +2054,7 @@ gb_internal void add_type_info_type(CheckerContext *c, Type *t) {
 }
 
 gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
-	if (t == nullptr) {
+	if (t == nullptr || c == nullptr) {
 		return;
 	}
 
@@ -3776,6 +3776,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
 		}
 		ac->no_sanitize_address = true;
 		return true;
+	} else if (name == "no_sanitize_memory") {
+		if (value != nullptr) {
+			error(value, "'%.*s' expects no parameter", LIT(name));
+		}
+		ac->no_sanitize_memory = true;
+		return true;
 	}
 	return false;
 }
@@ -6672,7 +6678,7 @@ gb_internal void check_sort_init_and_fini_procedures(Checker *c) {
 gb_internal void add_type_info_for_type_definitions(Checker *c) {
 	for_array(i, c->info.definitions) {
 		Entity *e = c->info.definitions[i];
-		if (e->kind == Entity_TypeName && e->type != nullptr) {
+		if (e->kind == Entity_TypeName && e->type != nullptr && is_type_typed(e->type)) {
 			i64 align = type_align_of(e->type);
 			if (align > 0 && ptr_set_exists(&c->info.minimum_dependency_set, e)) {
 				add_type_info_type(&c->builtin_ctx, e->type);
@@ -6794,7 +6800,7 @@ gb_internal void check_parsed_files(Checker *c) {
 	// NOTE(bill): Check for illegal cyclic type declarations
 	for_array(i, c->info.definitions) {
 		Entity *e = c->info.definitions[i];
-		if (e->kind == Entity_TypeName && e->type != nullptr) {
+		if (e->kind == Entity_TypeName && e->type != nullptr && is_type_typed(e->type)) {
 			(void)type_align_of(e->type);
 		} else if (e->kind == Entity_Procedure) {
 			DeclInfo *decl = e->decl_info;

+ 1 - 0
src/checker.hpp

@@ -140,6 +140,7 @@ struct AttributeContext {
 	bool    instrumentation_enter : 1;
 	bool    instrumentation_exit  : 1;
 	bool    no_sanitize_address   : 1;
+	bool    no_sanitize_memory    : 1;
 	bool    rodata                : 1;
 	bool    ignore_duplicates     : 1;
 	u32 optimization_mode; // ProcedureOptimizationMode

+ 2 - 0
src/checker_builtin_procs.hpp

@@ -240,6 +240,7 @@ BuiltinProc__type_begin,
 
 BuiltinProc__type_simple_boolean_begin,
 	BuiltinProc_type_is_boolean,
+	BuiltinProc_type_is_bit_field,
 	BuiltinProc_type_is_integer,
 	BuiltinProc_type_is_rune,
 	BuiltinProc_type_is_float,
@@ -630,6 +631,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("type_is_enum"),              1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_proc"),              1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_bit_set"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_is_bit_field"),         1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_simd_vector"),       1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_matrix"),            1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 

+ 1 - 0
src/entity.cpp

@@ -263,6 +263,7 @@ struct Entity {
 			bool    uses_branch_location       : 1;
 			bool    is_anonymous               : 1;
 			bool    no_sanitize_address        : 1;
+			bool    no_sanitize_memory         : 1;
 		} Procedure;
 		struct {
 			Array<Entity *> entities;

+ 11 - 0
src/llvm_backend_const.cpp

@@ -1054,9 +1054,20 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
 					}
 				}
 
+				res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc);
+				return res;
+			} else if (value.value_compound->tav.type == elem_type) {
+				// Compound is of array item type; expand its value to all items in array.
+				LLVMValueRef* values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)type->Array.count);
+
+				for (isize i = 0; i < type->Array.count; i++) {
+					values[i] = lb_const_value(m, elem_type, value, cc).value;
+				}
+
 				res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc);
 				return res;
 			} else {
+				// Assume that compound value is an array literal
 				GB_ASSERT_MSG(elem_count == type->Array.count, "%td != %td", elem_count, type->Array.count);
 
 				LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)type->Array.count);

+ 1 - 1
src/llvm_backend_proc.cpp

@@ -345,7 +345,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
 		if (build_context.sanitizer_flags & SanitizerFlag_Address && !entity->Procedure.no_sanitize_address) {
 			lb_add_attribute_to_proc(m, p->value, "sanitize_address");
 		}
-		if (build_context.sanitizer_flags & SanitizerFlag_Memory) {
+		if (build_context.sanitizer_flags & SanitizerFlag_Memory && !entity->Procedure.no_sanitize_memory) {
 			lb_add_attribute_to_proc(m, p->value, "sanitize_memory");
 		}
 		if (build_context.sanitizer_flags & SanitizerFlag_Thread) {

+ 52 - 52
src/main.cpp

@@ -1996,39 +1996,39 @@ gb_internal void show_timings(Checker *c, Timings *t) {
 
 	if (build_context.show_debug_messages && build_context.show_more_timings) {
 		{
-			gb_printf("\n");
-			gb_printf("Total Lines     - %td\n", lines);
-			gb_printf("Total Tokens    - %td\n", tokens);
-			gb_printf("Total Files     - %td\n", files);
-			gb_printf("Total Packages  - %td\n", packages);
-			gb_printf("Total File Size - %td\n", total_file_size);
-			gb_printf("\n");
+			gb_printf_err("\n");
+			gb_printf_err("Total Lines     - %td\n", lines);
+			gb_printf_err("Total Tokens    - %td\n", tokens);
+			gb_printf_err("Total Files     - %td\n", files);
+			gb_printf_err("Total Packages  - %td\n", packages);
+			gb_printf_err("Total File Size - %td\n", total_file_size);
+			gb_printf_err("\n");
 		}
 		{
 			f64 time = total_tokenizing_time;
-			gb_printf("Tokenization Only\n");
-			gb_printf("LOC/s        - %.3f\n", cast(f64)lines/time);
-			gb_printf("us/LOC       - %.3f\n", 1.0e6*time/cast(f64)lines);
-			gb_printf("Tokens/s     - %.3f\n", cast(f64)tokens/time);
-			gb_printf("us/Token     - %.3f\n", 1.0e6*time/cast(f64)tokens);
-			gb_printf("bytes/s      - %.3f\n", cast(f64)total_file_size/time);
-			gb_printf("MiB/s        - %.3f\n", cast(f64)(total_file_size/time)/(1024*1024));
-			gb_printf("us/bytes     - %.3f\n", 1.0e6*time/cast(f64)total_file_size);
+			gb_printf_err("Tokenization Only\n");
+			gb_printf_err("LOC/s        - %.3f\n", cast(f64)lines/time);
+			gb_printf_err("us/LOC       - %.3f\n", 1.0e6*time/cast(f64)lines);
+			gb_printf_err("Tokens/s     - %.3f\n", cast(f64)tokens/time);
+			gb_printf_err("us/Token     - %.3f\n", 1.0e6*time/cast(f64)tokens);
+			gb_printf_err("bytes/s      - %.3f\n", cast(f64)total_file_size/time);
+			gb_printf_err("MiB/s        - %.3f\n", cast(f64)(total_file_size/time)/(1024*1024));
+			gb_printf_err("us/bytes     - %.3f\n", 1.0e6*time/cast(f64)total_file_size);
 
-			gb_printf("\n");
+			gb_printf_err("\n");
 		}
 		{
 			f64 time = total_parsing_time;
-			gb_printf("Parsing Only\n");
-			gb_printf("LOC/s        - %.3f\n", cast(f64)lines/time);
-			gb_printf("us/LOC       - %.3f\n", 1.0e6*time/cast(f64)lines);
-			gb_printf("Tokens/s     - %.3f\n", cast(f64)tokens/time);
-			gb_printf("us/Token     - %.3f\n", 1.0e6*time/cast(f64)tokens);
-			gb_printf("bytes/s      - %.3f\n", cast(f64)total_file_size/time);
-			gb_printf("MiB/s        - %.3f\n", cast(f64)(total_file_size/time)/(1024*1024));
-			gb_printf("us/bytes     - %.3f\n", 1.0e6*time/cast(f64)total_file_size);
+			gb_printf_err("Parsing Only\n");
+			gb_printf_err("LOC/s        - %.3f\n", cast(f64)lines/time);
+			gb_printf_err("us/LOC       - %.3f\n", 1.0e6*time/cast(f64)lines);
+			gb_printf_err("Tokens/s     - %.3f\n", cast(f64)tokens/time);
+			gb_printf_err("us/Token     - %.3f\n", 1.0e6*time/cast(f64)tokens);
+			gb_printf_err("bytes/s      - %.3f\n", cast(f64)total_file_size/time);
+			gb_printf_err("MiB/s        - %.3f\n", cast(f64)(total_file_size/time)/(1024*1024));
+			gb_printf_err("us/bytes     - %.3f\n", 1.0e6*time/cast(f64)total_file_size);
 
-			gb_printf("\n");
+			gb_printf_err("\n");
 		}
 		{
 			TimeStamp ts = {};
@@ -2041,16 +2041,16 @@ gb_internal void show_timings(Checker *c, Timings *t) {
 			GB_ASSERT(ts.label == "parse files");
 
 			f64 parse_time = time_stamp_as_s(ts, t->freq);
-			gb_printf("Parse pass\n");
-			gb_printf("LOC/s        - %.3f\n", cast(f64)lines/parse_time);
-			gb_printf("us/LOC       - %.3f\n", 1.0e6*parse_time/cast(f64)lines);
-			gb_printf("Tokens/s     - %.3f\n", cast(f64)tokens/parse_time);
-			gb_printf("us/Token     - %.3f\n", 1.0e6*parse_time/cast(f64)tokens);
-			gb_printf("bytes/s      - %.3f\n", cast(f64)total_file_size/parse_time);
-			gb_printf("MiB/s        - %.3f\n", cast(f64)(total_file_size/parse_time)/(1024*1024));
-			gb_printf("us/bytes     - %.3f\n", 1.0e6*parse_time/cast(f64)total_file_size);
+			gb_printf_err("Parse pass\n");
+			gb_printf_err("LOC/s        - %.3f\n", cast(f64)lines/parse_time);
+			gb_printf_err("us/LOC       - %.3f\n", 1.0e6*parse_time/cast(f64)lines);
+			gb_printf_err("Tokens/s     - %.3f\n", cast(f64)tokens/parse_time);
+			gb_printf_err("us/Token     - %.3f\n", 1.0e6*parse_time/cast(f64)tokens);
+			gb_printf_err("bytes/s      - %.3f\n", cast(f64)total_file_size/parse_time);
+			gb_printf_err("MiB/s        - %.3f\n", cast(f64)(total_file_size/parse_time)/(1024*1024));
+			gb_printf_err("us/bytes     - %.3f\n", 1.0e6*parse_time/cast(f64)total_file_size);
 
-			gb_printf("\n");
+			gb_printf_err("\n");
 		}
 		{
 			TimeStamp ts = {};
@@ -2071,27 +2071,27 @@ gb_internal void show_timings(Checker *c, Timings *t) {
 			ts.finish = ts_end.finish;
 
 			f64 parse_time = time_stamp_as_s(ts, t->freq);
-			gb_printf("Checker pass\n");
-			gb_printf("LOC/s        - %.3f\n", cast(f64)lines/parse_time);
-			gb_printf("us/LOC       - %.3f\n", 1.0e6*parse_time/cast(f64)lines);
-			gb_printf("Tokens/s     - %.3f\n", cast(f64)tokens/parse_time);
-			gb_printf("us/Token     - %.3f\n", 1.0e6*parse_time/cast(f64)tokens);
-			gb_printf("bytes/s      - %.3f\n", cast(f64)total_file_size/parse_time);
-			gb_printf("MiB/s        - %.3f\n", (cast(f64)total_file_size/parse_time)/(1024*1024));
-			gb_printf("us/bytes     - %.3f\n", 1.0e6*parse_time/cast(f64)total_file_size);
-			gb_printf("\n");
+			gb_printf_err("Checker pass\n");
+			gb_printf_err("LOC/s        - %.3f\n", cast(f64)lines/parse_time);
+			gb_printf_err("us/LOC       - %.3f\n", 1.0e6*parse_time/cast(f64)lines);
+			gb_printf_err("Tokens/s     - %.3f\n", cast(f64)tokens/parse_time);
+			gb_printf_err("us/Token     - %.3f\n", 1.0e6*parse_time/cast(f64)tokens);
+			gb_printf_err("bytes/s      - %.3f\n", cast(f64)total_file_size/parse_time);
+			gb_printf_err("MiB/s        - %.3f\n", (cast(f64)total_file_size/parse_time)/(1024*1024));
+			gb_printf_err("us/bytes     - %.3f\n", 1.0e6*parse_time/cast(f64)total_file_size);
+			gb_printf_err("\n");
 		}
 		{
 			f64 total_time = t->total_time_seconds;
-			gb_printf("Total pass\n");
-			gb_printf("LOC/s        - %.3f\n", cast(f64)lines/total_time);
-			gb_printf("us/LOC       - %.3f\n", 1.0e6*total_time/cast(f64)lines);
-			gb_printf("Tokens/s     - %.3f\n", cast(f64)tokens/total_time);
-			gb_printf("us/Token     - %.3f\n", 1.0e6*total_time/cast(f64)tokens);
-			gb_printf("bytes/s      - %.3f\n", cast(f64)total_file_size/total_time);
-			gb_printf("MiB/s        - %.3f\n", cast(f64)(total_file_size/total_time)/(1024*1024));
-			gb_printf("us/bytes     - %.3f\n", 1.0e6*total_time/cast(f64)total_file_size);
-			gb_printf("\n");
+			gb_printf_err("Total pass\n");
+			gb_printf_err("LOC/s        - %.3f\n", cast(f64)lines/total_time);
+			gb_printf_err("us/LOC       - %.3f\n", 1.0e6*total_time/cast(f64)lines);
+			gb_printf_err("Tokens/s     - %.3f\n", cast(f64)tokens/total_time);
+			gb_printf_err("us/Token     - %.3f\n", 1.0e6*total_time/cast(f64)tokens);
+			gb_printf_err("bytes/s      - %.3f\n", cast(f64)total_file_size/total_time);
+			gb_printf_err("MiB/s        - %.3f\n", cast(f64)(total_file_size/total_time)/(1024*1024));
+			gb_printf_err("us/bytes     - %.3f\n", 1.0e6*total_time/cast(f64)total_file_size);
+			gb_printf_err("\n");
 		}
 	}
 }

+ 2 - 2
src/parser.cpp

@@ -5794,7 +5794,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const
 	for (FileInfo fi : list) {
 		String name = fi.name;
 		String ext = path_extension(name);
-		if (ext == FILE_EXT) {
+		if (ext == FILE_EXT && !path_is_directory(name)) {
 			files_with_ext += 1;
 		}
 		if (ext == FILE_EXT && !is_excluded_target_filename(name)) {
@@ -5819,7 +5819,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const
 	for (FileInfo fi : list) {
 		String name = fi.name;
 		String ext = path_extension(name);
-		if (ext == FILE_EXT) {
+		if (ext == FILE_EXT && !path_is_directory(name)) {
 			if (is_excluded_target_filename(name)) {
 				continue;
 			}

+ 2 - 2
src/timings.cpp

@@ -197,7 +197,7 @@ gb_internal void timings_print_all(Timings *t, TimingUnit unit = TimingUnit_Mill
 
 	f64 total_time = time_stamp(t->total, t->freq, unit);
 
-	gb_printf("%.*s%.*s - % 9.3f %s - %6.2f%%\n",
+	gb_printf_err("%.*s%.*s - % 9.3f %s - %6.2f%%\n",
 	          LIT(t->total.label),
 	          cast(int)(max_len-t->total.label.len), SPACES,
 	          total_time,
@@ -207,7 +207,7 @@ gb_internal void timings_print_all(Timings *t, TimingUnit unit = TimingUnit_Mill
 	for_array(i, t->sections) {
 		TimeStamp ts = t->sections[i];
 		f64 section_time = time_stamp(ts, t->freq, unit);
-		gb_printf("%.*s%.*s - % 9.3f %s - %6.2f%%\n",
+		gb_printf_err("%.*s%.*s - % 9.3f %s - %6.2f%%\n",
 		          LIT(ts.label),
 	              cast(int)(max_len-ts.label.len), SPACES,
 		          section_time,

+ 28 - 0
tests/core/encoding/cbor/test_core_cbor.odin

@@ -43,6 +43,8 @@ Foo :: struct {
 	biggest: big.Int,
 	smallest: big.Int,
 	ignore_this: ^Foo `cbor:"-"`,
+	mat: matrix[4, 4]f32,
+	vec: #simd [4]f64,
 }
 
 FooBar :: enum {
@@ -95,6 +97,8 @@ test_marshalling :: proc(t: ^testing.T) {
 			onetwenty = i128(12345),
 			small_onetwenty = -i128(max(u64)),
 			ignore_this = &Foo{},
+			mat = 1,
+			vec = 2,
 		}
 
 		big.atoi(&f.biggest, "1234567891011121314151617181920")
@@ -120,11 +124,35 @@ test_marshalling :: proc(t: ^testing.T) {
 		defer delete(diagnosis)
 		testing.expect_value(t, diagnosis, `{
 	"no": null,
+	"mat": [
+		1.0000,
+		0.0000,
+		0.0000,
+		0.0000,
+		0.0000,
+		1.0000,
+		0.0000,
+		0.0000,
+		0.0000,
+		0.0000,
+		1.0000,
+		0.0000,
+		0.0000,
+		0.0000,
+		0.0000,
+		1.0000
+	],
 	"neg": -69,
 	"nos": undefined,
 	"now": 1(1701117968),
 	"pos": 1212,
 	"str": "Hellope",
+	"vec": [
+		2.0000,
+		2.0000,
+		2.0000,
+		2.0000
+	],
 	"yes": true,
 	"comp": [
 		32.0000,

+ 1 - 1
tests/core/math/test_core_math.odin

@@ -1238,7 +1238,7 @@ test_count_digits :: proc(t: ^testing.T) {
 		buf: [64]u8
 		for n in 0..<i64(base*base*base) {
 			count := math.count_digits_of_base(n, base)
-			str := strconv.append_int(buf[:], n, base)
+			str := strconv.write_int(buf[:], n, base)
 			if !testing.expectf(t,
 				len(str) == count,
 				"decimal %i in base-%i digit count is %i, does not match length %i of %q",

+ 1 - 0
tests/issues/run.bat

@@ -17,6 +17,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style
 ..\..\..\odin test ..\test_issue_2637.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_2666.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_4210.odin %COMMON%  || exit /b
+..\..\..\odin test ..\test_issue_4364.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_4584.odin %COMMON%  || exit /b
 ..\..\..\odin build ..\test_issue_5043.odin %COMMON% || exit /b
 ..\..\..\odin build ..\test_issue_5097.odin %COMMON% || exit /b

+ 1 - 0
tests/issues/run.sh

@@ -18,6 +18,7 @@ $ODIN test ../test_issue_2615.odin $COMMON
 $ODIN test ../test_issue_2637.odin $COMMON
 $ODIN test ../test_issue_2666.odin $COMMON
 $ODIN test ../test_issue_4210.odin $COMMON
+$ODIN test ../test_issue_4364.odin $COMMON
 $ODIN test ../test_issue_4584.odin $COMMON
 if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then
 	echo "SUCCESSFUL 1/1"

+ 18 - 0
tests/issues/test_issue_4364.odin

@@ -0,0 +1,18 @@
+// Tests issue #4364 https://github.com/odin-lang/Odin/issues/4364
+package test_issues
+
+import "core:testing"
+
+@test
+test_const_array_fill_assignment :: proc(t: ^testing.T) {
+	MAGIC :: 12345
+	Struct :: struct {x: int}
+	CONST_ARR : [4]Struct : Struct{MAGIC}
+	arr := CONST_ARR
+
+	testing.expect_value(t, len(arr), 4)
+	testing.expect_value(t, arr[0], Struct{MAGIC})
+	testing.expect_value(t, arr[1], Struct{MAGIC})
+	testing.expect_value(t, arr[2], Struct{MAGIC})
+	testing.expect_value(t, arr[3], Struct{MAGIC})
+}

+ 3 - 3
vendor/directx/dxc/dxcapi.odin

@@ -194,7 +194,7 @@ ICompiler_VTable :: struct {
 	using iunknown_vtable: IUnknown_VTable,
 	Compile: proc "system" (
 		this: ^ICompiler, 
-		pSource: ^Buffer, 
+		pSource: ^IBlob,
 		pSourceName: wstring,
 		pEntryPoint: wstring,
 		pTargetProfile: wstring,
@@ -206,7 +206,7 @@ ICompiler_VTable :: struct {
 		ppResult: ^^IOperationResult) -> HRESULT,
 	Preprocess: proc "system" (
 		this: ^ICompiler, 
-		pSource: ^Buffer, 
+		pSource: ^IBlob,
 		pSourceName: wstring,
 		pArguments: [^]wstring,
 		argCount: u32,
@@ -227,7 +227,7 @@ ICompiler2_VTable :: struct {
 	using idxccompiler_vtable: ^ICompiler_VTable,
 	CompileWithDebug: proc "system" (
 		this: ^ICompiler2,
-		pSource: ^Buffer, 
+		pSource: ^IBlob,
 		pSourceName: wstring,
 		pEntryPoint: wstring,
 		pTargetProfile: wstring,