mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Merge branch 'trunk' into dylib-roc-benchmarks
This commit is contained in:
commit
b28561d1c9
68 changed files with 2297 additions and 818 deletions
|
@ -65,6 +65,16 @@ pub fn link(
|
|||
}
|
||||
|
||||
fn find_zig_str_path() -> PathBuf {
|
||||
// First try using the lib path relative to the executable location.
|
||||
let exe_relative_str_path = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|path| Some(path.parent()?.join("lib").join("str.zig")));
|
||||
if let Some(exe_relative_str_path) = exe_relative_str_path {
|
||||
if std::path::Path::exists(&exe_relative_str_path) {
|
||||
return exe_relative_str_path;
|
||||
}
|
||||
}
|
||||
|
||||
let zig_str_path = PathBuf::from("crates/compiler/builtins/bitcode/src/str.zig");
|
||||
|
||||
if std::path::Path::exists(&zig_str_path) {
|
||||
|
@ -1090,7 +1100,7 @@ fn link_windows(
|
|||
todo!("Add windows support to the surgical linker. See issue #2608.")
|
||||
}
|
||||
|
||||
pub fn module_to_dylib(
|
||||
pub fn llvm_module_to_dylib(
|
||||
module: &inkwell::module::Module,
|
||||
target: &Triple,
|
||||
opt_level: OptLevel,
|
||||
|
|
|
@ -16,6 +16,7 @@ lazy_static = "1.4.0"
|
|||
[build-dependencies]
|
||||
# dunce can be removed once ziglang/zig#5109 is fixed
|
||||
dunce = "1.0.2"
|
||||
fs_extra = "1.2.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
|
|
|
@ -146,6 +146,7 @@ comptime {
|
|||
exportStrFn(str.strSplitInPlaceC, "str_split_in_place");
|
||||
exportStrFn(str.countSegments, "count_segments");
|
||||
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
||||
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
|
||||
exportStrFn(str.startsWith, "starts_with");
|
||||
exportStrFn(str.startsWithScalar, "starts_with_scalar");
|
||||
exportStrFn(str.endsWith, "ends_with");
|
||||
|
@ -154,6 +155,11 @@ comptime {
|
|||
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
|
||||
exportStrFn(str.strFromFloatC, "from_float");
|
||||
exportStrFn(str.strEqual, "equal");
|
||||
exportStrFn(str.substringUnsafe, "substring_unsafe");
|
||||
exportStrFn(str.getUnsafe, "get_unsafe");
|
||||
exportStrFn(str.reserve, "reserve");
|
||||
exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe");
|
||||
exportStrFn(str.appendScalar, "append_scalar");
|
||||
exportStrFn(str.strToUtf8C, "to_utf8");
|
||||
exportStrFn(str.fromUtf8C, "from_utf8");
|
||||
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
|
||||
|
|
|
@ -112,7 +112,7 @@ pub const RocStr = extern struct {
|
|||
|
||||
pub fn eq(self: RocStr, other: RocStr) bool {
|
||||
// If they are byte-for-byte equal, they're definitely equal!
|
||||
if (self.str_bytes == other.str_bytes and self.str_len == other.str_len) {
|
||||
if (self.str_bytes == other.str_bytes and self.str_len == other.str_len and self.str_capacity == other.str_capacity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -173,10 +173,10 @@ pub const RocStr = extern struct {
|
|||
const element_width = 1;
|
||||
|
||||
if (self.str_bytes) |source_ptr| {
|
||||
if (self.isUnique()) {
|
||||
if (self.isUnique() and !self.isSmallStr()) {
|
||||
const new_source = utils.unsafeReallocate(source_ptr, RocStr.alignment, self.len(), new_length, element_width);
|
||||
|
||||
return RocStr{ .str_bytes = new_source, .str_len = new_length };
|
||||
return RocStr{ .str_bytes = new_source, .str_len = new_length, .str_capacity = new_length };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,11 @@ pub const RocStr = extern struct {
|
|||
}
|
||||
|
||||
pub fn capacity(self: RocStr) usize {
|
||||
return self.str_capacity ^ MASK;
|
||||
if (self.isSmallStr()) {
|
||||
return SMALL_STR_MAX_LENGTH;
|
||||
} else {
|
||||
return self.str_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
// This does a small string check, but no bounds checking whatsoever!
|
||||
|
@ -311,6 +315,10 @@ pub const RocStr = extern struct {
|
|||
return self.asU8ptr()[0..self.len()];
|
||||
}
|
||||
|
||||
pub fn asSliceWithCapacity(self: RocStr) []u8 {
|
||||
return self.asU8ptr()[0..self.capacity()];
|
||||
}
|
||||
|
||||
pub fn asU8ptr(self: RocStr) [*]u8 {
|
||||
|
||||
// Since this conditional would be prone to branch misprediction,
|
||||
|
@ -1199,6 +1207,56 @@ test "countGraphemeClusters: emojis, ut8, and ascii characters" {
|
|||
try expectEqual(count, 10);
|
||||
}
|
||||
|
||||
pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
|
||||
return string.len();
|
||||
}
|
||||
|
||||
pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr {
|
||||
const slice = string.asSlice()[start .. start + length];
|
||||
|
||||
return RocStr.fromSlice(slice);
|
||||
}
|
||||
|
||||
pub fn getUnsafe(string: RocStr, index: usize) callconv(.C) u8 {
|
||||
return string.getUnchecked(index);
|
||||
}
|
||||
|
||||
test "substringUnsafe: start" {
|
||||
const str = RocStr.fromSlice("abcdef");
|
||||
defer str.deinit();
|
||||
|
||||
const expected = RocStr.fromSlice("abc");
|
||||
defer expected.deinit();
|
||||
|
||||
const actual = substringUnsafe(str, 0, 3);
|
||||
|
||||
try expect(RocStr.eq(actual, expected));
|
||||
}
|
||||
|
||||
test "substringUnsafe: middle" {
|
||||
const str = RocStr.fromSlice("abcdef");
|
||||
defer str.deinit();
|
||||
|
||||
const expected = RocStr.fromSlice("bcd");
|
||||
defer expected.deinit();
|
||||
|
||||
const actual = substringUnsafe(str, 1, 3);
|
||||
|
||||
try expect(RocStr.eq(actual, expected));
|
||||
}
|
||||
|
||||
test "substringUnsafe: end" {
|
||||
const str = RocStr.fromSlice("a string so long it is heap-allocated");
|
||||
defer str.deinit();
|
||||
|
||||
const expected = RocStr.fromSlice("heap-allocated");
|
||||
defer expected.deinit();
|
||||
|
||||
const actual = substringUnsafe(str, 23, 37 - 23);
|
||||
|
||||
try expect(RocStr.eq(actual, expected));
|
||||
}
|
||||
|
||||
// Str.startsWith
|
||||
pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
|
||||
const bytes_len = string.len();
|
||||
|
@ -2315,3 +2373,119 @@ test "ReverseUtf8View: empty" {
|
|||
try expect(false);
|
||||
}
|
||||
}
|
||||
|
||||
test "capacity: small string" {
|
||||
const data_bytes = "foobar";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
defer data.deinit();
|
||||
|
||||
try expectEqual(data.capacity(), SMALL_STR_MAX_LENGTH);
|
||||
}
|
||||
|
||||
test "capacity: big string" {
|
||||
const data_bytes = "a string so large that it must be heap-allocated";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
defer data.deinit();
|
||||
|
||||
try expectEqual(data.capacity(), data_bytes.len);
|
||||
}
|
||||
|
||||
pub fn appendScalar(string: RocStr, scalar_u32: u32) callconv(.C) RocStr {
|
||||
const scalar = @intCast(u21, scalar_u32);
|
||||
const width = std.unicode.utf8CodepointSequenceLength(scalar) catch unreachable;
|
||||
|
||||
var output = string.reallocate(string.len() + width);
|
||||
var slice = output.asSliceWithCapacity();
|
||||
|
||||
_ = std.unicode.utf8Encode(scalar, slice[string.len() .. string.len() + width]) catch unreachable;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
test "appendScalar: small A" {
|
||||
const A: []const u8 = "A";
|
||||
|
||||
const data_bytes = "hello";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, A[0]);
|
||||
defer actual.deinit();
|
||||
|
||||
const expected_bytes = "helloA";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
test "appendScalar: small 😀" {
|
||||
const data_bytes = "hello";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, 0x1F600);
|
||||
defer actual.deinit();
|
||||
|
||||
const expected_bytes = "hello😀";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
test "appendScalar: big A" {
|
||||
const A: []const u8 = "A";
|
||||
|
||||
const data_bytes = "a string so large that it must be heap-allocated";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, A[0]);
|
||||
defer actual.deinit();
|
||||
|
||||
const expected_bytes = "a string so large that it must be heap-allocatedA";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
test "appendScalar: big 😀" {
|
||||
const data_bytes = "a string so large that it must be heap-allocated";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const actual = appendScalar(data, 0x1F600);
|
||||
defer actual.deinit();
|
||||
|
||||
const expected_bytes = "a string so large that it must be heap-allocated😀";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
pub fn reserve(string: RocStr, capacity: usize) callconv(.C) RocStr {
|
||||
if (capacity > string.capacity()) {
|
||||
return string.reallocate(capacity);
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getScalarUnsafe(string: RocStr, index: usize) callconv(.C) extern struct { bytesParsed: usize, scalar: u32 } {
|
||||
const slice = string.asSlice();
|
||||
const bytesParsed = @intCast(usize, std.unicode.utf8ByteSequenceLength(slice[index]) catch unreachable);
|
||||
const scalar = std.unicode.utf8Decode(slice[index .. index + bytesParsed]) catch unreachable;
|
||||
|
||||
return .{ .bytesParsed = bytesParsed, .scalar = @intCast(u32, scalar) };
|
||||
}
|
||||
|
||||
test "getScalarUnsafe" {
|
||||
const data_bytes = "A";
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
|
||||
const result = getScalarUnsafe(data, 0);
|
||||
|
||||
const expected = try std.unicode.utf8Decode("A");
|
||||
|
||||
try expectEqual(result.scalar, @intCast(u32, expected));
|
||||
try expectEqual(result.bytesParsed, 1);
|
||||
}
|
||||
|
|
|
@ -256,6 +256,10 @@ pub fn unsafeReallocate(
|
|||
const old_width = align_width + old_length * element_width;
|
||||
const new_width = align_width + new_length * element_width;
|
||||
|
||||
if (old_width == new_width) {
|
||||
return source_ptr;
|
||||
}
|
||||
|
||||
// TODO handle out of memory
|
||||
// NOTE realloc will dealloc the original allocation
|
||||
const old_allocation = source_ptr - align_width;
|
||||
|
|
|
@ -67,6 +67,8 @@ fn main() {
|
|||
"builtins-wasm32.o",
|
||||
);
|
||||
|
||||
copy_zig_builtins_to_target_dir(&bitcode_path);
|
||||
|
||||
get_zig_files(bitcode_path.as_path(), &|path| {
|
||||
let path: &Path = path;
|
||||
println!(
|
||||
|
@ -144,6 +146,39 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
|||
);
|
||||
}
|
||||
|
||||
fn copy_zig_builtins_to_target_dir(bitcode_path: &Path) {
|
||||
// To enable roc to find the zig biultins, we want them to be moved to a folder next to the roc executable.
|
||||
// So if <roc_folder>/roc is the executable. The zig files will be in <roc_folder>/lib/*.zig
|
||||
|
||||
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
|
||||
// So we just need to shed a 3 of the outer layers to get `/target/debug/` and then add `lib`.
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let target_profile_dir = Path::new(&out_dir)
|
||||
.parent()
|
||||
.and_then(|path| path.parent())
|
||||
.and_then(|path| path.parent())
|
||||
.unwrap()
|
||||
.join("lib");
|
||||
|
||||
let zig_src_dir = bitcode_path.join("src");
|
||||
|
||||
std::fs::create_dir_all(&target_profile_dir).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to create output library directory for zig bitcode {:?}: {:?}",
|
||||
target_profile_dir, err
|
||||
);
|
||||
});
|
||||
let mut options = fs_extra::dir::CopyOptions::new();
|
||||
options.content_only = true;
|
||||
options.overwrite = true;
|
||||
fs_extra::dir::copy(&zig_src_dir, &target_profile_dir, &options).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to copy zig bitcode files {:?} to {:?}: {:?}",
|
||||
zig_src_dir, target_profile_dir, err
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn run_command<S, I: Copy, P: AsRef<Path> + Copy>(path: P, command_str: &str, args: I)
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
|
|
|
@ -373,17 +373,11 @@ contains = \list, needle ->
|
|||
## `fold`, `foldLeft`, or `foldl`.
|
||||
walk : List elem, state, (state, elem -> state) -> state
|
||||
walk = \list, state, func ->
|
||||
walkHelp list state func 0 (len list)
|
||||
walkHelp = \currentState, element -> Continue (func currentState element)
|
||||
|
||||
## internal helper
|
||||
walkHelp : List elem, state, (state, elem -> state), Nat, Nat -> state
|
||||
walkHelp = \list, state, f, index, length ->
|
||||
if index < length then
|
||||
nextState = f state (getUnsafe list index)
|
||||
|
||||
walkHelp list nextState f (index + 1) length
|
||||
else
|
||||
state
|
||||
when List.iterate list state walkHelp is
|
||||
Continue newState -> newState
|
||||
Break void -> List.unreachable void
|
||||
|
||||
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
|
||||
## `fold`, `foldRight`, or `foldr`.
|
||||
|
@ -598,7 +592,7 @@ range = \start, end ->
|
|||
GT -> []
|
||||
EQ -> [start]
|
||||
LT ->
|
||||
length = Num.intCast (start - end)
|
||||
length = Num.intCast (end - start)
|
||||
|
||||
rangeHelp (List.withCapacity length) start end
|
||||
|
||||
|
@ -841,3 +835,6 @@ iterHelp = \list, state, f, index, length ->
|
|||
Break b
|
||||
else
|
||||
Continue state
|
||||
|
||||
## useful for typechecking guaranteed-unreachable cases
|
||||
unreachable : [] -> a
|
||||
|
|
|
@ -9,6 +9,7 @@ interface Str
|
|||
split,
|
||||
repeat,
|
||||
countGraphemes,
|
||||
countUtf8Bytes,
|
||||
startsWithScalar,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
|
@ -33,6 +34,13 @@ interface Str
|
|||
toU8,
|
||||
toI8,
|
||||
toScalars,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8WithIndex,
|
||||
reserve,
|
||||
appendScalar,
|
||||
walkScalars,
|
||||
walkScalarsUntil,
|
||||
]
|
||||
imports [Bool.{ Bool }, Result.{ Result }]
|
||||
|
||||
|
@ -221,3 +229,169 @@ toU16 : Str -> Result U16 [InvalidNumStr]*
|
|||
toI16 : Str -> Result I16 [InvalidNumStr]*
|
||||
toU8 : Str -> Result U8 [InvalidNumStr]*
|
||||
toI8 : Str -> Result I8 [InvalidNumStr]*
|
||||
|
||||
## Gets the byte at the given index, without performing a bounds check
|
||||
getUnsafe : Str, Nat -> U8
|
||||
|
||||
## gives the number of string bytes
|
||||
countUtf8Bytes : Str -> Nat
|
||||
|
||||
## string slice that does not do bounds checking or utf-8 verification
|
||||
substringUnsafe : Str, Nat, Nat -> Str
|
||||
|
||||
## Returns the string before the first occurrence of a delimiter, as well as the
|
||||
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
##
|
||||
## Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]*
|
||||
splitFirst = \haystack, needle ->
|
||||
when firstMatch haystack needle is
|
||||
Some index ->
|
||||
remaining = Str.countUtf8Bytes haystack - Str.countUtf8Bytes needle - index
|
||||
|
||||
before = Str.substringUnsafe haystack 0 index
|
||||
after = Str.substringUnsafe haystack (index + Str.countUtf8Bytes needle) remaining
|
||||
|
||||
Ok { before, after }
|
||||
None ->
|
||||
Err NotFound
|
||||
|
||||
firstMatch : Str, Str -> [Some Nat, None]
|
||||
firstMatch = \haystack, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
needleLength = Str.countUtf8Bytes needle
|
||||
lastPossible = Num.subSaturated haystackLength needleLength
|
||||
|
||||
firstMatchHelp haystack needle 0 lastPossible
|
||||
|
||||
firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None]
|
||||
firstMatchHelp = \haystack, needle, index, lastPossible ->
|
||||
if index < lastPossible then
|
||||
if matchesAt haystack index needle then
|
||||
Some index
|
||||
else
|
||||
firstMatchHelp haystack needle (index + 1) lastPossible
|
||||
else
|
||||
None
|
||||
|
||||
## Returns the string before the last occurrence of a delimiter, as well as the
|
||||
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
##
|
||||
## Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]*
|
||||
splitLast = \haystack, needle ->
|
||||
when lastMatch haystack needle is
|
||||
Some index ->
|
||||
remaining = Str.countUtf8Bytes haystack - Str.countUtf8Bytes needle - index
|
||||
|
||||
before = Str.substringUnsafe haystack 0 index
|
||||
after = Str.substringUnsafe haystack (index + Str.countUtf8Bytes needle) remaining
|
||||
|
||||
Ok { before, after }
|
||||
None ->
|
||||
Err NotFound
|
||||
|
||||
lastMatch : Str, Str -> [Some Nat, None]
|
||||
lastMatch = \haystack, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
needleLength = Str.countUtf8Bytes needle
|
||||
lastPossibleIndex = Num.subSaturated haystackLength (needleLength + 1)
|
||||
|
||||
lastMatchHelp haystack needle lastPossibleIndex
|
||||
|
||||
lastMatchHelp : Str, Str, Nat -> [Some Nat, None]
|
||||
lastMatchHelp = \haystack, needle, index ->
|
||||
if matchesAt haystack index needle then
|
||||
Some index
|
||||
else
|
||||
when Num.subChecked index 1 is
|
||||
Ok nextIndex ->
|
||||
lastMatchHelp haystack needle nextIndex
|
||||
Err _ ->
|
||||
None
|
||||
|
||||
min = \x, y -> if x < y then x else y
|
||||
|
||||
matchesAt : Str, Nat, Str -> Bool
|
||||
matchesAt = \haystack, haystackIndex, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
needleLength = Str.countUtf8Bytes needle
|
||||
endIndex = min (haystackIndex + needleLength) haystackLength
|
||||
|
||||
matchesAtHelp haystack haystackIndex needle 0 endIndex
|
||||
|
||||
matchesAtHelp : Str, Nat, Str, Nat, Nat -> Bool
|
||||
matchesAtHelp = \haystack, haystackIndex, needle, needleIndex, endIndex ->
|
||||
if haystackIndex < endIndex then
|
||||
if Str.getUnsafe haystack haystackIndex == Str.getUnsafe needle needleIndex then
|
||||
matchesAtHelp haystack (haystackIndex + 1) needle (needleIndex + 1) endIndex
|
||||
else
|
||||
False
|
||||
else
|
||||
True
|
||||
|
||||
## Walks over the string's UTF-8 bytes, calling a function which updates a state using each
|
||||
## UTF-8 `U8` byte as well as the index of that byte within the string.
|
||||
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
|
||||
walkUtf8WithIndex = \string, state, step ->
|
||||
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
|
||||
|
||||
walkUtf8WithIndexHelp : Str, state, (state, U8, Nat -> state), Nat, Nat -> state
|
||||
walkUtf8WithIndexHelp = \string, state, step, index, length ->
|
||||
if index < length then
|
||||
byte = Str.getUnsafe string index
|
||||
newState = step state byte index
|
||||
|
||||
walkUtf8WithIndexHelp string newState step (index + 1) length
|
||||
else
|
||||
state
|
||||
|
||||
## Make sure at least some number of bytes fit in this string without reallocating
|
||||
reserve : Str, Nat -> Str
|
||||
|
||||
## is UB when the scalar is invalid
|
||||
appendScalarUnsafe : Str, U32 -> Str
|
||||
|
||||
appendScalar : Str, U32 -> Result Str [InvalidScalar]*
|
||||
appendScalar = \string, scalar ->
|
||||
if isValidScalar scalar then
|
||||
Ok (appendScalarUnsafe string scalar)
|
||||
else
|
||||
Err InvalidScalar
|
||||
|
||||
isValidScalar : U32 -> Bool
|
||||
isValidScalar = \scalar ->
|
||||
scalar <= 0xD7FF || (scalar >= 0xE000 && scalar <= 0x10FFFF)
|
||||
|
||||
getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
|
||||
|
||||
walkScalars : Str, state, (state, U32 -> state) -> state
|
||||
walkScalars = \string, init, step ->
|
||||
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
|
||||
walkScalarsHelp : Str, state, (state, U32 -> state), Nat, Nat -> state
|
||||
walkScalarsHelp = \string, state, step, index, length ->
|
||||
if index < length then
|
||||
{ scalar, bytesParsed } = getScalarUnsafe string index
|
||||
newState = step state scalar
|
||||
|
||||
walkScalarsHelp string newState step (index + bytesParsed) length
|
||||
else
|
||||
state
|
||||
|
||||
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
|
||||
walkScalarsUntil = \string, init, step ->
|
||||
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
|
||||
walkScalarsUntilHelp : Str, state, (state, U32 -> [Break state, Continue state]), Nat, Nat -> state
|
||||
walkScalarsUntilHelp = \string, state, step, index, length ->
|
||||
if index < length then
|
||||
{ scalar, bytesParsed } = getScalarUnsafe string index
|
||||
|
||||
when step state scalar is
|
||||
Continue newState ->
|
||||
walkScalarsUntilHelp string newState step (index + bytesParsed) length
|
||||
Break newState ->
|
||||
newState
|
||||
else
|
||||
state
|
||||
|
|
|
@ -314,6 +314,7 @@ pub const STR_STR_SPLIT: &str = "roc_builtins.str.str_split";
|
|||
pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
|
||||
pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars";
|
||||
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
||||
pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes";
|
||||
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
||||
pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
|
||||
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||
|
@ -324,6 +325,7 @@ pub const STR_TO_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.to_int");
|
|||
pub const STR_TO_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.str.to_float");
|
||||
pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal";
|
||||
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
|
||||
pub const STR_SUBSTRING_UNSAFE: &str = "roc_builtins.str.substring_unsafe";
|
||||
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
|
||||
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
|
||||
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
|
||||
|
@ -331,6 +333,10 @@ pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
|
|||
pub const STR_TRIM: &str = "roc_builtins.str.trim";
|
||||
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
|
||||
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
|
||||
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
|
||||
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
|
||||
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
|
||||
pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
|
||||
|
||||
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
|
||||
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::def::Def;
|
||||
use crate::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue};
|
||||
use crate::expr::{Expr, Field, Recursive};
|
||||
use crate::num::{FloatBound, IntBound, IntWidth, NumBound};
|
||||
use crate::num::{FloatBound, IntBound, IntLitWidth, NumBound};
|
||||
use crate::pattern::Pattern;
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::called_via::CalledVia;
|
||||
|
@ -74,12 +74,18 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
STR_CONCAT => str_concat,
|
||||
STR_JOIN_WITH => str_join_with,
|
||||
STR_TO_SCALARS => str_to_scalars,
|
||||
STR_GET_UNSAFE => str_get_unsafe,
|
||||
STR_SPLIT => str_split,
|
||||
STR_IS_EMPTY => str_is_empty,
|
||||
STR_STARTS_WITH => str_starts_with,
|
||||
STR_STARTS_WITH_SCALAR => str_starts_with_scalar,
|
||||
STR_ENDS_WITH => str_ends_with,
|
||||
STR_COUNT_GRAPHEMES => str_count_graphemes,
|
||||
STR_COUNT_UTF8_BYTES => str_count_bytes,
|
||||
STR_SUBSTRING_UNSAFE => str_substring_unsafe,
|
||||
STR_RESERVE => str_reserve,
|
||||
STR_APPEND_SCALAR_UNSAFE => str_append_scalar_unsafe,
|
||||
STR_GET_SCALAR_UNSAFE => str_get_scalar_unsafe,
|
||||
STR_FROM_UTF8 => str_from_utf8,
|
||||
STR_FROM_UTF8_RANGE => str_from_utf8_range,
|
||||
STR_TO_UTF8 => str_to_utf8,
|
||||
|
@ -101,6 +107,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
STR_TO_I16 => str_to_num,
|
||||
STR_TO_U8 => str_to_num,
|
||||
STR_TO_I8 => str_to_num,
|
||||
LIST_UNREACHABLE => roc_unreachable,
|
||||
LIST_LEN => list_len,
|
||||
LIST_WITH_CAPACITY => list_with_capacity,
|
||||
LIST_GET_UNSAFE => list_get_unsafe,
|
||||
|
@ -1571,7 +1578,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
errorcode_var,
|
||||
Variable::UNSIGNED8,
|
||||
0,
|
||||
IntBound::Exact(IntWidth::U8),
|
||||
IntBound::Exact(IntLitWidth::U8),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1668,67 +1675,24 @@ fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
/// List.getUnsafe : Str, Nat -> U8
|
||||
fn str_get_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::StrGetUnsafe, var_store)
|
||||
}
|
||||
|
||||
/// Str.toScalars : Str -> List U32
|
||||
fn str_to_scalars(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let str_var = var_store.fresh();
|
||||
let list_u32_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrToScalars,
|
||||
args: vec![(str_var, Var(Symbol::ARG_1))],
|
||||
ret_var: list_u32_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(str_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
list_u32_var,
|
||||
)
|
||||
lowlevel_1(symbol, LowLevel::StrToScalars, var_store)
|
||||
}
|
||||
|
||||
/// Str.joinWith : List Str, Str -> Str
|
||||
fn str_join_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_str_var = var_store.fresh();
|
||||
let str_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrJoinWith,
|
||||
args: vec![
|
||||
(list_str_var, Var(Symbol::ARG_1)),
|
||||
(str_var, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: str_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_str_var, Symbol::ARG_1), (str_var, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
str_var,
|
||||
)
|
||||
lowlevel_2(symbol, LowLevel::StrJoinWith, var_store)
|
||||
}
|
||||
|
||||
/// Str.isEmpty : Str -> Bool
|
||||
fn str_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let str_var = var_store.fresh();
|
||||
let bool_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrIsEmpty,
|
||||
args: vec![(str_var, Var(Symbol::ARG_1))],
|
||||
ret_var: bool_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(str_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
bool_var,
|
||||
)
|
||||
lowlevel_1(symbol, LowLevel::StrIsEmpty, var_store)
|
||||
}
|
||||
|
||||
/// Str.startsWith : Str, Str -> Bool
|
||||
|
@ -1763,22 +1727,32 @@ fn str_ends_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
|
||||
/// Str.countGraphemes : Str -> Int
|
||||
fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let str_var = var_store.fresh();
|
||||
let int_var = var_store.fresh();
|
||||
lowlevel_1(symbol, LowLevel::StrCountGraphemes, var_store)
|
||||
}
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrCountGraphemes,
|
||||
args: vec![(str_var, Var(Symbol::ARG_1))],
|
||||
ret_var: int_var,
|
||||
};
|
||||
/// Str.countUtf8Bytes : Str -> Nat
|
||||
fn str_count_bytes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::StrCountUtf8Bytes, var_store)
|
||||
}
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(str_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
int_var,
|
||||
)
|
||||
/// Str.substringUnsafe : Str, Nat, Nat -> Nat
|
||||
fn str_substring_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_3(symbol, LowLevel::StrSubstringUnsafe, var_store)
|
||||
}
|
||||
|
||||
/// Str.reserve : Str, Nat -> Str
|
||||
fn str_reserve(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::StrReserve, var_store)
|
||||
}
|
||||
|
||||
/// Str.appendScalarUnsafe : Str, U32 -> Str
|
||||
fn str_append_scalar_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::StrAppendScalar, var_store)
|
||||
}
|
||||
|
||||
/// Str.getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
|
||||
fn str_get_scalar_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::StrGetScalarUnsafe, var_store)
|
||||
}
|
||||
|
||||
/// Str.fromUtf8 : List U8 -> Result Str [BadUtf8 { byteIndex : Nat, problem : Utf8Problem } }]*
|
||||
|
@ -2202,7 +2176,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
index_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
IntBound::Exact(IntWidth::Nat),
|
||||
IntBound::Exact(IntLitWidth::Nat),
|
||||
);
|
||||
|
||||
let clos = Closure(ClosureData {
|
||||
|
@ -2386,6 +2360,11 @@ fn list_prepend(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
/// List.unreachable : [] -> a
|
||||
fn roc_unreachable(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::Unreachable, var_store)
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::ListMap, var_store)
|
||||
|
|
|
@ -351,15 +351,28 @@ fn canonicalize_alias<'a>(
|
|||
region: loc_lowercase.region,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
is_phantom = true;
|
||||
None => match kind {
|
||||
AliasKind::Structural => {
|
||||
is_phantom = true;
|
||||
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
typ: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
typ: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
AliasKind::Opaque => {
|
||||
// Opaques can have phantom types.
|
||||
can_vars.push(Loc {
|
||||
value: AliasVar {
|
||||
name: loc_lowercase.value.clone(),
|
||||
var: var_store.fresh(),
|
||||
opt_bound_ability: None,
|
||||
},
|
||||
region: loc_lowercase.region,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ impl Output {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||
pub enum IntValue {
|
||||
I128([u8; 16]),
|
||||
U128([u8; 16]),
|
||||
|
|
|
@ -5,7 +5,7 @@ use roc_problem::can::Problem;
|
|||
use roc_problem::can::RuntimeError::*;
|
||||
use roc_problem::can::{FloatErrorKind, IntErrorKind};
|
||||
use roc_region::all::Region;
|
||||
pub use roc_types::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand};
|
||||
pub use roc_types::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
|
||||
use roc_types::subs::VarStore;
|
||||
|
||||
use std::str;
|
||||
|
@ -174,7 +174,7 @@ pub fn finish_parsing_float(raw: &str) -> Result<(&str, f64, FloatBound), (&str,
|
|||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum ParsedWidth {
|
||||
Int(IntWidth),
|
||||
Int(IntLitWidth),
|
||||
Float(FloatWidth),
|
||||
}
|
||||
|
||||
|
@ -188,17 +188,17 @@ fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
|
|||
}
|
||||
|
||||
parse_num_suffix! {
|
||||
"u8", ParsedWidth::Int(IntWidth::U8)
|
||||
"u16", ParsedWidth::Int(IntWidth::U16)
|
||||
"u32", ParsedWidth::Int(IntWidth::U32)
|
||||
"u64", ParsedWidth::Int(IntWidth::U64)
|
||||
"u128", ParsedWidth::Int(IntWidth::U128)
|
||||
"i8", ParsedWidth::Int(IntWidth::I8)
|
||||
"i16", ParsedWidth::Int(IntWidth::I16)
|
||||
"i32", ParsedWidth::Int(IntWidth::I32)
|
||||
"i64", ParsedWidth::Int(IntWidth::I64)
|
||||
"i128", ParsedWidth::Int(IntWidth::I128)
|
||||
"nat", ParsedWidth::Int(IntWidth::Nat)
|
||||
"u8", ParsedWidth::Int(IntLitWidth::U8)
|
||||
"u16", ParsedWidth::Int(IntLitWidth::U16)
|
||||
"u32", ParsedWidth::Int(IntLitWidth::U32)
|
||||
"u64", ParsedWidth::Int(IntLitWidth::U64)
|
||||
"u128", ParsedWidth::Int(IntLitWidth::U128)
|
||||
"i8", ParsedWidth::Int(IntLitWidth::I8)
|
||||
"i16", ParsedWidth::Int(IntLitWidth::I16)
|
||||
"i32", ParsedWidth::Int(IntLitWidth::I32)
|
||||
"i64", ParsedWidth::Int(IntLitWidth::I64)
|
||||
"i128", ParsedWidth::Int(IntLitWidth::I128)
|
||||
"nat", ParsedWidth::Int(IntLitWidth::Nat)
|
||||
"dec", ParsedWidth::Float(FloatWidth::Dec)
|
||||
"f32", ParsedWidth::Float(FloatWidth::F32)
|
||||
"f64", ParsedWidth::Float(FloatWidth::F64)
|
||||
|
@ -256,9 +256,9 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
|
|||
IntValue::I128(bytes) => {
|
||||
let num = i128::from_ne_bytes(bytes);
|
||||
|
||||
(lower_bound_of_int(num), num < 0)
|
||||
(lower_bound_of_int_literal(num), num < 0)
|
||||
}
|
||||
IntValue::U128(_) => (IntWidth::U128, false),
|
||||
IntValue::U128(_) => (IntLitWidth::U128, false),
|
||||
};
|
||||
|
||||
match opt_exact_bound {
|
||||
|
@ -314,8 +314,8 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
|
|||
}
|
||||
}
|
||||
|
||||
fn lower_bound_of_int(result: i128) -> IntWidth {
|
||||
use IntWidth::*;
|
||||
fn lower_bound_of_int_literal(result: i128) -> IntLitWidth {
|
||||
use IntLitWidth::*;
|
||||
if result >= 0 {
|
||||
// Positive
|
||||
let result = result as u128;
|
||||
|
@ -323,12 +323,16 @@ fn lower_bound_of_int(result: i128) -> IntWidth {
|
|||
I128
|
||||
} else if result > I64.max_value() {
|
||||
U64
|
||||
} else if result > U32.max_value() {
|
||||
} else if result > F64.max_value() {
|
||||
I64
|
||||
} else if result > U32.max_value() {
|
||||
F64
|
||||
} else if result > I32.max_value() {
|
||||
U32
|
||||
} else if result > U16.max_value() {
|
||||
} else if result > F32.max_value() {
|
||||
I32
|
||||
} else if result > U16.max_value() {
|
||||
F32
|
||||
} else if result > I16.max_value() {
|
||||
U16
|
||||
} else if result > U8.max_value() {
|
||||
|
@ -342,10 +346,14 @@ fn lower_bound_of_int(result: i128) -> IntWidth {
|
|||
// Negative
|
||||
if result < I64.min_value() {
|
||||
I128
|
||||
} else if result < I32.min_value() {
|
||||
} else if result < F64.min_value() {
|
||||
I64
|
||||
} else if result < I16.min_value() {
|
||||
} else if result < I32.min_value() {
|
||||
F64
|
||||
} else if result < F32.min_value() {
|
||||
I32
|
||||
} else if result < I16.min_value() {
|
||||
F32
|
||||
} else if result < I8.min_value() {
|
||||
I16
|
||||
} else {
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Length {
|
|||
} else if self.0 > 0 {
|
||||
Kind::Interned(self.0 as usize)
|
||||
} else {
|
||||
Kind::Generated(self.0.abs() as usize)
|
||||
Kind::Generated(self.0.unsigned_abs() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use arrayvec::ArrayVec;
|
||||
use roc_can::constraint::{Constraint, Constraints};
|
||||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand};
|
||||
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::num::NumericRange;
|
||||
|
@ -10,47 +10,54 @@ use roc_types::types::Type::{self, *};
|
|||
use roc_types::types::{AliasKind, Category};
|
||||
use roc_types::types::{OptAbleType, Reason};
|
||||
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn add_numeric_bound_constr(
|
||||
constraints: &mut Constraints,
|
||||
num_constraints: &mut impl Extend<Constraint>,
|
||||
num_type: Type,
|
||||
num_var: Variable,
|
||||
precision_var: Variable,
|
||||
bound: impl TypedNumericBound,
|
||||
region: Region,
|
||||
category: Category,
|
||||
) -> Type {
|
||||
let range = bound.numeric_bound();
|
||||
let total_num_type = num_type;
|
||||
|
||||
use roc_types::num::{float_width_to_variable, int_width_to_variable};
|
||||
use roc_types::num::{float_width_to_variable, int_lit_width_to_variable};
|
||||
|
||||
match range {
|
||||
NumericBound::None => {
|
||||
// no additional constraints
|
||||
total_num_type
|
||||
// no additional constraints, just a Num *
|
||||
num_num(Variable(num_var))
|
||||
}
|
||||
NumericBound::FloatExact(width) => {
|
||||
let actual_type = Variable(float_width_to_variable(width));
|
||||
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
|
||||
let because_suffix =
|
||||
constraints.equal_types(total_num_type.clone(), expected, category, region);
|
||||
constraints.equal_types(Variable(num_var), expected, category, region);
|
||||
|
||||
num_constraints.extend([because_suffix]);
|
||||
|
||||
total_num_type
|
||||
Variable(num_var)
|
||||
}
|
||||
NumericBound::IntExact(width) => {
|
||||
let actual_type = Variable(int_width_to_variable(width));
|
||||
let actual_type = Variable(int_lit_width_to_variable(width));
|
||||
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
|
||||
let because_suffix =
|
||||
constraints.equal_types(total_num_type.clone(), expected, category, region);
|
||||
constraints.equal_types(Variable(num_var), expected, category, region);
|
||||
|
||||
num_constraints.extend([because_suffix]);
|
||||
|
||||
total_num_type
|
||||
Variable(num_var)
|
||||
}
|
||||
NumericBound::Range(range) => {
|
||||
let actual_type = Variable(precision_var);
|
||||
let expected = Expected::NoExpectation(RangedNumber(range));
|
||||
let constr = constraints.equal_types(actual_type, expected, category, region);
|
||||
|
||||
num_constraints.extend([constr]);
|
||||
|
||||
num_num(Variable(num_var))
|
||||
}
|
||||
NumericBound::Range(range) => RangedNumber(Box::new(total_num_type), range),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +77,8 @@ pub fn int_literal(
|
|||
let num_type = add_numeric_bound_constr(
|
||||
constraints,
|
||||
&mut constrs,
|
||||
Variable(num_var),
|
||||
num_var,
|
||||
precision_var,
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
|
@ -106,7 +114,8 @@ pub fn float_literal(
|
|||
let num_type = add_numeric_bound_constr(
|
||||
constraints,
|
||||
&mut constrs,
|
||||
Variable(num_var),
|
||||
num_var,
|
||||
precision_var,
|
||||
bound,
|
||||
region,
|
||||
Category::Float,
|
||||
|
@ -134,13 +143,12 @@ pub fn num_literal(
|
|||
region: Region,
|
||||
bound: NumBound,
|
||||
) -> Constraint {
|
||||
let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
|
||||
|
||||
let mut constrs = ArrayVec::<_, 2>::new();
|
||||
let num_type = add_numeric_bound_constr(
|
||||
constraints,
|
||||
&mut constrs,
|
||||
open_number_type,
|
||||
num_var,
|
||||
num_var,
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
|
@ -330,6 +338,6 @@ impl TypedNumericBound for NumBound {
|
|||
pub enum NumericBound {
|
||||
None,
|
||||
FloatExact(FloatWidth),
|
||||
IntExact(IntWidth),
|
||||
IntExact(IntLitWidth),
|
||||
Range(NumericRange),
|
||||
}
|
||||
|
|
|
@ -226,15 +226,14 @@ pub fn constrain_pattern(
|
|||
);
|
||||
}
|
||||
|
||||
&NumLiteral(var, _, _, bound) => {
|
||||
state.vars.push(var);
|
||||
|
||||
let num_type = builtins::num_num(Type::Variable(var));
|
||||
&NumLiteral(precision_var, _, _, bound) => {
|
||||
state.vars.push(precision_var);
|
||||
|
||||
let num_type = builtins::add_numeric_bound_constr(
|
||||
constraints,
|
||||
&mut state.constraints,
|
||||
num_type,
|
||||
precision_var,
|
||||
precision_var,
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
|
@ -248,13 +247,14 @@ pub fn constrain_pattern(
|
|||
));
|
||||
}
|
||||
|
||||
&IntLiteral(num_var, precision_var, _, _, bound) => {
|
||||
&IntLiteral(num_precision_var, precision_var, _, _, bound) => {
|
||||
// First constraint on the free num var; this improves the resolved type quality in
|
||||
// case the bound is an alias.
|
||||
let num_type = builtins::add_numeric_bound_constr(
|
||||
constraints,
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
num_precision_var,
|
||||
num_precision_var,
|
||||
bound,
|
||||
region,
|
||||
Category::Int,
|
||||
|
@ -264,7 +264,7 @@ pub fn constrain_pattern(
|
|||
let int_type = builtins::num_int(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(constraints.equal_types(
|
||||
num_type, // TODO check me if something breaks!
|
||||
num_type.clone(), // TODO check me if something breaks!
|
||||
Expected::NoExpectation(int_type),
|
||||
Category::Int,
|
||||
region,
|
||||
|
@ -272,20 +272,21 @@ pub fn constrain_pattern(
|
|||
|
||||
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
|
||||
state.constraints.push(constraints.equal_pattern_types(
|
||||
Type::Variable(num_var),
|
||||
num_type,
|
||||
expected,
|
||||
PatternCategory::Int,
|
||||
region,
|
||||
));
|
||||
}
|
||||
|
||||
&FloatLiteral(num_var, precision_var, _, _, bound) => {
|
||||
&FloatLiteral(num_precision_var, precision_var, _, _, bound) => {
|
||||
// First constraint on the free num var; this improves the resolved type quality in
|
||||
// case the bound is an alias.
|
||||
let num_type = builtins::add_numeric_bound_constr(
|
||||
constraints,
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
num_precision_var,
|
||||
num_precision_var,
|
||||
bound,
|
||||
region,
|
||||
Category::Float,
|
||||
|
|
|
@ -151,7 +151,7 @@ impl FlatEncodable {
|
|||
// by the backend, and the backend treats opaques like structural aliases.
|
||||
_ => Self::from_var(subs, real_var),
|
||||
},
|
||||
Content::RangedNumber(real_var, _) => Self::from_var(subs, real_var),
|
||||
Content::RangedNumber(_) => Err(Underivable),
|
||||
//
|
||||
Content::RecursionVar { .. } => Err(Underivable),
|
||||
Content::Error => Err(Underivable),
|
||||
|
|
|
@ -4181,6 +4181,89 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
|
|||
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
|
||||
}
|
||||
|
||||
pub fn build_procedures_expose_expects<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
) -> Vec<'a, &'a str> {
|
||||
use bumpalo::collections::CollectIn;
|
||||
|
||||
// this is not entirely accurate: it will treat every top-level bool value (turned into a
|
||||
// zero-argument thunk) as an expect.
|
||||
let expects: Vec<_> = procedures
|
||||
.keys()
|
||||
.filter_map(|(symbol, proc_layout)| {
|
||||
if proc_layout.arguments.is_empty() && proc_layout.result == Layout::bool() {
|
||||
Some(*symbol)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect_in(env.arena);
|
||||
|
||||
let mod_solutions = build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
let captures_niche = CapturesNiche::no_niche();
|
||||
|
||||
let top_level = ProcLayout {
|
||||
arguments: &[],
|
||||
result: Layout::bool(),
|
||||
captures_niche,
|
||||
};
|
||||
|
||||
let mut expect_names = Vec::with_capacity_in(expects.len(), env.arena);
|
||||
|
||||
for symbol in expects.iter().copied() {
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes =
|
||||
roc_alias_analysis::func_name_bytes_help(symbol, it, captures_niche, &top_level.result);
|
||||
let func_name = FuncName(&bytes);
|
||||
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
|
||||
|
||||
let mut it = func_solutions.specs();
|
||||
let func_spec = it.next().unwrap();
|
||||
debug_assert!(
|
||||
it.next().is_none(),
|
||||
"we expect only one specialization of this symbol"
|
||||
);
|
||||
|
||||
// NOTE fake layout; it is only used for debug prints
|
||||
let roc_main_fn = function_value_by_func_spec(
|
||||
env,
|
||||
*func_spec,
|
||||
symbol,
|
||||
&[],
|
||||
captures_niche,
|
||||
&Layout::UNIT,
|
||||
);
|
||||
|
||||
let name = roc_main_fn.get_name().to_str().unwrap();
|
||||
|
||||
let expect_name = &format!("Expect_{}", name);
|
||||
let expect_name = env.arena.alloc_str(expect_name);
|
||||
expect_names.push(&*expect_name);
|
||||
|
||||
// Add main to the module.
|
||||
let _ = expose_function_to_host_help_c_abi(
|
||||
env,
|
||||
name,
|
||||
roc_main_fn,
|
||||
top_level.arguments,
|
||||
top_level.result,
|
||||
&format!("Expect_{}", name),
|
||||
);
|
||||
}
|
||||
|
||||
expect_names
|
||||
}
|
||||
|
||||
fn build_procedures_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
|
@ -5352,12 +5435,52 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
BasicValueEnum::IntValue(is_zero)
|
||||
}
|
||||
StrCountGraphemes => {
|
||||
// Str.countGraphemes : Str -> Int
|
||||
// Str.countGraphemes : Str -> Nat
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
call_bitcode_fn(env, &[string], bitcode::STR_COUNT_GRAPEHEME_CLUSTERS)
|
||||
}
|
||||
StrGetScalarUnsafe => {
|
||||
// Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 }
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
let index = load_symbol(scope, &args[1]);
|
||||
call_bitcode_fn(env, &[string, index], bitcode::STR_GET_SCALAR_UNSAFE)
|
||||
}
|
||||
StrCountUtf8Bytes => {
|
||||
// Str.countGraphemes : Str -> Nat
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
call_bitcode_fn(env, &[string], bitcode::STR_COUNT_UTF8_BYTES)
|
||||
}
|
||||
StrSubstringUnsafe => {
|
||||
// Str.substringUnsafe : Str, Nat, Nat -> Str
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
let start = load_symbol(scope, &args[1]);
|
||||
let length = load_symbol(scope, &args[2]);
|
||||
call_str_bitcode_fn(env, &[string, start, length], bitcode::STR_SUBSTRING_UNSAFE)
|
||||
}
|
||||
StrReserve => {
|
||||
// Str.reserve : Str, Nat -> Str
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
let capacity = load_symbol(scope, &args[1]);
|
||||
call_str_bitcode_fn(env, &[string, capacity], bitcode::STR_RESERVE)
|
||||
}
|
||||
StrAppendScalar => {
|
||||
// Str.appendScalar : Str, U32 -> Str
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
let capacity = load_symbol(scope, &args[1]);
|
||||
call_str_bitcode_fn(env, &[string, capacity], bitcode::STR_APPEND_SCALAR)
|
||||
}
|
||||
StrTrim => {
|
||||
// Str.trim : Str -> Str
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
@ -5486,8 +5609,17 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
list_prepend(env, original_wrapper, elem, elem_layout)
|
||||
}
|
||||
StrGetUnsafe => {
|
||||
// List.getUnsafe : List elem, Nat -> elem
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let wrapper_struct = load_symbol(scope, &args[0]);
|
||||
let elem_index = load_symbol(scope, &args[1]);
|
||||
|
||||
call_bitcode_fn(env, &[wrapper_struct, elem_index], bitcode::STR_GET_UNSAFE)
|
||||
}
|
||||
ListGetUnsafe => {
|
||||
// List.get : List elem, Nat -> [Ok elem, OutOfBounds]*
|
||||
// List.getUnsafe : List elem, Nat -> elem
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let (wrapper_struct, list_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
@ -5974,6 +6106,20 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
PtrCast | RefCountInc | RefCountDec => {
|
||||
unreachable!("Not used in LLVM backend: {:?}", op);
|
||||
}
|
||||
|
||||
Unreachable => match RocReturn::from_layout(env, layout) {
|
||||
RocReturn::Return => {
|
||||
let basic_type = basic_type_from_layout(env, layout);
|
||||
basic_type.const_zero()
|
||||
}
|
||||
RocReturn::ByPointer => {
|
||||
let basic_type = basic_type_from_layout(env, layout);
|
||||
let ptr = env.builder.build_alloca(basic_type, "unreachable_alloca");
|
||||
env.builder.build_store(ptr, basic_type.const_zero());
|
||||
|
||||
ptr.into()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -218,6 +218,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
// Str
|
||||
StrConcat => self.load_args_and_call_zig(backend, bitcode::STR_CONCAT),
|
||||
StrToScalars => self.load_args_and_call_zig(backend, bitcode::STR_TO_SCALARS),
|
||||
StrGetUnsafe => self.load_args_and_call_zig(backend, bitcode::STR_GET_UNSAFE),
|
||||
StrJoinWith => self.load_args_and_call_zig(backend, bitcode::STR_JOIN_WITH),
|
||||
StrIsEmpty => match backend.storage.get(&self.arguments[0]) {
|
||||
StoredValue::StackMemory { location, .. } => {
|
||||
|
@ -239,6 +240,9 @@ impl<'a> LowLevelCall<'a> {
|
|||
StrCountGraphemes => {
|
||||
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS)
|
||||
}
|
||||
StrCountUtf8Bytes => {
|
||||
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES)
|
||||
}
|
||||
StrToNum => {
|
||||
let number_layout = match self.ret_layout {
|
||||
Layout::Struct { field_layouts, .. } => field_layouts[0],
|
||||
|
@ -285,8 +289,16 @@ impl<'a> LowLevelCall<'a> {
|
|||
StrTrimLeft => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_LEFT),
|
||||
StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT),
|
||||
StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8),
|
||||
StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE),
|
||||
StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT),
|
||||
StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR),
|
||||
StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM),
|
||||
StrGetScalarUnsafe => {
|
||||
self.load_args_and_call_zig(backend, bitcode::STR_GET_SCALAR_UNSAFE)
|
||||
}
|
||||
StrSubstringUnsafe => {
|
||||
self.load_args_and_call_zig(backend, bitcode::STR_SUBSTRING_UNSAFE)
|
||||
}
|
||||
|
||||
// List
|
||||
ListLen => match backend.storage.get(&self.arguments[0]) {
|
||||
|
@ -1684,6 +1696,17 @@ impl<'a> LowLevelCall<'a> {
|
|||
BoxExpr | UnboxExpr => {
|
||||
unreachable!("The {:?} operation is turned into mono Expr", self.lowlevel)
|
||||
}
|
||||
|
||||
Unreachable => match self.ret_storage {
|
||||
StoredValue::VirtualMachineStack { value_type, .. }
|
||||
| StoredValue::Local { value_type, .. } => match value_type {
|
||||
ValueType::I32 => backend.code_builder.i32_const(0),
|
||||
ValueType::I64 => backend.code_builder.i64_const(0),
|
||||
ValueType::F32 => backend.code_builder.f32_const(0.0),
|
||||
ValueType::F64 => backend.code_builder.f64_const(0.0),
|
||||
},
|
||||
StoredValue::StackMemory { .. } => { /* do nothing */ }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -176,6 +176,7 @@ wasm_result_primitive!(u16, i32_store16, Align::Bytes2);
|
|||
wasm_result_primitive!(i16, i32_store16, Align::Bytes2);
|
||||
wasm_result_primitive!(u32, i32_store, Align::Bytes4);
|
||||
wasm_result_primitive!(i32, i32_store, Align::Bytes4);
|
||||
wasm_result_primitive!(char, i32_store, Align::Bytes4);
|
||||
wasm_result_primitive!(u64, i64_store, Align::Bytes8);
|
||||
wasm_result_primitive!(i64, i64_store, Align::Bytes8);
|
||||
wasm_result_primitive!(usize, i32_store, Align::Bytes4);
|
||||
|
|
|
@ -23,7 +23,7 @@ macro_rules! wasm32_sized_primitive {
|
|||
}
|
||||
|
||||
wasm32_sized_primitive!(
|
||||
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder,
|
||||
u8, i8, u16, i16, u32, i32, char, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder,
|
||||
);
|
||||
|
||||
impl Wasm32Sized for () {
|
||||
|
|
|
@ -106,7 +106,7 @@ impl<'a> From<&'a str> for ModuleName {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<IdentStr> for ModuleName {
|
||||
impl From<IdentStr> for ModuleName {
|
||||
fn from(string: IdentStr) -> Self {
|
||||
Self(string.as_str().into())
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ impl<'a> From<&'a str> for ForeignSymbol {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for ForeignSymbol {
|
||||
impl From<String> for ForeignSymbol {
|
||||
fn from(string: String) -> Self {
|
||||
Self(string.into())
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ impl<'a> From<&'a str> for Uppercase {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Uppercase {
|
||||
impl From<String> for Uppercase {
|
||||
fn from(string: String) -> Self {
|
||||
Self(string.into())
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ impl<'a> From<&'a Lowercase> for &'a str {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<String> for Lowercase {
|
||||
impl From<String> for Lowercase {
|
||||
fn from(string: String) -> Self {
|
||||
Self(string.into())
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ pub enum LowLevel {
|
|||
StrEndsWith,
|
||||
StrSplit,
|
||||
StrCountGraphemes,
|
||||
StrCountUtf8Bytes,
|
||||
StrFromInt,
|
||||
StrFromUtf8,
|
||||
StrFromUtf8Range,
|
||||
|
@ -24,6 +25,11 @@ pub enum LowLevel {
|
|||
StrTrimRight,
|
||||
StrToNum,
|
||||
StrToScalars,
|
||||
StrGetUnsafe,
|
||||
StrSubstringUnsafe,
|
||||
StrReserve,
|
||||
StrAppendScalar,
|
||||
StrGetScalarUnsafe,
|
||||
ListLen,
|
||||
ListWithCapacity,
|
||||
ListGetUnsafe,
|
||||
|
@ -115,6 +121,7 @@ pub enum LowLevel {
|
|||
RefCountDec,
|
||||
BoxExpr,
|
||||
UnboxExpr,
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
macro_rules! higher_order {
|
||||
|
@ -165,6 +172,7 @@ impl LowLevelWrapperType {
|
|||
|
||||
match symbol {
|
||||
Symbol::STR_CONCAT => CanBeReplacedBy(StrConcat),
|
||||
Symbol::STR_GET_UNSAFE => CanBeReplacedBy(StrGetUnsafe),
|
||||
Symbol::STR_TO_SCALARS => CanBeReplacedBy(StrToScalars),
|
||||
Symbol::STR_JOIN_WITH => CanBeReplacedBy(StrJoinWith),
|
||||
Symbol::STR_IS_EMPTY => CanBeReplacedBy(StrIsEmpty),
|
||||
|
@ -173,10 +181,13 @@ impl LowLevelWrapperType {
|
|||
Symbol::STR_ENDS_WITH => CanBeReplacedBy(StrEndsWith),
|
||||
Symbol::STR_SPLIT => CanBeReplacedBy(StrSplit),
|
||||
Symbol::STR_COUNT_GRAPHEMES => CanBeReplacedBy(StrCountGraphemes),
|
||||
Symbol::STR_COUNT_UTF8_BYTES => CanBeReplacedBy(StrCountUtf8Bytes),
|
||||
Symbol::STR_FROM_UTF8 => WrapperIsRequired,
|
||||
Symbol::STR_FROM_UTF8_RANGE => WrapperIsRequired,
|
||||
Symbol::STR_TO_UTF8 => CanBeReplacedBy(StrToUtf8),
|
||||
Symbol::STR_REPEAT => CanBeReplacedBy(StrRepeat),
|
||||
Symbol::STR_RESERVE => CanBeReplacedBy(StrReserve),
|
||||
Symbol::STR_APPEND_SCALAR_UNSAFE => CanBeReplacedBy(StrAppendScalar),
|
||||
Symbol::STR_TRIM => CanBeReplacedBy(StrTrim),
|
||||
Symbol::STR_TRIM_LEFT => CanBeReplacedBy(StrTrimLeft),
|
||||
Symbol::STR_TRIM_RIGHT => CanBeReplacedBy(StrTrimRight),
|
||||
|
|
|
@ -181,7 +181,10 @@ impl fmt::Debug for Symbol {
|
|||
// might be used in a panic error message, and if we panick
|
||||
// while we're already panicking it'll kill the process
|
||||
// without printing any of the errors!
|
||||
println!("DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err);
|
||||
use std::io::Write;
|
||||
|
||||
let mut stderr = std::io::stderr();
|
||||
writeln!(stderr, "DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err).unwrap();
|
||||
|
||||
fallback_debug_fmt(*self, f)
|
||||
}
|
||||
|
@ -1190,6 +1193,18 @@ define_builtins! {
|
|||
32 STR_TO_U8: "toU8"
|
||||
33 STR_TO_I8: "toI8"
|
||||
34 STR_TO_SCALARS: "toScalars"
|
||||
35 STR_GET_UNSAFE: "getUnsafe"
|
||||
36 STR_COUNT_UTF8_BYTES: "countUtf8Bytes"
|
||||
37 STR_SUBSTRING_UNSAFE: "substringUnsafe"
|
||||
38 STR_SPLIT_FIRST: "splitFirst"
|
||||
39 STR_SPLIT_LAST: "splitLast"
|
||||
40 STR_WALK_UTF8_WITH_INDEX: "walkUtf8WithIndex"
|
||||
41 STR_RESERVE: "reserve"
|
||||
42 STR_APPEND_SCALAR_UNSAFE: "appendScalarUnsafe"
|
||||
43 STR_APPEND_SCALAR: "appendScalar"
|
||||
44 STR_GET_SCALAR_UNSAFE: "getScalarUnsafe"
|
||||
45 STR_WALK_SCALARS: "walkScalars"
|
||||
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
|
||||
}
|
||||
5 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
|
@ -1256,6 +1271,7 @@ define_builtins! {
|
|||
61 LIST_REPLACE_UNSAFE: "replaceUnsafe"
|
||||
62 LIST_WITH_CAPACITY: "withCapacity"
|
||||
63 LIST_ITERATE: "iterate"
|
||||
64 LIST_UNREACHABLE: "unreachable"
|
||||
}
|
||||
6 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" // the Result.Result type alias
|
||||
|
|
|
@ -884,14 +884,19 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
// - arguments that we may want to update destructively must be Owned
|
||||
// - other refcounted arguments are Borrowed
|
||||
match op {
|
||||
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes => {
|
||||
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => {
|
||||
arena.alloc_slice_copy(&[borrowed])
|
||||
}
|
||||
ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
|
||||
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
StrSubstringUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
StrReserve => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
StrAppendScalar => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
StrGetScalarUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
StrTrim => arena.alloc_slice_copy(&[owned]),
|
||||
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
|
||||
StrTrimRight => arena.alloc_slice_copy(&[owned]),
|
||||
|
|
|
@ -53,10 +53,8 @@ pub fn deep_copy_type_vars_into_expr<'a>(
|
|||
let go_help = |e: &Expr| help(subs, e, substitutions);
|
||||
|
||||
match expr {
|
||||
Num(var, str, val, bound) => Num(sub!(*var), str.clone(), val.clone(), *bound),
|
||||
Int(v1, v2, str, val, bound) => {
|
||||
Int(sub!(*v1), sub!(*v2), str.clone(), val.clone(), *bound)
|
||||
}
|
||||
Num(var, str, val, bound) => Num(sub!(*var), str.clone(), *val, *bound),
|
||||
Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
|
||||
Float(v1, v2, str, val, bound) => {
|
||||
Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound)
|
||||
}
|
||||
|
@ -663,10 +661,8 @@ fn deep_copy_type_vars<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
RangedNumber(typ, range) => {
|
||||
let new_typ = descend_var!(typ);
|
||||
|
||||
perform_clone!(RangedNumber(new_typ, range))
|
||||
RangedNumber(range) => {
|
||||
perform_clone!(RangedNumber(range))
|
||||
}
|
||||
Error => Error,
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ use roc_debug_flags::{
|
|||
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
|
||||
};
|
||||
use roc_derive_key::GlobalDerivedSymbols;
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_error_macros::{internal_error, todo_abilities};
|
||||
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
|
||||
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
|
||||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
|
@ -3610,66 +3610,22 @@ fn specialize_naked_symbol<'a>(
|
|||
)
|
||||
}
|
||||
|
||||
fn try_make_literal<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
can_expr: &roc_can::expr::Expr,
|
||||
) -> Option<Literal<'a>> {
|
||||
fn try_make_literal<'a>(can_expr: &roc_can::expr::Expr, layout: Layout<'a>) -> Option<Literal<'a>> {
|
||||
use roc_can::expr::Expr::*;
|
||||
|
||||
match can_expr {
|
||||
Int(_, precision, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) {
|
||||
IntOrFloat::Int(_) => Some(match *int {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
}
|
||||
Int(_, _, int_str, int, _bound) => {
|
||||
Some(make_num_literal(layout, int_str, IntOrFloatValue::Int(*int)).to_expr_literal())
|
||||
}
|
||||
|
||||
Float(_, precision, float_str, float, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) {
|
||||
IntOrFloat::Float(_) => Some(Literal::Float(*float)),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(float_str) {
|
||||
Some(d) => d,
|
||||
None => panic!(
|
||||
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
|
||||
float_str
|
||||
),
|
||||
};
|
||||
|
||||
Some(Literal::Decimal(dec.to_ne_bytes()))
|
||||
}
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
}
|
||||
}
|
||||
Float(_, _, float_str, float, _bound) => Some(
|
||||
make_num_literal(layout, float_str, IntOrFloatValue::Float(*float)).to_expr_literal(),
|
||||
),
|
||||
|
||||
// TODO investigate lifetime trouble
|
||||
// Str(string) => Some(Literal::Str(env.arena.alloc(string))),
|
||||
Num(var, num_str, num, _bound) => {
|
||||
// first figure out what kind of number this is
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
|
||||
IntOrFloat::Int(_) => Some(match *num {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
IntOrFloat::Float(_) => Some(match *num {
|
||||
IntValue::I128(n) => Literal::Float(i128::from_ne_bytes(n) as f64),
|
||||
IntValue::U128(n) => Literal::Float(u128::from_ne_bytes(n) as f64),
|
||||
}),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(num_str) {
|
||||
Some(d) => d,
|
||||
None => panic!(
|
||||
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
|
||||
num_str
|
||||
),
|
||||
};
|
||||
|
||||
Some(Literal::Decimal(dec.to_ne_bytes()))
|
||||
}
|
||||
}
|
||||
Num(_, num_str, num, _bound) => {
|
||||
Some(make_num_literal(layout, num_str, IntOrFloatValue::Int(*num)).to_expr_literal())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
@ -3689,44 +3645,35 @@ pub fn with_hole<'a>(
|
|||
let arena = env.arena;
|
||||
|
||||
match can_expr {
|
||||
Int(_, precision, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) {
|
||||
IntOrFloat::Int(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(match int {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
Layout::Builtin(Builtin::Int(precision)),
|
||||
hole,
|
||||
),
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
}
|
||||
}
|
||||
Int(_, _, int_str, int, _bound) => assign_num_literal_expr(
|
||||
env,
|
||||
layout_cache,
|
||||
assigned,
|
||||
variable,
|
||||
&int_str,
|
||||
IntOrFloatValue::Int(int),
|
||||
hole,
|
||||
),
|
||||
|
||||
Float(_, precision, float_str, float, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) {
|
||||
IntOrFloat::Float(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Float(float)),
|
||||
Layout::Builtin(Builtin::Float(precision)),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(&float_str) {
|
||||
Some(d) => d,
|
||||
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", float_str),
|
||||
};
|
||||
Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Decimal(dec.to_ne_bytes())),
|
||||
Layout::Builtin(Builtin::Decimal),
|
||||
hole,
|
||||
)
|
||||
}
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
}
|
||||
}
|
||||
Float(_, _, float_str, float, _bound) => assign_num_literal_expr(
|
||||
env,
|
||||
layout_cache,
|
||||
assigned,
|
||||
variable,
|
||||
&float_str,
|
||||
IntOrFloatValue::Float(float),
|
||||
hole,
|
||||
),
|
||||
|
||||
Num(_, num_str, num, _bound) => assign_num_literal_expr(
|
||||
env,
|
||||
layout_cache,
|
||||
assigned,
|
||||
variable,
|
||||
&num_str,
|
||||
IntOrFloatValue::Int(num),
|
||||
hole,
|
||||
),
|
||||
|
||||
Str(string) => Stmt::Let(
|
||||
assigned,
|
||||
|
@ -3741,42 +3688,6 @@ pub fn with_hole<'a>(
|
|||
Layout::int_width(IntWidth::I32),
|
||||
hole,
|
||||
),
|
||||
|
||||
Num(var, num_str, num, _bound) => {
|
||||
// first figure out what kind of number this is
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
|
||||
IntOrFloat::Int(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(match num {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
Layout::int_width(precision),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::Float(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(match num {
|
||||
IntValue::I128(n) => Literal::Float(i128::from_ne_bytes(n) as f64),
|
||||
IntValue::U128(n) => Literal::Float(u128::from_ne_bytes(n) as f64),
|
||||
}),
|
||||
Layout::float_width(precision),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(&num_str) {
|
||||
Some(d) => d,
|
||||
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str),
|
||||
};
|
||||
Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Decimal(dec.to_ne_bytes())),
|
||||
Layout::Builtin(Builtin::Decimal),
|
||||
hole,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
LetNonRec(def, cont) => from_can_let(
|
||||
env,
|
||||
procs,
|
||||
|
@ -4281,8 +4192,12 @@ pub fn with_hole<'a>(
|
|||
|
||||
let mut symbol_exprs = Vec::with_capacity_in(loc_elems.len(), env.arena);
|
||||
|
||||
let elem_layout = layout_cache
|
||||
.from_var(env.arena, elem_var, env.subs)
|
||||
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
|
||||
|
||||
for arg_expr in loc_elems.into_iter() {
|
||||
if let Some(literal) = try_make_literal(env, &arg_expr.value) {
|
||||
if let Some(literal) = try_make_literal(&arg_expr.value, elem_layout) {
|
||||
elements.push(ListLiteralElement::Literal(literal));
|
||||
} else {
|
||||
let symbol = possible_reuse_symbol_or_specialize(
|
||||
|
@ -4300,10 +4215,6 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
let arg_symbols = arg_symbols.into_bump_slice();
|
||||
|
||||
let elem_layout = layout_cache
|
||||
.from_var(env.arena, elem_var, env.subs)
|
||||
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
|
||||
|
||||
let expr = Expr::Array {
|
||||
elem_layout,
|
||||
elems: elements.into_bump_slice(),
|
||||
|
@ -8275,40 +8186,20 @@ fn from_can_pattern_help<'a>(
|
|||
Underscore => Ok(Pattern::Underscore),
|
||||
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
|
||||
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
|
||||
IntLiteral(_, precision_var, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
|
||||
IntOrFloat::Int(precision) => match *int {
|
||||
IntValue::I128(n) | IntValue::U128(n) => Ok(Pattern::IntLiteral(n, precision)),
|
||||
},
|
||||
other => {
|
||||
panic!(
|
||||
"Invalid precision for int pattern: {:?} has {:?}",
|
||||
can_pattern, other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
FloatLiteral(_, precision_var, float_str, float, _bound) => {
|
||||
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) {
|
||||
IntOrFloat::Int(_) => {
|
||||
panic!("Invalid precision for float pattern {:?}", precision_var)
|
||||
}
|
||||
IntOrFloat::Float(precision) => {
|
||||
Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
|
||||
}
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(float_str) {
|
||||
Some(d) => d,
|
||||
None => panic!(
|
||||
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
|
||||
float_str
|
||||
),
|
||||
};
|
||||
Ok(Pattern::DecimalLiteral(dec.to_ne_bytes()))
|
||||
}
|
||||
}
|
||||
}
|
||||
IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern(
|
||||
env,
|
||||
layout_cache,
|
||||
*var,
|
||||
int_str,
|
||||
IntOrFloatValue::Int(*int),
|
||||
)),
|
||||
FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern(
|
||||
env,
|
||||
layout_cache,
|
||||
*var,
|
||||
float_str,
|
||||
IntOrFloatValue::Float(*float),
|
||||
)),
|
||||
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
|
||||
SingleQuote(c) => Ok(Pattern::IntLiteral(
|
||||
(*c as i128).to_ne_bytes(),
|
||||
|
@ -8328,30 +8219,13 @@ fn from_can_pattern_help<'a>(
|
|||
// TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
|
||||
Err(RuntimeError::UnsupportedPattern(loc_ident.region))
|
||||
}
|
||||
NumLiteral(var, num_str, num, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
|
||||
IntOrFloat::Int(precision) => Ok(match num {
|
||||
IntValue::I128(num) | IntValue::U128(num) => {
|
||||
Pattern::IntLiteral(*num, precision)
|
||||
}
|
||||
}),
|
||||
IntOrFloat::Float(precision) => {
|
||||
// TODO: this may be lossy
|
||||
let num = match *num {
|
||||
IntValue::I128(n) => f64::to_bits(i128::from_ne_bytes(n) as f64),
|
||||
IntValue::U128(n) => f64::to_bits(u128::from_ne_bytes(n) as f64),
|
||||
};
|
||||
Ok(Pattern::FloatLiteral(num, precision))
|
||||
}
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(num_str) {
|
||||
Some(d) => d,
|
||||
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str),
|
||||
};
|
||||
Ok(Pattern::DecimalLiteral(dec.to_ne_bytes()))
|
||||
}
|
||||
}
|
||||
}
|
||||
NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern(
|
||||
env,
|
||||
layout_cache,
|
||||
*var,
|
||||
num_str,
|
||||
IntOrFloatValue::Int(*num),
|
||||
)),
|
||||
|
||||
AppliedTag {
|
||||
whole_var,
|
||||
|
@ -8962,77 +8836,99 @@ fn from_can_record_destruct<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IntOrFloat {
|
||||
Int(IntWidth),
|
||||
Float(FloatWidth),
|
||||
DecimalFloatType,
|
||||
enum IntOrFloatValue {
|
||||
Int(IntValue),
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
/// Given the `a` in `Num a`, determines whether it's an int or a float
|
||||
pub fn num_argument_to_int_or_float(
|
||||
subs: &Subs,
|
||||
target_info: TargetInfo,
|
||||
var: Variable,
|
||||
known_to_be_float: bool,
|
||||
) -> IntOrFloat {
|
||||
match subs.get_content_without_compacting(var) {
|
||||
Content::FlexVar(_) | Content::RigidVar(_) if known_to_be_float => {
|
||||
IntOrFloat::Float(FloatWidth::F64)
|
||||
}
|
||||
Content::FlexVar(_) | Content::RigidVar(_) => IntOrFloat::Int(IntWidth::I64), // We default (Num *) to I64
|
||||
enum NumLiteral {
|
||||
Int([u8; 16], IntWidth),
|
||||
U128([u8; 16]),
|
||||
Float(f64, FloatWidth),
|
||||
Decimal([u8; 16]),
|
||||
}
|
||||
|
||||
Content::Alias(Symbol::NUM_INTEGER, args, _, _) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
// Recurse on the second argument
|
||||
let var = subs[args.all_variables().into_iter().next().unwrap()];
|
||||
num_argument_to_int_or_float(subs, target_info, var, false)
|
||||
}
|
||||
|
||||
other @ Content::Alias(symbol, args, _, _) => {
|
||||
if let Some(int_width) = IntWidth::try_from_symbol(*symbol) {
|
||||
return IntOrFloat::Int(int_width);
|
||||
}
|
||||
|
||||
if let Some(float_width) = FloatWidth::try_from_symbol(*symbol) {
|
||||
return IntOrFloat::Float(float_width);
|
||||
}
|
||||
|
||||
match *symbol {
|
||||
Symbol::NUM_FLOATINGPOINT => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
// Recurse on the second argument
|
||||
let var = subs[args.all_variables().into_iter().next().unwrap()];
|
||||
num_argument_to_int_or_float(subs, target_info, var, true)
|
||||
}
|
||||
|
||||
Symbol::NUM_DECIMAL => IntOrFloat::DecimalFloatType,
|
||||
|
||||
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
|
||||
let int_width = match target_info.ptr_width() {
|
||||
roc_target::PtrWidth::Bytes4 => IntWidth::U32,
|
||||
roc_target::PtrWidth::Bytes8 => IntWidth::U64,
|
||||
};
|
||||
|
||||
IntOrFloat::Int(int_width)
|
||||
}
|
||||
|
||||
_ => panic!(
|
||||
"Unrecognized Num type argument for var {:?} with Content: {:?}",
|
||||
var, other
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
other => {
|
||||
panic!(
|
||||
"Unrecognized Num type argument for var {:?} with Content: {:?}",
|
||||
var, other
|
||||
);
|
||||
impl NumLiteral {
|
||||
fn to_expr_literal(&self) -> Literal<'static> {
|
||||
match *self {
|
||||
NumLiteral::Int(n, _) => Literal::Int(n),
|
||||
NumLiteral::U128(n) => Literal::U128(n),
|
||||
NumLiteral::Float(n, _) => Literal::Float(n),
|
||||
NumLiteral::Decimal(n) => Literal::Decimal(n),
|
||||
}
|
||||
}
|
||||
fn to_pattern(&self) -> Pattern<'static> {
|
||||
match *self {
|
||||
NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w),
|
||||
NumLiteral::U128(_) => todo!(),
|
||||
NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w),
|
||||
NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_num_literal(layout: Layout<'_>, num_str: &str, num_value: IntOrFloatValue) -> NumLiteral {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int(width)) => match num_value {
|
||||
IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width),
|
||||
IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n),
|
||||
IntOrFloatValue::Float(..) => {
|
||||
internal_error!("Float value where int was expected, should have been a type error")
|
||||
}
|
||||
},
|
||||
Layout::Builtin(Builtin::Float(width)) => match num_value {
|
||||
IntOrFloatValue::Float(n) => NumLiteral::Float(n, width),
|
||||
IntOrFloatValue::Int(int_value) => match int_value {
|
||||
IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width),
|
||||
IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width),
|
||||
},
|
||||
},
|
||||
Layout::Builtin(Builtin::Decimal) => {
|
||||
let dec = match RocDec::from_str(num_str) {
|
||||
Some(d) => d,
|
||||
None => internal_error!(
|
||||
"Invalid decimal for float literal = {}. This should be a type error!",
|
||||
num_str
|
||||
),
|
||||
};
|
||||
NumLiteral::Decimal(dec.to_ne_bytes())
|
||||
}
|
||||
layout => internal_error!(
|
||||
"Found a non-num layout where a number was expected: {:?}",
|
||||
layout
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_num_literal_expr<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
assigned: Symbol,
|
||||
variable: Variable,
|
||||
num_str: &str,
|
||||
num_value: IntOrFloatValue,
|
||||
hole: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, variable, env.subs)
|
||||
.unwrap();
|
||||
let literal = make_num_literal(layout, num_str, num_value).to_expr_literal();
|
||||
|
||||
Stmt::Let(assigned, Expr::Literal(literal), layout, hole)
|
||||
}
|
||||
|
||||
fn make_num_literal_pattern<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
variable: Variable,
|
||||
num_str: &str,
|
||||
num_value: IntOrFloatValue,
|
||||
) -> Pattern<'a> {
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, variable, env.subs)
|
||||
.unwrap();
|
||||
let literal = make_num_literal(layout, num_str, num_value);
|
||||
literal.to_pattern()
|
||||
}
|
||||
|
||||
type ToLowLevelCallArguments<'a> = (
|
||||
|
|
|
@ -8,6 +8,7 @@ use roc_module::ident::{Lowercase, TagName};
|
|||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_problem::can::RuntimeError;
|
||||
use roc_target::{PtrWidth, TargetInfo};
|
||||
use roc_types::num::NumericRange;
|
||||
use roc_types::subs::{
|
||||
self, Content, FlatType, Label, RecordFields, Subs, UnionTags, UnsortedUnionLabels, Variable,
|
||||
};
|
||||
|
@ -81,7 +82,9 @@ impl<'a> RawFunctionLayout<'a> {
|
|||
}
|
||||
LambdaSet(lset) => Self::layout_from_lambda_set(env, lset),
|
||||
Structure(flat_type) => Self::layout_from_flat_type(env, flat_type),
|
||||
RangedNumber(typ, _) => Self::from_var(env, typ),
|
||||
RangedNumber(..) => Ok(Self::ZeroArgumentThunk(Layout::new_help(
|
||||
env, var, content,
|
||||
)?)),
|
||||
|
||||
// Ints
|
||||
Alias(Symbol::NUM_I128, args, _, _) => {
|
||||
|
@ -1205,6 +1208,16 @@ pub fn is_unresolved_var(subs: &Subs, var: Variable) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_any_float_range(subs: &Subs, var: Variable) -> bool {
|
||||
use {roc_types::num::IntLitWidth::*, Content::*, NumericRange::*};
|
||||
let content = subs.get_content_without_compacting(var);
|
||||
matches!(
|
||||
content,
|
||||
RangedNumber(NumAtLeastEitherSign(I8) | NumAtLeastSigned(I8)),
|
||||
)
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub const VOID: Self = Layout::Union(UnionLayout::NonRecursive(&[]));
|
||||
pub const UNIT: Self = Layout::Struct {
|
||||
|
@ -1252,7 +1265,8 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
|
||||
Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT
|
||||
if is_unresolved_var(env.subs, actual_var) =>
|
||||
if is_unresolved_var(env.subs, actual_var)
|
||||
|| is_any_float_range(env.subs, actual_var) =>
|
||||
{
|
||||
// default to f64
|
||||
return Ok(Layout::f64());
|
||||
|
@ -1262,12 +1276,47 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
RangedNumber(typ, _) => Self::from_var(env, typ),
|
||||
RangedNumber(range) => Self::layout_from_ranged_number(env, range),
|
||||
|
||||
Error => Err(LayoutProblem::Erroneous),
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_from_ranged_number(
|
||||
env: &mut Env<'a, '_>,
|
||||
range: NumericRange,
|
||||
) -> Result<Self, LayoutProblem> {
|
||||
use roc_types::num::IntLitWidth;
|
||||
|
||||
// If we chose the default int layout then the real var might have been `Num *`, or
|
||||
// similar. In this case fix-up width if we need to. Choose I64 if the range says
|
||||
// that the number will fit, otherwise choose the next-largest number layout.
|
||||
//
|
||||
// We don't pass the range down because `RangedNumber`s are somewhat rare, they only
|
||||
// appear due to number literals, so no need to increase parameter list sizes.
|
||||
let num_layout = match range {
|
||||
NumericRange::IntAtLeastSigned(w) | NumericRange::NumAtLeastSigned(w) => {
|
||||
[IntLitWidth::I64, IntLitWidth::I128]
|
||||
.iter()
|
||||
.find(|candidate| candidate.is_superset(&w, true))
|
||||
.expect("if number doesn't fit, should have been a type error")
|
||||
}
|
||||
NumericRange::IntAtLeastEitherSign(w) | NumericRange::NumAtLeastEitherSign(w) => [
|
||||
IntLitWidth::I64,
|
||||
IntLitWidth::U64,
|
||||
IntLitWidth::I128,
|
||||
IntLitWidth::U128,
|
||||
]
|
||||
.iter()
|
||||
.find(|candidate| candidate.is_superset(&w, false))
|
||||
.expect("if number doesn't fit, should have been a type error"),
|
||||
};
|
||||
Ok(Layout::int_literal_width_to_int(
|
||||
*num_layout,
|
||||
env.target_info,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||
/// monomorphized away already!
|
||||
|
@ -1724,6 +1773,32 @@ impl<'a> Layout<'a> {
|
|||
pub fn default_float() -> Layout<'a> {
|
||||
Layout::f64()
|
||||
}
|
||||
|
||||
pub fn int_literal_width_to_int(
|
||||
width: roc_types::num::IntLitWidth,
|
||||
target_info: TargetInfo,
|
||||
) -> Layout<'a> {
|
||||
use roc_types::num::IntLitWidth::*;
|
||||
match width {
|
||||
U8 => Layout::u8(),
|
||||
U16 => Layout::u16(),
|
||||
U32 => Layout::u32(),
|
||||
U64 => Layout::u64(),
|
||||
U128 => Layout::u128(),
|
||||
I8 => Layout::i8(),
|
||||
I16 => Layout::i16(),
|
||||
I32 => Layout::i32(),
|
||||
I64 => Layout::i64(),
|
||||
I128 => Layout::i128(),
|
||||
Nat => Layout::usize(target_info),
|
||||
// f32 int literal bounded by +/- 2^24, so fit it into an i32
|
||||
F32 => Layout::i32(),
|
||||
// f64 int literal bounded by +/- 2^53, so fit it into an i32
|
||||
F64 => Layout::i64(),
|
||||
// dec int literal bounded by i128, so fit it into an i128
|
||||
Dec => Layout::i128(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Builtin<'a> {
|
||||
|
|
|
@ -144,7 +144,7 @@ impl FunctionLayout {
|
|||
Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset),
|
||||
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
|
||||
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
|
||||
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual),
|
||||
Content::RangedNumber(_) => todo!(),
|
||||
Content::Error => Err(TypeError(())),
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ impl LambdaSet {
|
|||
Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset),
|
||||
Content::Structure(_flat_type) => unreachable!(),
|
||||
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
|
||||
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual),
|
||||
Content::RangedNumber(_) => todo!(),
|
||||
Content::Error => Err(TypeError(())),
|
||||
}
|
||||
}
|
||||
|
@ -686,7 +686,7 @@ impl Layout {
|
|||
}
|
||||
}
|
||||
}
|
||||
Content::RangedNumber(typ, _) => Self::from_var_help(layouts, subs, *typ),
|
||||
Content::RangedNumber(_) => todo!(),
|
||||
Content::Error => Err(TypeError(())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1451,7 +1451,7 @@ macro_rules! word1_check_indent {
|
|||
($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => {
|
||||
and!(
|
||||
word1($word, $word_problem),
|
||||
crate::parser::check_indent($min_indent, $indent_problem)
|
||||
$crate::parser::check_indent($min_indent, $indent_problem)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1855,7 +1855,7 @@ fn compact_lambda_set<P: Phase>(
|
|||
| FlexVar(..)
|
||||
| RecursionVar { .. }
|
||||
| LambdaSet(..)
|
||||
| RangedNumber(_, _) => {
|
||||
| RangedNumber(_) => {
|
||||
internal_error!("unexpected")
|
||||
}
|
||||
};
|
||||
|
@ -2174,9 +2174,8 @@ fn type_to_variable<'a>(
|
|||
Variable(_) | EmptyRec | EmptyTagUnion => {
|
||||
unreachable!("This variant should never be deferred!")
|
||||
}
|
||||
RangedNumber(typ, range) => {
|
||||
let ty_var = helper!(typ);
|
||||
let content = Content::RangedNumber(ty_var, *range);
|
||||
RangedNumber(range) => {
|
||||
let content = Content::RangedNumber(*range);
|
||||
|
||||
register_with_known_var(subs, destination, rank, pools, content)
|
||||
}
|
||||
|
@ -3266,7 +3265,7 @@ fn adjust_rank_content(
|
|||
rank
|
||||
}
|
||||
|
||||
RangedNumber(typ, _) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ),
|
||||
RangedNumber(_) => group_rank,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3556,8 +3555,8 @@ fn deep_copy_var_help(
|
|||
);
|
||||
}
|
||||
|
||||
RangedNumber(typ, range) => {
|
||||
let new_content = RangedNumber(work!(typ), range);
|
||||
RangedNumber(range) => {
|
||||
let new_content = RangedNumber(range);
|
||||
|
||||
subs.set_content_unchecked(copy, new_content);
|
||||
}
|
||||
|
|
|
@ -1572,7 +1572,7 @@ mod solve_expr {
|
|||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo "happy" 2020
|
||||
Foo "happy" 12
|
||||
"#
|
||||
),
|
||||
"[Foo Str (Num *)]*",
|
||||
|
@ -2531,7 +2531,7 @@ mod solve_expr {
|
|||
{ numIdentity, x : numIdentity 42, y }
|
||||
"#
|
||||
),
|
||||
"{ numIdentity : Num a -> Num a, x : Num b, y : Float * }",
|
||||
"{ numIdentity : Num a -> Num a, x : Num *, y : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3951,7 +3951,7 @@ mod solve_expr {
|
|||
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
||||
"#
|
||||
),
|
||||
"{ x : Num a, y : Float *, z : Int * }",
|
||||
"{ x : Num *, y : Float *, z : Int * }",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3968,7 +3968,7 @@ mod solve_expr {
|
|||
{ a, b }
|
||||
"#
|
||||
),
|
||||
"{ a : { x : Num a, y : Float *, z : c }, b : { blah : Str, x : Num b, y : Float *, z : d } }",
|
||||
"{ a : { x : Num *, y : Float *, z : c }, b : { blah : Str, x : Num *, y : Float *, z : a } }",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7050,4 +7050,55 @@ mod solve_expr {
|
|||
&["fun : {} -[[thunk(5) [A Str]*, thunk(5) { a : Str }]]-> Str",]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_phantom_type() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
F a b := b
|
||||
|
||||
foo : F Str Str -> F U8 Str
|
||||
|
||||
x : F Str Str
|
||||
|
||||
foo x
|
||||
"#
|
||||
),
|
||||
"F U8 Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_phantom_type_flow() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
F a b := b
|
||||
|
||||
foo : _ -> F U8 Str
|
||||
foo = \it -> it
|
||||
|
||||
foo
|
||||
"#
|
||||
),
|
||||
"F U8 Str -> F U8 Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_unbound_phantom_type_star() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
F a b := b
|
||||
|
||||
foo = \@F {} -> @F ""
|
||||
|
||||
foo
|
||||
"#
|
||||
),
|
||||
"F * {}* -> F * Str",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3530,3 +3530,48 @@ fn round_to_u32() {
|
|||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn promote_u64_number_layout() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
9999999999999999999 + 1
|
||||
"#
|
||||
),
|
||||
10000000000000000000,
|
||||
u64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn promote_i128_number_layout() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
a: 18446744073709551616 + 1,
|
||||
b: -9223372036854775809 + 1,
|
||||
}
|
||||
"#
|
||||
),
|
||||
(18446744073709551617, -9223372036854775808),
|
||||
(i128, i128)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn promote_u128_number_layout() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
170141183460469231731687303715884105728 + 1
|
||||
"#
|
||||
),
|
||||
170141183460469231731687303715884105729,
|
||||
u128
|
||||
);
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ fn when_two_element_tag_first() {
|
|||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : [A (Int *), B (Int *)]
|
||||
x : [A (Int a), B (Int a)]
|
||||
x = A 0x2
|
||||
|
||||
when x is
|
||||
|
@ -192,7 +192,7 @@ fn when_two_element_tag_second() {
|
|||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x : [A (Int *), B (Int *)]
|
||||
x : [A (Int a), B (Int a)]
|
||||
x = B 0x3
|
||||
|
||||
when x is
|
||||
|
|
|
@ -1700,3 +1700,75 @@ fn to_scalar_4_byte() {
|
|||
RocList<u32>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn str_split_first() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.splitFirst "foo/bar/baz" "/"
|
||||
"#
|
||||
),
|
||||
// the result is a { before, after } record, and because of
|
||||
// alphabetic ordering the fields here are flipped
|
||||
RocResult::ok((RocStr::from("bar/baz"), RocStr::from("foo"))),
|
||||
RocResult<(RocStr, RocStr), ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn str_split_last() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.splitLast"foo/bar/baz" "/"
|
||||
"#
|
||||
),
|
||||
RocResult::ok((RocStr::from("baz"), RocStr::from("foo/bar"))),
|
||||
RocResult<(RocStr, RocStr), ()>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn str_walk_utf8_with_index() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.walkUtf8WithIndex "abcd" [] (\list, byte, index -> List.append list (Pair index byte))
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[(0, b'a'), (1, b'b'), (2, b'c'), (3, b'd')]),
|
||||
RocList<(u64, u8)>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn str_append_scalar() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.appendScalar "abcd" 'A'
|
||||
"#
|
||||
),
|
||||
RocStr::from("abcdA"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn str_walk_scalars() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.walkScalars "abcd" [] List.append
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&['a', 'b', 'c', 'd']),
|
||||
RocList<char>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ macro_rules! from_wasm_memory_primitive {
|
|||
}
|
||||
|
||||
from_wasm_memory_primitive!(
|
||||
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder,
|
||||
u8, i8, u16, i16, u32, i32, char, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder,
|
||||
);
|
||||
|
||||
impl FromWasmerMemory for () {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::helpers::from_wasmer_memory::FromWasmerMemory;
|
||||
use inkwell::module::Module;
|
||||
use libloading::Library;
|
||||
use roc_build::link::module_to_dylib;
|
||||
use roc_build::link::llvm_module_to_dylib;
|
||||
use roc_build::program::FunctionIterator;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
|
|
|
@ -1314,3 +1314,16 @@ fn str_to_dec() {
|
|||
RocDec
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_walk_scalars() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Str.walkScalars "abcd" [] List.append
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&['a', 'b', 'c', 'd']),
|
||||
RocList<char>
|
||||
);
|
||||
}
|
||||
|
|
13
crates/compiler/test_mono/generated/choose_i128_layout.txt
Normal file
13
crates/compiler/test_mono/generated/choose_i128_layout.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
procedure Num.19 (#Attr.2, #Attr.3):
|
||||
let Num.189 : I128 = lowlevel NumAdd #Attr.2 #Attr.3;
|
||||
ret Num.189;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.6 : I128 = 18446744073709551616i64;
|
||||
let Test.7 : I128 = 1i64;
|
||||
let Test.2 : I128 = CallByName Num.19 Test.6 Test.7;
|
||||
let Test.4 : I128 = -9223372036854775809i64;
|
||||
let Test.5 : I128 = 1i64;
|
||||
let Test.3 : I128 = CallByName Num.19 Test.4 Test.5;
|
||||
let Test.1 : {I128, I128} = Struct {Test.2, Test.3};
|
||||
ret Test.1;
|
|
@ -0,0 +1,9 @@
|
|||
procedure Num.19 (#Attr.2, #Attr.3):
|
||||
let Num.188 : U128 = lowlevel NumAdd #Attr.2 #Attr.3;
|
||||
ret Num.188;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.2 : U128 = 170141183460469231731687303715884105728u128;
|
||||
let Test.3 : U128 = 1i64;
|
||||
let Test.1 : U128 = CallByName Num.19 Test.2 Test.3;
|
||||
ret Test.1;
|
|
@ -0,0 +1,9 @@
|
|||
procedure Num.19 (#Attr.2, #Attr.3):
|
||||
let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
|
||||
ret Num.188;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.2 : U64 = 9999999999999999999i64;
|
||||
let Test.3 : U64 = 1i64;
|
||||
let Test.1 : U64 = CallByName Num.19 Test.2 Test.3;
|
||||
ret Test.1;
|
|
@ -39,17 +39,17 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
|||
|
||||
procedure Str.27 (#Attr.2):
|
||||
let #Attr.3 : {I64, U8} = lowlevel StrToNum #Attr.2;
|
||||
let Str.42 : U8 = StructAtIndex 1 #Attr.3;
|
||||
let Str.43 : U8 = 0i64;
|
||||
let Str.39 : Int1 = lowlevel NumGt Str.42 Str.43;
|
||||
if Str.39 then
|
||||
let Str.41 : Int1 = false;
|
||||
let Str.40 : [C Int1, C I64] = TagId(0) Str.41;
|
||||
ret Str.40;
|
||||
let Str.159 : U8 = StructAtIndex 1 #Attr.3;
|
||||
let Str.160 : U8 = 0i64;
|
||||
let Str.156 : Int1 = lowlevel NumGt Str.159 Str.160;
|
||||
if Str.156 then
|
||||
let Str.158 : Int1 = false;
|
||||
let Str.157 : [C Int1, C I64] = TagId(0) Str.158;
|
||||
ret Str.157;
|
||||
else
|
||||
let Str.38 : I64 = StructAtIndex 0 #Attr.3;
|
||||
let Str.37 : [C Int1, C I64] = TagId(1) Str.38;
|
||||
ret Str.37;
|
||||
let Str.155 : I64 = StructAtIndex 0 #Attr.3;
|
||||
let Str.154 : [C Int1, C I64] = TagId(1) Str.155;
|
||||
ret Str.154;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.4 : Int1 = true;
|
||||
|
|
|
@ -27,12 +27,12 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
|||
ret Num.188;
|
||||
|
||||
procedure Str.16 (#Attr.2, #Attr.3):
|
||||
let Str.37 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
|
||||
ret Str.37;
|
||||
let Str.154 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
|
||||
ret Str.154;
|
||||
|
||||
procedure Str.3 (#Attr.2, #Attr.3):
|
||||
let Str.38 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.38;
|
||||
let Str.155 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.155;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
||||
|
|
|
@ -29,8 +29,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
|||
ret Num.188;
|
||||
|
||||
procedure Str.3 (#Attr.2, #Attr.3):
|
||||
let Str.38 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.38;
|
||||
let Str.155 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.155;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
||||
|
|
|
@ -1652,3 +1652,33 @@ fn lambda_set_niche_same_layout_different_constructor() {
|
|||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn choose_u64_layout() {
|
||||
indoc!(
|
||||
r#"
|
||||
9999999999999999999 + 1
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn choose_i128_layout() {
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
a: 18446744073709551616 + 1,
|
||||
b: -9223372036854775809 + 1,
|
||||
}
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn choose_u128_layout() {
|
||||
indoc!(
|
||||
r#"
|
||||
170141183460469231731687303715884105728 + 1
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,54 +1,22 @@
|
|||
use crate::subs::Variable;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
/// A bound placed on a number because of its literal value.
|
||||
/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum NumericRange {
|
||||
IntAtLeastSigned(IntWidth),
|
||||
IntAtLeastEitherSign(IntWidth),
|
||||
NumAtLeastSigned(IntWidth),
|
||||
NumAtLeastEitherSign(IntWidth),
|
||||
IntAtLeastSigned(IntLitWidth),
|
||||
IntAtLeastEitherSign(IntLitWidth),
|
||||
NumAtLeastSigned(IntLitWidth),
|
||||
NumAtLeastEitherSign(IntLitWidth),
|
||||
}
|
||||
|
||||
impl NumericRange {
|
||||
pub fn contains_symbol(&self, symbol: Symbol) -> Option<bool> {
|
||||
let contains = match symbol {
|
||||
Symbol::NUM_I8 => self.contains_int_width(IntWidth::I8),
|
||||
Symbol::NUM_U8 => self.contains_int_width(IntWidth::U8),
|
||||
Symbol::NUM_I16 => self.contains_int_width(IntWidth::I16),
|
||||
Symbol::NUM_U16 => self.contains_int_width(IntWidth::U16),
|
||||
Symbol::NUM_I32 => self.contains_int_width(IntWidth::I32),
|
||||
Symbol::NUM_U32 => self.contains_int_width(IntWidth::U32),
|
||||
Symbol::NUM_I64 => self.contains_int_width(IntWidth::I64),
|
||||
Symbol::NUM_NAT => self.contains_int_width(IntWidth::Nat),
|
||||
Symbol::NUM_U64 => self.contains_int_width(IntWidth::U64),
|
||||
Symbol::NUM_I128 => self.contains_int_width(IntWidth::I128),
|
||||
Symbol::NUM_U128 => self.contains_int_width(IntWidth::U128),
|
||||
|
||||
Symbol::NUM_DEC => self.contains_float_width(FloatWidth::Dec),
|
||||
Symbol::NUM_F32 => self.contains_float_width(FloatWidth::F32),
|
||||
Symbol::NUM_F64 => self.contains_float_width(FloatWidth::F64),
|
||||
|
||||
Symbol::NUM_NUM | Symbol::NUM_INT | Symbol::NUM_FRAC => {
|
||||
// these satisfy any range that they are given
|
||||
true
|
||||
}
|
||||
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(contains)
|
||||
}
|
||||
|
||||
fn contains_float_width(&self, _width: FloatWidth) -> bool {
|
||||
pub fn contains_float_width(&self, _width: FloatWidth) -> bool {
|
||||
// we don't currently check the float width
|
||||
true
|
||||
}
|
||||
|
||||
fn contains_int_width(&self, width: IntWidth) -> bool {
|
||||
pub fn contains_int_width(&self, width: IntLitWidth) -> bool {
|
||||
use NumericRange::*;
|
||||
|
||||
let (range_signedness, at_least_width) = match self {
|
||||
|
@ -68,35 +36,88 @@ impl NumericRange {
|
|||
width.signedness_and_width().1 >= at_least_width.signedness_and_width().1
|
||||
}
|
||||
|
||||
fn width(&self) -> IntLitWidth {
|
||||
use NumericRange::*;
|
||||
match self {
|
||||
IntAtLeastSigned(w)
|
||||
| IntAtLeastEitherSign(w)
|
||||
| NumAtLeastSigned(w)
|
||||
| NumAtLeastEitherSign(w) => *w,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the intersection of `self` and `other`, i.e. the greatest lower bound of both, or
|
||||
/// `None` if there is no common lower bound.
|
||||
pub fn intersection(&self, other: &Self) -> Option<Self> {
|
||||
use NumericRange::*;
|
||||
let (left, right) = (self.width(), other.width());
|
||||
let (constructor, is_negative): (fn(IntLitWidth) -> NumericRange, _) = match (self, other) {
|
||||
// Matching against a signed int, the intersection must also be a signed int
|
||||
(IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => (IntAtLeastSigned, true),
|
||||
// It's a signed number, but also an int, so the intersection must be a signed int
|
||||
(NumAtLeastSigned(_), IntAtLeastEitherSign(_))
|
||||
| (IntAtLeastEitherSign(_), NumAtLeastSigned(_)) => (IntAtLeastSigned, true),
|
||||
// It's a signed number
|
||||
(NumAtLeastSigned(_), NumAtLeastSigned(_) | NumAtLeastEitherSign(_))
|
||||
| (NumAtLeastEitherSign(_), NumAtLeastSigned(_)) => (NumAtLeastSigned, true),
|
||||
// Otherwise we must be an int, signed or unsigned
|
||||
(IntAtLeastEitherSign(_), IntAtLeastEitherSign(_) | NumAtLeastEitherSign(_))
|
||||
| (NumAtLeastEitherSign(_), IntAtLeastEitherSign(_)) => (IntAtLeastEitherSign, false),
|
||||
// Otherwise we must be a num, signed or unsigned
|
||||
(NumAtLeastEitherSign(_), NumAtLeastEitherSign(_)) => (NumAtLeastEitherSign, false),
|
||||
};
|
||||
|
||||
// If the intersection must be signed but one of the lower bounds isn't signed, then there
|
||||
// is no intersection.
|
||||
if is_negative && (!left.is_signed() || !right.is_signed()) {
|
||||
None
|
||||
}
|
||||
// Otherwise, find the greatest lower bound depending on the signed-ness.
|
||||
else if left.is_superset(&right, is_negative) {
|
||||
Some(constructor(left))
|
||||
} else if right.is_superset(&left, is_negative) {
|
||||
Some(constructor(right))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn variable_slice(&self) -> &'static [Variable] {
|
||||
use NumericRange::*;
|
||||
|
||||
match self {
|
||||
IntAtLeastSigned(width) => {
|
||||
let target = int_width_to_variable(*width);
|
||||
let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap();
|
||||
let end = SIGNED_VARIABLES.len() - 3;
|
||||
let target = int_lit_width_to_variable(*width);
|
||||
let start = SIGNED_INT_VARIABLES
|
||||
.iter()
|
||||
.position(|v| *v == target)
|
||||
.unwrap();
|
||||
|
||||
&SIGNED_VARIABLES[start..end]
|
||||
&SIGNED_INT_VARIABLES[start..]
|
||||
}
|
||||
IntAtLeastEitherSign(width) => {
|
||||
let target = int_width_to_variable(*width);
|
||||
let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap();
|
||||
let end = ALL_VARIABLES.len() - 3;
|
||||
let target = int_lit_width_to_variable(*width);
|
||||
let start = ALL_INT_VARIABLES.iter().position(|v| *v == target).unwrap();
|
||||
|
||||
&ALL_VARIABLES[start..end]
|
||||
&ALL_INT_VARIABLES[start..]
|
||||
}
|
||||
NumAtLeastSigned(width) => {
|
||||
let target = int_width_to_variable(*width);
|
||||
let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap();
|
||||
let target = int_lit_width_to_variable(*width);
|
||||
let start = SIGNED_INT_OR_FLOAT_VARIABLES
|
||||
.iter()
|
||||
.position(|v| *v == target)
|
||||
.unwrap();
|
||||
|
||||
&SIGNED_VARIABLES[start..]
|
||||
&SIGNED_INT_OR_FLOAT_VARIABLES[start..]
|
||||
}
|
||||
NumAtLeastEitherSign(width) => {
|
||||
let target = int_width_to_variable(*width);
|
||||
let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap();
|
||||
let target = int_lit_width_to_variable(*width);
|
||||
let start = ALL_INT_OR_FLOAT_VARIABLES
|
||||
.iter()
|
||||
.position(|v| *v == target)
|
||||
.unwrap();
|
||||
|
||||
&ALL_VARIABLES[start..]
|
||||
&ALL_INT_OR_FLOAT_VARIABLES[start..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +130,7 @@ enum IntSignedness {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum IntWidth {
|
||||
pub enum IntLitWidth {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
|
@ -121,13 +142,21 @@ pub enum IntWidth {
|
|||
I64,
|
||||
I128,
|
||||
Nat,
|
||||
// An int literal can be promoted to an f32/f64/Dec if appropriate. The respective widths for
|
||||
// integers that can be stored in these float types without losing precision are:
|
||||
// f32: +/- 2^24
|
||||
// f64: +/- 2^53
|
||||
// dec: Int128::MAX/Int128::MIN
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
}
|
||||
|
||||
impl IntWidth {
|
||||
impl IntLitWidth {
|
||||
/// Returns the `IntSignedness` and bit width of a variant.
|
||||
fn signedness_and_width(&self) -> (IntSignedness, u32) {
|
||||
use IntLitWidth::*;
|
||||
use IntSignedness::*;
|
||||
use IntWidth::*;
|
||||
match self {
|
||||
U8 => (Unsigned, 8),
|
||||
U16 => (Unsigned, 16),
|
||||
|
@ -139,13 +168,20 @@ impl IntWidth {
|
|||
I32 => (Signed, 32),
|
||||
I64 => (Signed, 64),
|
||||
I128 => (Signed, 128),
|
||||
// TODO: this is platform specific!
|
||||
// TODO: Nat is platform specific!
|
||||
Nat => (Unsigned, 64),
|
||||
F32 => (Signed, 24),
|
||||
F64 => (Signed, 53),
|
||||
Dec => (Signed, 128),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_signed(&self) -> bool {
|
||||
self.signedness_and_width().0 == IntSignedness::Signed
|
||||
}
|
||||
|
||||
pub fn type_str(&self) -> &'static str {
|
||||
use IntWidth::*;
|
||||
use IntLitWidth::*;
|
||||
match self {
|
||||
U8 => "U8",
|
||||
U16 => "U16",
|
||||
|
@ -158,11 +194,14 @@ impl IntWidth {
|
|||
I64 => "I64",
|
||||
I128 => "I128",
|
||||
Nat => "Nat",
|
||||
F32 => "F32",
|
||||
F64 => "F64",
|
||||
Dec => "Dec",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_value(&self) -> u128 {
|
||||
use IntWidth::*;
|
||||
use IntLitWidth::*;
|
||||
match self {
|
||||
U8 => u8::MAX as u128,
|
||||
U16 => u16::MAX as u128,
|
||||
|
@ -176,11 +215,17 @@ impl IntWidth {
|
|||
I128 => i128::MAX as u128,
|
||||
// TODO: this is platform specific!
|
||||
Nat => u64::MAX as u128,
|
||||
// Max int value without losing precision: 2^24
|
||||
F32 => 16_777_216,
|
||||
// Max int value without losing precision: 2^53
|
||||
F64 => 9_007_199_254_740_992,
|
||||
// Max int value without losing precision: I128::MAX
|
||||
Dec => i128::MAX as u128,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_value(&self) -> i128 {
|
||||
use IntWidth::*;
|
||||
use IntLitWidth::*;
|
||||
match self {
|
||||
U8 | U16 | U32 | U64 | U128 | Nat => 0,
|
||||
I8 => i8::MIN as i128,
|
||||
|
@ -188,6 +233,12 @@ impl IntWidth {
|
|||
I32 => i32::MIN as i128,
|
||||
I64 => i64::MIN as i128,
|
||||
I128 => i128::MIN,
|
||||
// Min int value without losing precision: -2^24
|
||||
F32 => -16_777_216,
|
||||
// Min int value without losing precision: -2^53
|
||||
F64 => -9_007_199_254_740_992,
|
||||
// Min int value without losing precision: I128::MIN
|
||||
Dec => i128::MIN,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,9 +304,12 @@ pub enum IntBound {
|
|||
/// There is no bound on the width.
|
||||
None,
|
||||
/// Must have an exact width.
|
||||
Exact(IntWidth),
|
||||
Exact(IntLitWidth),
|
||||
/// Must have a certain sign and a minimum width.
|
||||
AtLeast { sign: SignDemand, width: IntWidth },
|
||||
AtLeast {
|
||||
sign: SignDemand,
|
||||
width: IntLitWidth,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
|
@ -270,23 +324,26 @@ pub enum NumBound {
|
|||
/// Must be an integer of a certain size, or any float.
|
||||
AtLeastIntOrFloat {
|
||||
sign: SignDemand,
|
||||
width: IntWidth,
|
||||
width: IntLitWidth,
|
||||
},
|
||||
}
|
||||
|
||||
pub const fn int_width_to_variable(w: IntWidth) -> Variable {
|
||||
pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
|
||||
match w {
|
||||
IntWidth::U8 => Variable::U8,
|
||||
IntWidth::U16 => Variable::U16,
|
||||
IntWidth::U32 => Variable::U32,
|
||||
IntWidth::U64 => Variable::U64,
|
||||
IntWidth::U128 => Variable::U128,
|
||||
IntWidth::I8 => Variable::I8,
|
||||
IntWidth::I16 => Variable::I16,
|
||||
IntWidth::I32 => Variable::I32,
|
||||
IntWidth::I64 => Variable::I64,
|
||||
IntWidth::I128 => Variable::I128,
|
||||
IntWidth::Nat => Variable::NAT,
|
||||
IntLitWidth::U8 => Variable::U8,
|
||||
IntLitWidth::U16 => Variable::U16,
|
||||
IntLitWidth::U32 => Variable::U32,
|
||||
IntLitWidth::U64 => Variable::U64,
|
||||
IntLitWidth::U128 => Variable::U128,
|
||||
IntLitWidth::I8 => Variable::I8,
|
||||
IntLitWidth::I16 => Variable::I16,
|
||||
IntLitWidth::I32 => Variable::I32,
|
||||
IntLitWidth::I64 => Variable::I64,
|
||||
IntLitWidth::I128 => Variable::I128,
|
||||
IntLitWidth::Nat => Variable::NAT,
|
||||
IntLitWidth::F32 => Variable::F32,
|
||||
IntLitWidth::F64 => Variable::F64,
|
||||
IntLitWidth::Dec => Variable::DEC,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,7 +355,35 @@ pub const fn float_width_to_variable(w: FloatWidth) -> Variable {
|
|||
}
|
||||
}
|
||||
|
||||
const ALL_VARIABLES: &[Variable] = &[
|
||||
const ALL_INT_OR_FLOAT_VARIABLES: &[Variable] = &[
|
||||
Variable::I8,
|
||||
Variable::U8,
|
||||
Variable::I16,
|
||||
Variable::U16,
|
||||
Variable::F32,
|
||||
Variable::I32,
|
||||
Variable::U32,
|
||||
Variable::F64,
|
||||
Variable::I64,
|
||||
Variable::NAT, // FIXME: Nat's order here depends on the platform
|
||||
Variable::U64,
|
||||
Variable::I128,
|
||||
Variable::DEC,
|
||||
Variable::U128,
|
||||
];
|
||||
|
||||
const SIGNED_INT_OR_FLOAT_VARIABLES: &[Variable] = &[
|
||||
Variable::I8,
|
||||
Variable::I16,
|
||||
Variable::F32,
|
||||
Variable::I32,
|
||||
Variable::F64,
|
||||
Variable::I64,
|
||||
Variable::I128,
|
||||
Variable::DEC,
|
||||
];
|
||||
|
||||
const ALL_INT_VARIABLES: &[Variable] = &[
|
||||
Variable::I8,
|
||||
Variable::U8,
|
||||
Variable::I16,
|
||||
|
@ -306,22 +391,16 @@ const ALL_VARIABLES: &[Variable] = &[
|
|||
Variable::I32,
|
||||
Variable::U32,
|
||||
Variable::I64,
|
||||
Variable::NAT, // FIXME: Nat's order here depends on the platfor,
|
||||
Variable::NAT, // FIXME: Nat's order here depends on the platform
|
||||
Variable::U64,
|
||||
Variable::I128,
|
||||
Variable::U128,
|
||||
Variable::F32,
|
||||
Variable::F64,
|
||||
Variable::DEC,
|
||||
];
|
||||
|
||||
const SIGNED_VARIABLES: &[Variable] = &[
|
||||
const SIGNED_INT_VARIABLES: &[Variable] = &[
|
||||
Variable::I8,
|
||||
Variable::I16,
|
||||
Variable::I32,
|
||||
Variable::I64,
|
||||
Variable::I128,
|
||||
Variable::F32,
|
||||
Variable::F64,
|
||||
Variable::DEC,
|
||||
];
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::types::{name_type_var, RecordField, Uls};
|
|||
use roc_collections::all::MutMap;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use std::fmt::Write;
|
||||
|
||||
pub static WILDCARD: &str = "*";
|
||||
static EMPTY_RECORD: &str = "{}";
|
||||
|
@ -380,9 +381,10 @@ fn find_names_needed(
|
|||
);
|
||||
}
|
||||
}
|
||||
&RangedNumber(typ, _) => {
|
||||
RangedNumber(_) => {
|
||||
subs.set_content(variable, FlexVar(None));
|
||||
find_names_needed(
|
||||
typ,
|
||||
variable,
|
||||
subs,
|
||||
roots,
|
||||
root_appearances,
|
||||
|
@ -643,7 +645,7 @@ fn write_content<'a>(
|
|||
subs,
|
||||
buf,
|
||||
parens,
|
||||
false,
|
||||
write_parens,
|
||||
);
|
||||
}
|
||||
Symbol::NUM_FLOATINGPOINT => write_float(
|
||||
|
@ -782,14 +784,23 @@ fn write_content<'a>(
|
|||
|
||||
buf.push(']');
|
||||
}
|
||||
RangedNumber(typ, _range_vars) => write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*typ),
|
||||
subs,
|
||||
buf,
|
||||
parens,
|
||||
),
|
||||
RangedNumber(range) => {
|
||||
buf.push_str("Range(");
|
||||
for (i, &var) in range.variable_slice().iter().enumerate() {
|
||||
if i > 0 {
|
||||
buf.push_str(", ");
|
||||
}
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(var),
|
||||
subs,
|
||||
buf,
|
||||
Parens::Unnecessary,
|
||||
);
|
||||
}
|
||||
buf.push(')');
|
||||
}
|
||||
Error => buf.push_str("<type mismatch>"),
|
||||
}
|
||||
}
|
||||
|
@ -828,21 +839,23 @@ fn write_integer<'a>(
|
|||
|
||||
macro_rules! derive_num_writes {
|
||||
($($lit:expr, $tag:path)*) => {
|
||||
write_parens!(
|
||||
write_parens,
|
||||
buf,
|
||||
match content {
|
||||
$(
|
||||
&Alias($tag, _, _, _) => {
|
||||
buf.push_str($lit)
|
||||
},
|
||||
)*
|
||||
actual => {
|
||||
buf.push_str("Int ");
|
||||
write_content(env, ctx, actual, subs, buf, parens);
|
||||
}
|
||||
match content {
|
||||
$(
|
||||
&Alias($tag, _, _, _) => {
|
||||
buf.push_str($lit)
|
||||
},
|
||||
)*
|
||||
actual => {
|
||||
write_parens!(
|
||||
write_parens,
|
||||
buf,
|
||||
{
|
||||
buf.push_str("Int ");
|
||||
write_content(env, ctx, actual, subs, buf, parens);
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1145,9 +1158,7 @@ fn write_flat_type<'a>(
|
|||
)
|
||||
})
|
||||
}
|
||||
Erroneous(problem) => {
|
||||
buf.push_str(&format!("<Type Mismatch: {:?}>", problem));
|
||||
}
|
||||
Erroneous(problem) => write!(buf, "<Type Mismatch: {:?}>", problem).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -841,8 +841,8 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
|
|||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
Content::RangedNumber(typ, range) => {
|
||||
write!(f, "RangedNumber({:?}, {:?})", typ, range)
|
||||
Content::RangedNumber(range) => {
|
||||
write!(f, "RangedNumber( {:?})", range)
|
||||
}
|
||||
Content::Error => write!(f, "Error"),
|
||||
}
|
||||
|
@ -2202,7 +2202,7 @@ pub enum Content {
|
|||
LambdaSet(LambdaSet),
|
||||
Structure(FlatType),
|
||||
Alias(Symbol, AliasVariables, Variable, AliasKind),
|
||||
RangedNumber(Variable, crate::num::NumericRange),
|
||||
RangedNumber(crate::num::NumericRange),
|
||||
Error,
|
||||
}
|
||||
|
||||
|
@ -3150,15 +3150,7 @@ fn occurs(
|
|||
|
||||
occurs_union(subs, root_var, &new_seen, include_recursion_var, solved)
|
||||
}
|
||||
RangedNumber(typ, _range_vars) => {
|
||||
let mut new_seen = seen.to_owned();
|
||||
new_seen.push(root_var);
|
||||
|
||||
short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?;
|
||||
// _range_vars excluded because they are not explicitly part of the type.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
RangedNumber(_range_vars) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3345,10 +3337,8 @@ fn explicit_substitute(
|
|||
|
||||
in_var
|
||||
}
|
||||
RangedNumber(typ, range) => {
|
||||
let new_typ = explicit_substitute(subs, from, to, typ, seen);
|
||||
|
||||
subs.set_content(in_var, RangedNumber(new_typ, range));
|
||||
RangedNumber(range) => {
|
||||
subs.set_content(in_var, RangedNumber(range));
|
||||
|
||||
in_var
|
||||
}
|
||||
|
@ -3462,7 +3452,7 @@ fn get_var_names(
|
|||
taken_names
|
||||
}
|
||||
|
||||
RangedNumber(typ, _) => get_var_names(subs, typ, taken_names),
|
||||
RangedNumber(_) => taken_names,
|
||||
|
||||
Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(_, args) => {
|
||||
|
@ -3685,6 +3675,13 @@ fn content_to_err_type(
|
|||
Alias(symbol, args, aliased_to, kind) => {
|
||||
let err_type = var_to_err_type(subs, state, aliased_to);
|
||||
|
||||
// Lift RangedNumber up if needed.
|
||||
if let (Symbol::NUM_INT | Symbol::NUM_NUM | Symbol::NUM_INTEGER, ErrorType::Range(_)) =
|
||||
(symbol, &err_type)
|
||||
{
|
||||
return err_type;
|
||||
}
|
||||
|
||||
let mut err_args = Vec::with_capacity(args.len());
|
||||
|
||||
for var_index in args.into_iter() {
|
||||
|
@ -3703,17 +3700,18 @@ fn content_to_err_type(
|
|||
ErrorType::Error
|
||||
}
|
||||
|
||||
RangedNumber(typ, range) => {
|
||||
let err_type = var_to_err_type(subs, state, typ);
|
||||
|
||||
RangedNumber(range) => {
|
||||
if state.context == ErrorTypeContext::ExpandRanges {
|
||||
let mut types = Vec::new();
|
||||
for var in range.variable_slice() {
|
||||
types.push(var_to_err_type(subs, state, *var));
|
||||
}
|
||||
ErrorType::Range(Box::new(err_type), types)
|
||||
ErrorType::Range(types)
|
||||
} else {
|
||||
err_type
|
||||
let content = FlexVar(None);
|
||||
subs.set_content(var, content);
|
||||
subs.set_mark(var, Mark::NONE);
|
||||
var_to_err_type(subs, state, var)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3968,6 +3966,9 @@ impl StorageSubs {
|
|||
pub fn as_inner_mut(&mut self) -> &mut Subs {
|
||||
&mut self.subs
|
||||
}
|
||||
pub fn as_inner(&self) -> &Subs {
|
||||
&self.subs
|
||||
}
|
||||
|
||||
pub fn extend_with_variable(&mut self, source: &mut Subs, variable: Variable) -> Variable {
|
||||
storage_copy_var_to(source, &mut self.subs, variable)
|
||||
|
@ -4160,7 +4161,7 @@ impl StorageSubs {
|
|||
recursion_var: recursion_var.map(|v| Self::offset_variable(offsets, v)),
|
||||
unspecialized: Self::offset_uls_slice(offsets, *unspecialized),
|
||||
}),
|
||||
RangedNumber(typ, range) => RangedNumber(Self::offset_variable(offsets, *typ), *range),
|
||||
RangedNumber(range) => RangedNumber(*range),
|
||||
Error => Content::Error,
|
||||
}
|
||||
}
|
||||
|
@ -4601,11 +4602,8 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
|
|||
copy
|
||||
}
|
||||
|
||||
RangedNumber(typ, range) => {
|
||||
let new_typ = storage_copy_var_to_help(env, typ);
|
||||
|
||||
let new_content = RangedNumber(new_typ, range);
|
||||
|
||||
RangedNumber(range) => {
|
||||
let new_content = RangedNumber(range);
|
||||
env.target.set(copy, make_descriptor(new_content));
|
||||
copy
|
||||
}
|
||||
|
@ -4729,7 +4727,7 @@ fn is_registered(content: &Content) -> bool {
|
|||
Content::Structure(_)
|
||||
| Content::RecursionVar { .. }
|
||||
| Content::Alias(_, _, _, _)
|
||||
| Content::RangedNumber(_, _)
|
||||
| Content::RangedNumber(_)
|
||||
| Content::Error
|
||||
| Content::LambdaSet(_) => true,
|
||||
}
|
||||
|
@ -5067,10 +5065,8 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
|
|||
copy
|
||||
}
|
||||
|
||||
RangedNumber(typ, range) => {
|
||||
let new_typ = copy_import_to_help(env, max_rank, typ);
|
||||
|
||||
let new_content = RangedNumber(new_typ, range);
|
||||
RangedNumber(range) => {
|
||||
let new_content = RangedNumber(range);
|
||||
|
||||
env.target.set(copy, make_descriptor(new_content));
|
||||
copy
|
||||
|
@ -5232,9 +5228,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
|
|||
stack.push(*var);
|
||||
}
|
||||
}
|
||||
&RangedNumber(typ, _) => {
|
||||
stack.push(typ);
|
||||
}
|
||||
&RangedNumber(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use roc_module::low_level::LowLevel;
|
|||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
pub const TYPE_NUM: &str = "Num";
|
||||
pub const TYPE_INTEGER: &str = "Integer";
|
||||
|
@ -260,7 +261,7 @@ pub enum Type {
|
|||
/// Applying a type to some arguments (e.g. Dict.Dict String Int)
|
||||
Apply(Symbol, Vec<Type>, Region),
|
||||
Variable(Variable),
|
||||
RangedNumber(Box<Type>, NumericRange),
|
||||
RangedNumber(NumericRange),
|
||||
/// A type error, which will code gen to a runtime error
|
||||
Erroneous(Problem),
|
||||
}
|
||||
|
@ -348,7 +349,7 @@ impl Clone for Type {
|
|||
}
|
||||
Self::Apply(arg0, arg1, arg2) => Self::Apply(*arg0, arg1.clone(), *arg2),
|
||||
Self::Variable(arg0) => Self::Variable(*arg0),
|
||||
Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), *arg1),
|
||||
Self::RangedNumber(arg1) => Self::RangedNumber(*arg1),
|
||||
Self::Erroneous(arg0) => Self::Erroneous(arg0.clone()),
|
||||
}
|
||||
}
|
||||
|
@ -651,8 +652,8 @@ impl fmt::Debug for Type {
|
|||
|
||||
write!(f, " as <{:?}>", rec)
|
||||
}
|
||||
Type::RangedNumber(typ, range_vars) => {
|
||||
write!(f, "Ranged({:?}, {:?})", typ, range_vars)
|
||||
Type::RangedNumber(range_vars) => {
|
||||
write!(f, "Ranged({:?})", range_vars)
|
||||
}
|
||||
Type::UnspecializedLambdaSet(uls) => {
|
||||
write!(f, "{:?}", uls)
|
||||
|
@ -793,9 +794,7 @@ impl Type {
|
|||
Apply(_, args, _) => {
|
||||
stack.extend(args);
|
||||
}
|
||||
RangedNumber(typ, _) => {
|
||||
stack.push(typ);
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet(Uls(v, _, _)) => {
|
||||
debug_assert!(
|
||||
substitutions.get(v).is_none(),
|
||||
|
@ -910,9 +909,7 @@ impl Type {
|
|||
Apply(_, args, _) => {
|
||||
stack.extend(args);
|
||||
}
|
||||
RangedNumber(typ, _) => {
|
||||
stack.push(typ);
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet(Uls(v, _, _)) => {
|
||||
debug_assert!(
|
||||
substitutions.get(v).is_none(),
|
||||
|
@ -1015,7 +1012,7 @@ impl Type {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
RangedNumber(typ, _) => typ.substitute_alias(rep_symbol, rep_args, actual),
|
||||
RangedNumber(_) => Ok(()),
|
||||
UnspecializedLambdaSet(..) => Ok(()),
|
||||
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()),
|
||||
}
|
||||
|
@ -1072,7 +1069,7 @@ impl Type {
|
|||
}
|
||||
Apply(symbol, _, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
RangedNumber(typ, _) => typ.contains_symbol(rep_symbol),
|
||||
RangedNumber(_) => false,
|
||||
UnspecializedLambdaSet(Uls(_, sym, _)) => *sym == rep_symbol,
|
||||
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false,
|
||||
}
|
||||
|
@ -1123,7 +1120,7 @@ impl Type {
|
|||
} => actual_type.contains_variable(rep_variable),
|
||||
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
|
||||
RangedNumber(typ, _) => typ.contains_variable(rep_variable),
|
||||
RangedNumber(_) => false,
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) => false,
|
||||
}
|
||||
}
|
||||
|
@ -1386,9 +1383,7 @@ impl Type {
|
|||
}
|
||||
}
|
||||
}
|
||||
RangedNumber(typ, _) => {
|
||||
typ.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet(..) => {}
|
||||
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
|
||||
}
|
||||
|
@ -1524,9 +1519,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
|
|||
Erroneous(Problem::CyclicAlias(alias, _, _)) => {
|
||||
output.push(*alias);
|
||||
}
|
||||
RangedNumber(typ, _) => {
|
||||
stack.push(typ);
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet(Uls(_, _sym, _)) => {
|
||||
// ignore the member symbol because unspecialized lambda sets are internal-only
|
||||
}
|
||||
|
@ -1646,9 +1639,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
|||
}
|
||||
variables_help(actual, accum);
|
||||
}
|
||||
RangedNumber(typ, _) => {
|
||||
variables_help(typ, accum);
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
|
@ -1789,9 +1780,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
|||
}
|
||||
variables_help_detailed(actual, accum);
|
||||
}
|
||||
RangedNumber(typ, _) => {
|
||||
variables_help_detailed(typ, accum);
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help_detailed(x, accum);
|
||||
|
@ -2088,7 +2077,7 @@ pub enum ErrorType {
|
|||
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
Function(Vec<ErrorType>, Box<ErrorType>, Box<ErrorType>),
|
||||
Alias(Symbol, Vec<ErrorType>, Box<ErrorType>, AliasKind),
|
||||
Range(Box<ErrorType>, Vec<ErrorType>),
|
||||
Range(Vec<ErrorType>),
|
||||
Error,
|
||||
}
|
||||
|
||||
|
@ -2144,8 +2133,7 @@ impl ErrorType {
|
|||
});
|
||||
t.add_names(taken);
|
||||
}
|
||||
Range(typ, ts) => {
|
||||
typ.add_names(taken);
|
||||
Range(ts) => {
|
||||
ts.iter().for_each(|t| {
|
||||
t.add_names(taken);
|
||||
});
|
||||
|
@ -2299,7 +2287,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
buf.push('(');
|
||||
}
|
||||
buf.push_str(name.as_str());
|
||||
buf.push_str(&format!(" has {:?}", symbol));
|
||||
write!(buf, "has {:?}", symbol).unwrap();
|
||||
if write_parens {
|
||||
buf.push(')');
|
||||
}
|
||||
|
@ -2310,7 +2298,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
if write_parens {
|
||||
buf.push('(');
|
||||
}
|
||||
buf.push_str(&format!("{:?}", symbol));
|
||||
write!(buf, "{:?}", symbol).unwrap();
|
||||
|
||||
for arg in arguments {
|
||||
buf.push(' ');
|
||||
|
@ -2355,7 +2343,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
if write_parens {
|
||||
buf.push('(');
|
||||
}
|
||||
buf.push_str(&format!("{:?}", symbol));
|
||||
write!(buf, "{:?}", symbol).unwrap();
|
||||
|
||||
for arg in arguments {
|
||||
buf.push(' ');
|
||||
|
@ -2434,7 +2422,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
let mut it = tags.into_iter().peekable();
|
||||
|
||||
while let Some((tag, args)) = it.next() {
|
||||
buf.push_str(&format!("{:?}", tag));
|
||||
write!(buf, "{:?}", tag).unwrap();
|
||||
for arg in args {
|
||||
buf.push(' ');
|
||||
write_debug_error_type_help(arg, buf, Parens::InTypeParam);
|
||||
|
@ -2453,7 +2441,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
|
||||
let mut it = tags.into_iter().peekable();
|
||||
while let Some((tag, args)) = it.next() {
|
||||
buf.push_str(&format!("{:?}", tag));
|
||||
write!(buf, "{:?}", tag).unwrap();
|
||||
for arg in args {
|
||||
buf.push(' ');
|
||||
write_debug_error_type_help(arg, buf, Parens::Unnecessary);
|
||||
|
@ -2471,8 +2459,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
|
||||
write_debug_error_type_help(*rec, buf, Parens::Unnecessary);
|
||||
}
|
||||
Range(typ, types) => {
|
||||
write_debug_error_type_help(*typ, buf, parens);
|
||||
Range(types) => {
|
||||
buf.push('<');
|
||||
|
||||
let mut it = types.into_iter().peekable();
|
||||
|
@ -2824,9 +2811,7 @@ fn instantiate_lambda_sets_as_unspecialized(
|
|||
stack.extend(args.iter_mut().rev());
|
||||
}
|
||||
Type::Variable(_) => {}
|
||||
Type::RangedNumber(t, _) => {
|
||||
stack.push(t);
|
||||
}
|
||||
Type::RangedNumber(_) => {}
|
||||
Type::Erroneous(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use roc_debug_flags::{ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS};
|
|||
use roc_error_macros::internal_error;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::num::NumericRange;
|
||||
use roc_types::num::{FloatWidth, IntLitWidth, NumericRange};
|
||||
use roc_types::subs::Content::{self, *};
|
||||
use roc_types::subs::{
|
||||
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, LambdaSet, Mark,
|
||||
|
@ -475,7 +475,7 @@ fn unify_context<M: MetaCollector>(subs: &mut Subs, pool: &mut Pool, ctx: Contex
|
|||
unify_opaque(subs, pool, &ctx, *symbol, *args, *real_var)
|
||||
}
|
||||
LambdaSet(lset) => unify_lambda_set(subs, pool, &ctx, *lset, &ctx.second_desc.content),
|
||||
&RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars),
|
||||
&RangedNumber(range_vars) => unify_ranged_number(subs, pool, &ctx, range_vars),
|
||||
Error => {
|
||||
// Error propagates. Whatever we're comparing it to doesn't matter!
|
||||
merge(subs, &ctx, Error)
|
||||
|
@ -488,85 +488,171 @@ fn unify_context<M: MetaCollector>(subs: &mut Subs, pool: &mut Pool, ctx: Contex
|
|||
result
|
||||
}
|
||||
|
||||
fn not_in_range_mismatch<M: MetaCollector>() -> Outcome<M> {
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeNotInRange],
|
||||
must_implement_ability: Default::default(),
|
||||
lambda_sets_to_specialize: Default::default(),
|
||||
extra_metadata: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unify_ranged_number<M: MetaCollector>(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
real_var: Variable,
|
||||
range_vars: NumericRange,
|
||||
) -> Outcome<M> {
|
||||
let other_content = &ctx.second_desc.content;
|
||||
|
||||
let outcome = match other_content {
|
||||
match other_content {
|
||||
FlexVar(_) => {
|
||||
// Ranged number wins
|
||||
merge(subs, ctx, RangedNumber(real_var, range_vars))
|
||||
merge(subs, ctx, RangedNumber(range_vars))
|
||||
}
|
||||
RecursionVar { .. }
|
||||
| RigidVar(..)
|
||||
| Alias(..)
|
||||
| Structure(..)
|
||||
| RigidAbleVar(..)
|
||||
| FlexAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
&RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode);
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, ctx.first, other_range_vars)
|
||||
} else {
|
||||
outcome
|
||||
}
|
||||
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect
|
||||
RigidVar(name) => {
|
||||
// Int a vs Int <range>, the rigid wins
|
||||
merge(subs, ctx, RigidVar(*name))
|
||||
}
|
||||
RecursionVar { .. } | Alias(..) | Structure(..) | RigidAbleVar(..) | FlexAbleVar(..) => {
|
||||
check_and_merge_valid_range(subs, pool, ctx, ctx.first, range_vars, ctx.second)
|
||||
}
|
||||
&RangedNumber(other_range_vars) => match range_vars.intersection(&other_range_vars) {
|
||||
Some(range) => merge(subs, ctx, RangedNumber(range)),
|
||||
None => not_in_range_mismatch(),
|
||||
},
|
||||
LambdaSet(..) => mismatch!(),
|
||||
Error => merge(subs, ctx, Error),
|
||||
};
|
||||
|
||||
if !outcome.mismatches.is_empty() {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
check_valid_range(subs, ctx.second, range_vars)
|
||||
}
|
||||
|
||||
fn check_valid_range<M: MetaCollector>(
|
||||
fn check_and_merge_valid_range<M: MetaCollector>(
|
||||
subs: &mut Subs,
|
||||
var: Variable,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
range_var: Variable,
|
||||
range: NumericRange,
|
||||
var: Variable,
|
||||
) -> Outcome<M> {
|
||||
let content = subs.get_content_without_compacting(var);
|
||||
use Content::*;
|
||||
let content = *subs.get_content_without_compacting(var);
|
||||
|
||||
match content {
|
||||
&Content::Alias(symbol, _, actual, _) => {
|
||||
match range.contains_symbol(symbol) {
|
||||
None => {
|
||||
// symbol not recognized; go into the alias
|
||||
return check_valid_range(subs, actual, range);
|
||||
}
|
||||
Some(false) => {
|
||||
let outcome = Outcome {
|
||||
mismatches: vec![Mismatch::TypeNotInRange],
|
||||
must_implement_ability: Default::default(),
|
||||
lambda_sets_to_specialize: Default::default(),
|
||||
extra_metadata: Default::default(),
|
||||
};
|
||||
|
||||
return outcome;
|
||||
}
|
||||
Some(true) => { /* fall through */ }
|
||||
macro_rules! merge_if {
|
||||
($cond:expr) => {
|
||||
if $cond {
|
||||
merge(subs, ctx, content)
|
||||
} else {
|
||||
not_in_range_mismatch()
|
||||
}
|
||||
}
|
||||
|
||||
Content::RangedNumber(_, _) => {
|
||||
// these ranges always intersect, we need more information before we can say more
|
||||
}
|
||||
|
||||
_ => {
|
||||
// anything else is definitely a type error, and will be reported elsewhere
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Outcome::default()
|
||||
match content {
|
||||
RangedNumber(other_range) => match range.intersection(&other_range) {
|
||||
Some(r) => {
|
||||
if r == range {
|
||||
merge(subs, ctx, RangedNumber(range))
|
||||
} else {
|
||||
merge(subs, ctx, RangedNumber(other_range))
|
||||
}
|
||||
}
|
||||
None => not_in_range_mismatch(),
|
||||
},
|
||||
Alias(symbol, args, _real_var, kind) => match symbol {
|
||||
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::I8))
|
||||
}
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::U8))
|
||||
}
|
||||
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::I16))
|
||||
}
|
||||
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::U16))
|
||||
}
|
||||
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::I32))
|
||||
}
|
||||
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::U32))
|
||||
}
|
||||
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::I64))
|
||||
}
|
||||
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::Nat))
|
||||
}
|
||||
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::U64))
|
||||
}
|
||||
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::I128))
|
||||
}
|
||||
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => {
|
||||
merge_if!(range.contains_int_width(IntLitWidth::U128))
|
||||
}
|
||||
|
||||
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => {
|
||||
merge_if!(range.contains_float_width(FloatWidth::Dec))
|
||||
}
|
||||
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => {
|
||||
merge_if!(range.contains_float_width(FloatWidth::F32))
|
||||
}
|
||||
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => {
|
||||
merge_if!(range.contains_float_width(FloatWidth::F64))
|
||||
}
|
||||
Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT => match range {
|
||||
NumericRange::IntAtLeastSigned(_) | NumericRange::IntAtLeastEitherSign(_) => {
|
||||
mismatch!()
|
||||
}
|
||||
NumericRange::NumAtLeastSigned(_) | NumericRange::NumAtLeastEitherSign(_) => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
let arg = subs.get_subs_slice(args.all_variables())[0];
|
||||
let new_range_var = wrap_range_var(subs, symbol, range_var, kind);
|
||||
unify_pool(subs, pool, new_range_var, arg, ctx.mode)
|
||||
}
|
||||
},
|
||||
Symbol::NUM_NUM => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
let arg = subs.get_subs_slice(args.all_variables())[0];
|
||||
let new_range_var = wrap_range_var(subs, symbol, range_var, kind);
|
||||
unify_pool(subs, pool, new_range_var, arg, ctx.mode)
|
||||
}
|
||||
Symbol::NUM_INT | Symbol::NUM_INTEGER => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
let arg = subs.get_subs_slice(args.all_variables())[0];
|
||||
let new_range_var = wrap_range_var(subs, symbol, range_var, kind);
|
||||
unify_pool(subs, pool, new_range_var, arg, ctx.mode)
|
||||
}
|
||||
|
||||
_ => mismatch!(),
|
||||
},
|
||||
|
||||
_ => mismatch!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a number range var down into a number type, so as to preserve type hierarchy structure.
|
||||
/// For example when we have Num (Int a) ~ Num (NumericRange <U128>), we want to produce
|
||||
/// Num (Int (NumericRange <U128>))
|
||||
/// on the right (which this function does) and then unify
|
||||
/// Num (Int a) ~ Num (Int (NumericRange <U128>))
|
||||
fn wrap_range_var(
|
||||
subs: &mut Subs,
|
||||
symbol: Symbol,
|
||||
range_var: Variable,
|
||||
alias_kind: AliasKind,
|
||||
) -> Variable {
|
||||
let range_desc = subs.get(range_var);
|
||||
let new_range_var = subs.fresh(range_desc);
|
||||
let var_slice = AliasVariables::insert_into_subs(subs, [new_range_var], []);
|
||||
subs.set_content(
|
||||
range_var,
|
||||
Alias(symbol, var_slice, new_range_var, alias_kind),
|
||||
);
|
||||
new_range_var
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -660,13 +746,8 @@ fn unify_alias<M: MetaCollector>(
|
|||
}
|
||||
}
|
||||
Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode);
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, real_var, *other_range_vars)
|
||||
} else {
|
||||
outcome
|
||||
}
|
||||
RangedNumber(other_range_vars) => {
|
||||
check_and_merge_valid_range(subs, pool, ctx, ctx.second, *other_range_vars, ctx.first)
|
||||
}
|
||||
LambdaSet(..) => mismatch!("cannot unify alias {:?} with lambda set {:?}: lambda sets should never be directly behind an alias!", ctx.first, other_content),
|
||||
Error => merge(subs, ctx, Error),
|
||||
|
@ -723,15 +804,11 @@ fn unify_opaque<M: MetaCollector>(
|
|||
mismatch!("{:?}", symbol)
|
||||
}
|
||||
}
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
RangedNumber(other_range_vars) => {
|
||||
// This opaque might be a number, check if it unifies with the target ranged number var.
|
||||
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, ctx.first, *other_range_vars)
|
||||
} else {
|
||||
outcome
|
||||
}
|
||||
check_and_merge_valid_range(subs, pool, ctx, ctx.second, *other_range_vars, ctx.first)
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
// _other has an underscore because it's unused in --release builds
|
||||
_other => {
|
||||
// The type on the left is an opaque, but the one on the right is not!
|
||||
|
@ -866,13 +943,8 @@ fn unify_structure<M: MetaCollector>(
|
|||
// other
|
||||
)
|
||||
}
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, ctx.first, *other_range_vars)
|
||||
} else {
|
||||
outcome
|
||||
}
|
||||
RangedNumber(other_range_vars) => {
|
||||
check_and_merge_valid_range(subs, pool, ctx, ctx.second, *other_range_vars, ctx.first)
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
}
|
||||
|
@ -2247,13 +2319,16 @@ fn unify_rigid<M: MetaCollector>(
|
|||
"Rigid {:?} with FlexAble {:?}", ctx.first, other
|
||||
)
|
||||
}
|
||||
RangedNumber(..) => {
|
||||
// Int a vs Int <range>, the rigid wins
|
||||
merge(subs, ctx, RigidVar(*name))
|
||||
}
|
||||
|
||||
RigidVar(_)
|
||||
| RigidAbleVar(..)
|
||||
| RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(..)
|
||||
| RangedNumber(..)
|
||||
| LambdaSet(..) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue