mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-02 11:22:19 +00:00
Merge branch 'main' into luke-windows
This commit is contained in:
commit
2955b00bb5
25 changed files with 1651 additions and 1067 deletions
2
.github/workflows/nightly_linux_arm64.yml
vendored
2
.github/workflows/nightly_linux_arm64.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
|||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build release with lto
|
||||
run: cargo build --profile=release-with-lto --locked --bin roc
|
||||
run: cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
|
2
.github/workflows/nightly_linux_x86_64.yml
vendored
2
.github/workflows/nightly_linux_x86_64.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
|||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build release with lto
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. This was also faster in our tests: https://roc.zulipchat.com/#narrow/stream/231635-compiler-development/topic/.2Ecargo.2Fconfig.2Etoml/near/325726299
|
||||
|
||||
- name: get commit SHA
|
||||
|
|
|
@ -42,11 +42,7 @@ jobs:
|
|||
run: ./ci/write_version.sh
|
||||
|
||||
- name: build nightly release
|
||||
run: cargo build --locked --profile=release-with-lto --bin roc
|
||||
|
||||
# this makes the roc binary a lot smaller
|
||||
- name: strip debug info
|
||||
run: strip ./target/release-with-lto/roc
|
||||
run: cargo build --locked --profile=release-with-lto --bin roc --bin roc_ls
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
|
||||
|
|
2
.github/workflows/nightly_macos_x86_64.yml
vendored
2
.github/workflows/nightly_macos_x86_64.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
|||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- name: build release
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc
|
||||
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
|
||||
# target-cpu=x86-64 -> For maximal compatibility for all CPU's.
|
||||
|
||||
- name: get commit SHA
|
||||
|
|
|
@ -32,8 +32,9 @@ jobs:
|
|||
- name: test building default.nix
|
||||
run: nix-build
|
||||
|
||||
# for skipped tests: see issue 6274
|
||||
- name: execute tests with --release
|
||||
run: nix develop -c cargo test --locked --release
|
||||
run: nix develop -c cargo test --locked --release -- --skip cli_run::inspect_gui --skip cli_run::hello_gui
|
||||
|
||||
- name: make a libapp.so for the next step
|
||||
run: nix develop -c cargo run -- gen-stub-lib examples/platform-switching/rocLovesRust.roc
|
||||
|
|
|
@ -53,9 +53,7 @@ build-nightly-release:
|
|||
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
|
||||
# version.txt is used by the CLI: roc --version
|
||||
RUN ./ci/write_version.sh
|
||||
RUN RUSTFLAGS=$RUSTFLAGS cargo build --profile=release-with-lto --locked --bin roc
|
||||
# strip debug info
|
||||
RUN strip ./target/release-with-lto/roc
|
||||
RUN RUSTFLAGS=$RUSTFLAGS cargo build --profile=release-with-lto --locked --bin roc --bin roc_ls
|
||||
RUN ./ci/package_release.sh $RELEASE_FOLDER_NAME
|
||||
RUN ls
|
||||
SAVE ARTIFACT ./$RELEASE_FOLDER_NAME.tar.gz AS LOCAL $RELEASE_FOLDER_NAME.tar.gz
|
||||
|
|
|
@ -3,15 +3,21 @@
|
|||
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
|
||||
set -euxo pipefail
|
||||
|
||||
cp target/release-with-lto/roc ./roc # to be able to delete "target" later
|
||||
# this makes the binaries a lot smaller
|
||||
strip ./target/release-with-lto/roc
|
||||
strip ./target/release-with-lto/roc_ls
|
||||
|
||||
# to be able to delete "target" later
|
||||
cp target/release-with-lto/roc ./roc
|
||||
cp target/release-with-lto/roc_ls ./roc_lang_server
|
||||
|
||||
# delete unnecessary files and folders
|
||||
git clean -fdx --exclude roc
|
||||
git clean -fdx --exclude roc --exclude roc_lang_server
|
||||
|
||||
mkdir $1
|
||||
|
||||
|
||||
mv roc LICENSE LEGAL_DETAILS $1
|
||||
mv roc roc_lang_server LICENSE LEGAL_DETAILS $1
|
||||
|
||||
mkdir $1/examples
|
||||
mv examples/helloWorld.roc examples/platform-switching examples/cli $1/examples
|
||||
|
|
|
@ -538,20 +538,39 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
|
|||
Err KeyNotFound ->
|
||||
when alter Missing is
|
||||
Present newValue ->
|
||||
if maxBucketCapacity == 0 then
|
||||
if List.len data >= (Num.toNat maxBucketCapacity) then
|
||||
# Need to reallocate let regular insert handle that.
|
||||
insert (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key newValue
|
||||
else
|
||||
# Can skip work by jumping staight to the found bucket.
|
||||
# That will be the location we want to insert in.
|
||||
hash = hashKey key
|
||||
distAndFingerprint = distAndFingerprintFromHash hash
|
||||
baseDistAndFingerprint = distAndFingerprintFromHash hash
|
||||
baseBucketIndex = bucketIndexFromHash hash shifts
|
||||
|
||||
insertHelper buckets data bucketIndex distAndFingerprint key newValue maxBucketCapacity maxLoadFactor shifts
|
||||
# Due to the unrolling of loops in find along with loop optimizations,
|
||||
# The bucketIndex is not guaranteed to be correct here.
|
||||
# It is only correct if we have traversed past the number of find unrolls.
|
||||
dist = circularDist baseBucketIndex bucketIndex (List.len buckets)
|
||||
if dist <= findManualUnrolls then
|
||||
insertHelper buckets data baseBucketIndex baseDistAndFingerprint key newValue maxBucketCapacity maxLoadFactor shifts
|
||||
else
|
||||
distAndFingerprint = incrementDistN baseDistAndFingerprint (Num.toU32 dist)
|
||||
insertHelper buckets data bucketIndex distAndFingerprint key newValue maxBucketCapacity maxLoadFactor shifts
|
||||
|
||||
Missing ->
|
||||
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
|
||||
|
||||
circularDist = \start, end, size ->
|
||||
correction =
|
||||
if start > end then
|
||||
size
|
||||
else
|
||||
0
|
||||
end
|
||||
|> Num.subWrap start
|
||||
|> Num.addWrap correction
|
||||
|
||||
## Returns the keys and values of a dictionary as a [List].
|
||||
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
|
@ -709,21 +728,26 @@ maxBucketCount = maxSize
|
|||
incrementDist = \distAndFingerprint ->
|
||||
distAndFingerprint + distInc
|
||||
|
||||
incrementDistN = \distAndFingerprint, n ->
|
||||
distAndFingerprint + (n * distInc)
|
||||
|
||||
decrementDist = \distAndFingerprint ->
|
||||
distAndFingerprint - distInc
|
||||
|
||||
find : Dict k v, k -> { bucketIndex : Nat, result : Result v [KeyNotFound] }
|
||||
find = \@Dict { buckets, data, shifts }, key ->
|
||||
if !(List.isEmpty data) then
|
||||
hash = hashKey key
|
||||
distAndFingerprint = distAndFingerprintFromHash hash
|
||||
bucketIndex = bucketIndexFromHash hash shifts
|
||||
hash = hashKey key
|
||||
distAndFingerprint = distAndFingerprintFromHash hash
|
||||
bucketIndex = bucketIndexFromHash hash shifts
|
||||
|
||||
if !(List.isEmpty data) then
|
||||
# TODO: this is true in the C++ code, confirm it in Roc as well.
|
||||
# unrolled loop. *Always* check a few directly, then enter the loop. This is faster.
|
||||
findFirstUnroll buckets bucketIndex distAndFingerprint data key
|
||||
else
|
||||
{ bucketIndex: 0, result: Err KeyNotFound }
|
||||
{ bucketIndex, result: Err KeyNotFound }
|
||||
|
||||
findManualUnrolls = 2
|
||||
|
||||
findFirstUnroll : List Bucket, Nat, U32, List (k, v), k -> { bucketIndex : Nat, result : Result v [KeyNotFound] } where k implements Eq
|
||||
findFirstUnroll = \buckets, bucketIndex, distAndFingerprint, data, key ->
|
||||
|
@ -890,16 +914,16 @@ nextWhileLessHelper = \buckets, bucketIndex, distAndFingerprint ->
|
|||
else
|
||||
(bucketIndex, distAndFingerprint)
|
||||
|
||||
placeAndShiftUp = \buckets0, bucket0, bucketIndex ->
|
||||
placeAndShiftUp = \buckets0, bucket, bucketIndex ->
|
||||
loaded = listGetUnsafe buckets0 (Num.toNat bucketIndex)
|
||||
if loaded.distAndFingerprint != 0 then
|
||||
{ list: buckets1, value: bucket1 } = List.replace buckets0 (Num.toNat bucketIndex) bucket0
|
||||
buckets1 = List.set buckets0 (Num.toNat bucketIndex) bucket
|
||||
placeAndShiftUp
|
||||
buckets1
|
||||
{ bucket1 & distAndFingerprint: incrementDist bucket1.distAndFingerprint }
|
||||
{ loaded & distAndFingerprint: incrementDist loaded.distAndFingerprint }
|
||||
(nextBucketIndex bucketIndex (List.len buckets1))
|
||||
else
|
||||
List.set buckets0 (Num.toNat bucketIndex) bucket0
|
||||
List.set buckets0 (Num.toNat bucketIndex) bucket
|
||||
|
||||
nextBucketIndex = \bucketIndex, maxBuckets ->
|
||||
# I just ported this impl directly.
|
||||
|
@ -1168,6 +1192,48 @@ expect
|
|||
|> get 7
|
||||
|> Bool.isEq (Ok "Testing")
|
||||
|
||||
# All BadKey's hash to the same location.
|
||||
# This is needed to test some robinhood logic.
|
||||
BadKey := U64 implements [
|
||||
Eq,
|
||||
Hash {
|
||||
hash: hashBadKey,
|
||||
},
|
||||
]
|
||||
|
||||
hashBadKey : hasher, BadKey -> hasher where hasher implements Hasher
|
||||
hashBadKey = \hasher, _ -> Hash.hash hasher 0
|
||||
|
||||
expect
|
||||
badKeys = [
|
||||
@BadKey 0,
|
||||
@BadKey 1,
|
||||
@BadKey 2,
|
||||
@BadKey 3,
|
||||
@BadKey 4,
|
||||
@BadKey 5,
|
||||
@BadKey 6,
|
||||
@BadKey 5,
|
||||
@BadKey 4,
|
||||
@BadKey 3,
|
||||
@BadKey 3,
|
||||
@BadKey 3,
|
||||
@BadKey 10,
|
||||
]
|
||||
|
||||
dict =
|
||||
acc, k <- List.walk badKeys (Dict.empty {})
|
||||
Dict.update acc k \val ->
|
||||
when val is
|
||||
Present p -> Present (p + 1)
|
||||
Missing -> Present 0
|
||||
|
||||
allInsertedCorrectly =
|
||||
acc, k <- List.walk badKeys Bool.true
|
||||
acc && Dict.contains dict k
|
||||
|
||||
allInsertedCorrectly
|
||||
|
||||
# Note, there are a number of places we should probably use set and replace unsafe.
|
||||
# unsafe primitive that does not perform a bounds check
|
||||
listGetUnsafe : List a, Nat -> a
|
||||
|
|
|
@ -560,8 +560,6 @@ tau = 2 * pi
|
|||
# ------- Functions
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
## This is the same as calling `Num.format {}` - so for more details on
|
||||
## exact formatting, see `Num.format`.
|
||||
## ```
|
||||
## Num.toStr 42
|
||||
## ```
|
||||
|
@ -573,7 +571,6 @@ tau = 2 * pi
|
|||
## When this function is given a non-[finite](Num.isFinite)
|
||||
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
|
||||
##
|
||||
## To get strings in hexadecimal, octal, or binary format, use `Num.format`.
|
||||
toStr : Num * -> Str
|
||||
intCast : Int a -> Int b
|
||||
|
||||
|
|
|
@ -710,7 +710,7 @@ impl LlvmBackendMode {
|
|||
match self {
|
||||
LlvmBackendMode::Binary => false,
|
||||
LlvmBackendMode::BinaryDev => false,
|
||||
LlvmBackendMode::BinaryGlue => false,
|
||||
LlvmBackendMode::BinaryGlue => true,
|
||||
LlvmBackendMode::GenTest => true,
|
||||
LlvmBackendMode::WasmGenTest => true,
|
||||
LlvmBackendMode::CliTest => true,
|
||||
|
@ -1060,9 +1060,9 @@ pub fn module_from_builtins<'ctx>(
|
|||
|
||||
// In testing, this adds about 20ms extra to compilation.
|
||||
// Long term it would be best if we could do this on the zig side.
|
||||
// This change enables us to dce all the parts of compiler-rt we don't use.
|
||||
// That said, it would be better to dce them before roc app compiltation time.
|
||||
// Anything not depended on by a `roc_builtin.` function could alread by DCE'd theoretically.
|
||||
// The core issue is that we have to properly labael certain functions as private and DCE them.
|
||||
// Otherwise, now that zig bundles all of compiler-rt, we would optimize and compile the entire library.
|
||||
// Anything not depended on by a `roc_builtin.` function could already by DCE'd theoretically.
|
||||
// That said, this workaround is good enough and fixes compilations times.
|
||||
|
||||
// Also, must_keep is the functions we depend on that would normally be provide by libc.
|
||||
|
@ -1072,6 +1072,11 @@ pub fn module_from_builtins<'ctx>(
|
|||
"floorf",
|
||||
"memcpy",
|
||||
"memset",
|
||||
// I have no idea why this function is special.
|
||||
// Without it, some tests hang on M1 mac outside of nix.
|
||||
"__muloti4",
|
||||
// fixes `Undefined Symbol in relocation`
|
||||
"__udivti3",
|
||||
// Roc special functions
|
||||
"__roc_force_longjmp",
|
||||
"__roc_force_setjmp",
|
||||
|
@ -4463,31 +4468,68 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
|
|||
}
|
||||
}
|
||||
|
||||
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||
|
||||
let call_result = if env.mode.returns_roc_result() {
|
||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||
if args.len() == roc_function.get_params().len() {
|
||||
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||
|
||||
let dbg_loc = builder.get_current_debug_location().unwrap();
|
||||
let roc_wrapper_function =
|
||||
make_exception_catcher(env, layout_interner, roc_function, return_layout);
|
||||
debug_assert_eq!(
|
||||
arguments_for_call.len(),
|
||||
roc_wrapper_function.get_params().len()
|
||||
);
|
||||
let dbg_loc = builder.get_current_debug_location().unwrap();
|
||||
let roc_wrapper_function =
|
||||
make_exception_catcher(env, layout_interner, roc_function, return_layout);
|
||||
debug_assert_eq!(
|
||||
arguments_for_call.len(),
|
||||
roc_wrapper_function.get_params().len()
|
||||
);
|
||||
|
||||
builder.position_at_end(entry);
|
||||
builder.set_current_debug_location(dbg_loc);
|
||||
builder.position_at_end(entry);
|
||||
builder.set_current_debug_location(dbg_loc);
|
||||
|
||||
let wrapped_layout = roc_call_result_layout(env.arena, return_layout);
|
||||
call_direct_roc_function(
|
||||
env,
|
||||
layout_interner,
|
||||
roc_function,
|
||||
wrapped_layout,
|
||||
arguments_for_call,
|
||||
)
|
||||
let wrapped_layout = roc_call_result_layout(env.arena, return_layout);
|
||||
call_direct_roc_function(
|
||||
env,
|
||||
layout_interner,
|
||||
roc_function,
|
||||
wrapped_layout,
|
||||
arguments_for_call,
|
||||
)
|
||||
} else {
|
||||
debug_assert_eq!(args.len() + 1, roc_function.get_params().len());
|
||||
|
||||
arguments_for_call.push(args[0]);
|
||||
|
||||
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||
|
||||
let dbg_loc = builder.get_current_debug_location().unwrap();
|
||||
let roc_wrapper_function =
|
||||
make_exception_catcher(env, layout_interner, roc_function, return_layout);
|
||||
|
||||
builder.position_at_end(entry);
|
||||
builder.set_current_debug_location(dbg_loc);
|
||||
|
||||
let wrapped_layout = roc_call_result_layout(env.arena, return_layout);
|
||||
let call_result = call_direct_roc_function(
|
||||
env,
|
||||
layout_interner,
|
||||
roc_wrapper_function,
|
||||
wrapped_layout,
|
||||
arguments_for_call,
|
||||
);
|
||||
|
||||
let output_arg_index = 0;
|
||||
|
||||
let output_arg = c_function
|
||||
.get_nth_param(output_arg_index as u32)
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
env.builder.new_build_store(output_arg, call_result);
|
||||
|
||||
builder.new_build_return(None);
|
||||
|
||||
return c_function;
|
||||
}
|
||||
} else {
|
||||
let arguments_for_call = &arguments_for_call.into_bump_slice();
|
||||
|
||||
call_direct_roc_function(
|
||||
env,
|
||||
layout_interner,
|
||||
|
@ -4511,6 +4553,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>(
|
|||
output_arg,
|
||||
call_result,
|
||||
);
|
||||
|
||||
builder.new_build_return(None);
|
||||
|
||||
c_function
|
||||
|
|
|
@ -80,10 +80,6 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
|
|||
let i32_type = ctx.i32_type();
|
||||
let void_type = ctx.void_type();
|
||||
|
||||
if let Some(func) = module.get_function("__muloti4") {
|
||||
func.set_linkage(Linkage::WeakAny);
|
||||
}
|
||||
|
||||
add_intrinsic(
|
||||
ctx,
|
||||
module,
|
||||
|
|
|
@ -684,6 +684,7 @@ impl MakeSpecializationsPass {
|
|||
struct State<'a> {
|
||||
pub root_id: ModuleId,
|
||||
pub root_subs: Option<Subs>,
|
||||
pub root_path: PathBuf,
|
||||
pub cache_dir: PathBuf,
|
||||
/// If the root is an app module, the shorthand specified in its header's `to` field
|
||||
pub opt_platform_shorthand: Option<&'a str>,
|
||||
|
@ -752,6 +753,7 @@ impl<'a> State<'a> {
|
|||
|
||||
fn new(
|
||||
root_id: ModuleId,
|
||||
root_path: PathBuf,
|
||||
opt_platform_shorthand: Option<&'a str>,
|
||||
target_info: TargetInfo,
|
||||
function_kind: FunctionKind,
|
||||
|
@ -770,6 +772,7 @@ impl<'a> State<'a> {
|
|||
|
||||
Self {
|
||||
root_id,
|
||||
root_path,
|
||||
root_subs: None,
|
||||
opt_platform_shorthand,
|
||||
cache_dir,
|
||||
|
@ -1077,8 +1080,9 @@ pub struct LoadStart<'a> {
|
|||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
root_id: ModuleId,
|
||||
opt_platform_shorthand: Option<&'a str>,
|
||||
root_path: PathBuf,
|
||||
root_msg: Msg<'a>,
|
||||
opt_platform_shorthand: Option<&'a str>,
|
||||
src_dir: PathBuf,
|
||||
}
|
||||
|
||||
|
@ -1101,7 +1105,7 @@ impl<'a> LoadStart<'a> {
|
|||
|
||||
let res_loaded = load_filename(
|
||||
arena,
|
||||
filename,
|
||||
filename.clone(),
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
|
@ -1136,6 +1140,7 @@ impl<'a> LoadStart<'a> {
|
|||
ident_ids_by_module,
|
||||
src_dir,
|
||||
root_id: header_output.module_id,
|
||||
root_path: filename,
|
||||
root_msg: header_output.msg,
|
||||
opt_platform_shorthand: header_output.opt_platform_shorthand,
|
||||
})
|
||||
|
@ -1162,7 +1167,7 @@ impl<'a> LoadStart<'a> {
|
|||
|
||||
let header_output = load_from_str(
|
||||
arena,
|
||||
filename,
|
||||
filename.clone(),
|
||||
src,
|
||||
Arc::clone(&arc_modules),
|
||||
Arc::clone(&ident_ids_by_module),
|
||||
|
@ -1178,6 +1183,7 @@ impl<'a> LoadStart<'a> {
|
|||
src_dir,
|
||||
ident_ids_by_module,
|
||||
root_id,
|
||||
root_path: filename,
|
||||
root_msg,
|
||||
opt_platform_shorthand: opt_platform_id,
|
||||
})
|
||||
|
@ -1352,6 +1358,7 @@ pub fn load_single_threaded<'a>(
|
|||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
root_id,
|
||||
root_path,
|
||||
root_msg,
|
||||
src_dir,
|
||||
opt_platform_shorthand,
|
||||
|
@ -1367,6 +1374,7 @@ pub fn load_single_threaded<'a>(
|
|||
let number_of_workers = 1;
|
||||
let mut state = State::new(
|
||||
root_id,
|
||||
root_path,
|
||||
opt_platform_shorthand,
|
||||
target_info,
|
||||
function_kind,
|
||||
|
@ -1503,7 +1511,7 @@ fn state_thread_step<'a>(
|
|||
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
|
||||
}
|
||||
Msg::FailedToReadFile { filename, error } => {
|
||||
let buf = to_file_problem_report_string(&filename, error);
|
||||
let buf = to_file_problem_report_string(filename, error);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
|
||||
|
@ -1654,7 +1662,7 @@ pub fn report_loading_problem(
|
|||
}
|
||||
LoadingProblem::FormattedReport(report) => report,
|
||||
LoadingProblem::FileProblem { filename, error } => {
|
||||
to_file_problem_report_string(&filename, error)
|
||||
to_file_problem_report_string(filename, error)
|
||||
}
|
||||
err => todo!("Loading error: {:?}", err),
|
||||
}
|
||||
|
@ -1677,6 +1685,7 @@ fn load_multi_threaded<'a>(
|
|||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
root_id,
|
||||
root_path,
|
||||
root_msg,
|
||||
src_dir,
|
||||
opt_platform_shorthand,
|
||||
|
@ -1707,6 +1716,7 @@ fn load_multi_threaded<'a>(
|
|||
|
||||
let mut state = State::new(
|
||||
root_id,
|
||||
root_path,
|
||||
opt_platform_shorthand,
|
||||
target_info,
|
||||
function_kind,
|
||||
|
@ -2238,6 +2248,7 @@ fn update<'a>(
|
|||
let buf = to_https_problem_report_string(
|
||||
url,
|
||||
Problem::InvalidUrl(url_err),
|
||||
header.module_path,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
|
@ -3159,7 +3170,7 @@ fn finish_specialization<'a>(
|
|||
}
|
||||
Valid(To::NewPackage(p_or_p)) => PathBuf::from(p_or_p.as_str()),
|
||||
other => {
|
||||
let buf = to_missing_platform_report(state.root_id, other);
|
||||
let buf = report_cannot_run(state.root_id, state.root_path, other);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
};
|
||||
|
@ -3513,9 +3524,7 @@ fn load_builtin_module_help<'a>(
|
|||
) -> (HeaderInfo<'a>, roc_parse::state::State<'a>) {
|
||||
let is_root_module = false;
|
||||
let opt_shorthand = None;
|
||||
|
||||
let filename = PathBuf::from(filename);
|
||||
|
||||
let parse_state = roc_parse::state::State::new(src_bytes.as_bytes());
|
||||
let parsed = roc_parse::module::parse_header(arena, parse_state.clone());
|
||||
|
||||
|
@ -3968,6 +3977,7 @@ fn parse_header<'a>(
|
|||
module_timing,
|
||||
)?;
|
||||
|
||||
let filename = resolved_header.module_path.clone();
|
||||
let mut messages = Vec::with_capacity(packages.len() + 1);
|
||||
|
||||
// It's important that the app header is first in the list!
|
||||
|
@ -3982,6 +3992,7 @@ fn parse_header<'a>(
|
|||
module_id,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
filename,
|
||||
);
|
||||
|
||||
// Look at the app module's `to` keyword to determine which package was the platform.
|
||||
|
@ -4084,6 +4095,7 @@ fn load_packages<'a>(
|
|||
module_id: ModuleId,
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
filename: PathBuf,
|
||||
) {
|
||||
// Load all the packages
|
||||
for Loc { value: entry, .. } in packages.iter() {
|
||||
|
@ -4121,7 +4133,7 @@ fn load_packages<'a>(
|
|||
}
|
||||
}
|
||||
Err(problem) => {
|
||||
let buf = to_https_problem_report_string(src, problem);
|
||||
let buf = to_https_problem_report_string(src, problem, filename);
|
||||
|
||||
load_messages.push(Msg::FailedToLoad(LoadingProblem::FormattedReport(buf)));
|
||||
return;
|
||||
|
@ -6581,7 +6593,11 @@ fn to_parse_problem_report<'a>(
|
|||
buf
|
||||
}
|
||||
|
||||
fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> String {
|
||||
fn report_cannot_run(
|
||||
module_id: ModuleId,
|
||||
filename: PathBuf,
|
||||
platform_path: &PlatformPath,
|
||||
) -> String {
|
||||
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
|
||||
use ven_pretty::DocAllocator;
|
||||
use PlatformPath::*;
|
||||
|
@ -6591,20 +6607,20 @@ fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> Stri
|
|||
let alloc = RocDocAllocator::new(&[], module_id, &interns);
|
||||
|
||||
let report = {
|
||||
match other {
|
||||
match platform_path {
|
||||
Valid(_) => unreachable!(),
|
||||
NotSpecified => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow("I could not find a platform based on your input file."),
|
||||
alloc.reflow(r"Does the module header contain an entry that looks like this:"),
|
||||
alloc.reflow(r"Does the module header have an entry that looks like this?"),
|
||||
alloc
|
||||
.parser_suggestion(" packages { pf: \"platform\" }")
|
||||
.parser_suggestion("packages { blah: \"…path or URL to platform…\" }")
|
||||
.indent(4),
|
||||
alloc.reflow("See also TODO."),
|
||||
alloc.reflow("Tip: The following part of the tutorial has an example of specifying a platform:\n\n<https://www.roc-lang.org/tutorial#building-an-application>"),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "NO PLATFORM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
|
@ -6619,7 +6635,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> Stri
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "NO PLATFORM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
|
@ -6634,7 +6650,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> Stri
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "NO PLATFORM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
|
@ -6649,7 +6665,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> Stri
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "NO PLATFORM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
procedure Dict.1 (Dict.692):
|
||||
let Dict.701 : List {U32, U32} = Array [];
|
||||
let Dict.702 : List {[], []} = Array [];
|
||||
let Dict.703 : U64 = 0i64;
|
||||
let Dict.42 : Float32 = CallByName Dict.42;
|
||||
let Dict.43 : U8 = CallByName Dict.43;
|
||||
let Dict.700 : {List {U32, U32}, List {[], []}, U64, Float32, U8} = Struct {Dict.701, Dict.702, Dict.703, Dict.42, Dict.43};
|
||||
ret Dict.700;
|
||||
procedure Dict.1 (Dict.726):
|
||||
let Dict.735 : List {U32, U32} = Array [];
|
||||
let Dict.736 : List {[], []} = Array [];
|
||||
let Dict.737 : U64 = 0i64;
|
||||
let Dict.44 : Float32 = CallByName Dict.44;
|
||||
let Dict.45 : U8 = CallByName Dict.45;
|
||||
let Dict.734 : {List {U32, U32}, List {[], []}, U64, Float32, U8} = Struct {Dict.735, Dict.736, Dict.737, Dict.44, Dict.45};
|
||||
ret Dict.734;
|
||||
|
||||
procedure Dict.4 (Dict.698):
|
||||
let Dict.150 : List {[], []} = StructAtIndex 1 Dict.698;
|
||||
let #Derived_gen.0 : List {U32, U32} = StructAtIndex 0 Dict.698;
|
||||
procedure Dict.4 (Dict.732):
|
||||
let Dict.157 : List {[], []} = StructAtIndex 1 Dict.732;
|
||||
let #Derived_gen.0 : List {U32, U32} = StructAtIndex 0 Dict.732;
|
||||
dec #Derived_gen.0;
|
||||
let Dict.699 : U64 = CallByName List.6 Dict.150;
|
||||
dec Dict.150;
|
||||
ret Dict.699;
|
||||
let Dict.733 : U64 = CallByName List.6 Dict.157;
|
||||
dec Dict.157;
|
||||
ret Dict.733;
|
||||
|
||||
procedure Dict.42 ():
|
||||
let Dict.707 : Float32 = 0.8f64;
|
||||
ret Dict.707;
|
||||
procedure Dict.44 ():
|
||||
let Dict.741 : Float32 = 0.8f64;
|
||||
ret Dict.741;
|
||||
|
||||
procedure Dict.43 ():
|
||||
let Dict.705 : U8 = 64i64;
|
||||
let Dict.706 : U8 = 3i64;
|
||||
let Dict.704 : U8 = CallByName Num.20 Dict.705 Dict.706;
|
||||
ret Dict.704;
|
||||
procedure Dict.45 ():
|
||||
let Dict.739 : U8 = 64i64;
|
||||
let Dict.740 : U8 = 3i64;
|
||||
let Dict.738 : U8 = CallByName Num.20 Dict.739 Dict.740;
|
||||
ret Dict.738;
|
||||
|
||||
procedure List.6 (#Attr.2):
|
||||
let List.553 : U64 = lowlevel ListLen #Attr.2;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -234,7 +234,19 @@ fn verify_procedures<'a>(
|
|||
|
||||
if !has_changes.stdout.is_empty() {
|
||||
println!("{}", std::str::from_utf8(&has_changes.stdout).unwrap());
|
||||
panic!("Output changed: resolve conflicts and `git add` the file.");
|
||||
panic!(indoc!(
|
||||
r#"
|
||||
|
||||
Mono output has changed! This is normal when making changes to the builtins.
|
||||
To fix it; run these commands locally:
|
||||
|
||||
cargo test -p test_mono -p uitest --no-fail-fast
|
||||
git add -u
|
||||
git commit -S -m "update mono tests"
|
||||
git push origin YOUR_BRANCH_NAME
|
||||
|
||||
"#
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,22 +3,22 @@
|
|||
app "test" provides [main] to "./platform"
|
||||
|
||||
f = \{} ->
|
||||
#^{-1} <2918><117>{} -<120>[[f(1)]]-> <116>[Ok <2926>{}]<80>*
|
||||
#^{-1} <2937><117>{} -<120>[[f(1)]]-> <116>[Ok <2945>{}]<80>*
|
||||
when g {} is
|
||||
# ^ <2908><2926>{} -<2916>[[g(2)]]-> <72>[Ok <2926>{}]<102>*
|
||||
# ^ <2927><2945>{} -<2935>[[g(2)]]-> <72>[Ok <2945>{}]<102>*
|
||||
_ -> Ok {}
|
||||
|
||||
g = \{} ->
|
||||
#^{-1} <2908><2926>{} -<2916>[[g(2)]]-> <72>[Ok <2926>{}]<102>*
|
||||
#^{-1} <2927><2945>{} -<2935>[[g(2)]]-> <72>[Ok <2945>{}]<102>*
|
||||
when h {} is
|
||||
# ^ <2913><2926>{} -<2921>[[h(3)]]-> <94>[Ok <2926>{}]<124>*
|
||||
# ^ <2932><2945>{} -<2940>[[h(3)]]-> <94>[Ok <2945>{}]<124>*
|
||||
_ -> Ok {}
|
||||
|
||||
h = \{} ->
|
||||
#^{-1} <2913><2926>{} -<2921>[[h(3)]]-> <94>[Ok <2926>{}]<124>*
|
||||
#^{-1} <2932><2945>{} -<2940>[[h(3)]]-> <94>[Ok <2945>{}]<124>*
|
||||
when f {} is
|
||||
# ^ <2918><117>{} -<120>[[f(1)]]-> <116>[Ok <2926>{}]<80>*
|
||||
# ^ <2937><117>{} -<120>[[f(1)]]-> <116>[Ok <2945>{}]<80>*
|
||||
_ -> Ok {}
|
||||
|
||||
main = f {}
|
||||
# ^ <2928><133>{} -<136>[[f(1)]]-> <138>[Ok <2926>{}]<2927>w_a
|
||||
# ^ <2947><133>{} -<136>[[f(1)]]-> <138>[Ok <2945>{}]<2946>w_a
|
||||
|
|
|
@ -11,8 +11,9 @@ use roc_build::{
|
|||
};
|
||||
use roc_collections::MutMap;
|
||||
use roc_error_macros::todo_lambda_erasure;
|
||||
use roc_gen_llvm::run_roc::RocCallResult;
|
||||
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadedModule, LoadingProblem, Threading};
|
||||
use roc_mono::ir::{generate_glue_procs, GlueProc, OptLevel};
|
||||
use roc_mono::ir::{generate_glue_procs, CrashTag, GlueProc, OptLevel};
|
||||
use roc_mono::layout::{GlobalLayoutInterner, LayoutCache, LayoutInterner};
|
||||
use roc_packaging::cache::{self, RocCacheDir};
|
||||
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
|
||||
|
@ -123,14 +124,16 @@ pub fn generate(
|
|||
if problems.warnings > 0 {
|
||||
problems.print_to_stdout(total_time);
|
||||
println!(
|
||||
".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
|
||||
".\n\nRunning glue despite warnings…\n\n\x1B[36m{}\x1B[39m",
|
||||
"─".repeat(80)
|
||||
);
|
||||
}
|
||||
|
||||
let lib = unsafe { Library::new(lib_path) }.unwrap();
|
||||
type MakeGlueReturnType =
|
||||
roc_std::RocResult<roc_std::RocList<roc_type::File>, roc_std::RocStr>;
|
||||
type MakeGlue = unsafe extern "C" fn(
|
||||
*mut roc_std::RocResult<roc_std::RocList<roc_type::File>, roc_std::RocStr>,
|
||||
*mut RocCallResult<MakeGlueReturnType>,
|
||||
&roc_std::RocList<roc_type::Types>,
|
||||
);
|
||||
|
||||
|
@ -140,14 +143,22 @@ pub fn generate(
|
|||
};
|
||||
let roc_types: roc_std::RocList<roc_type::Types> =
|
||||
types.iter().map(|x| x.into()).collect();
|
||||
let mut files = roc_std::RocResult::err(roc_std::RocStr::empty());
|
||||
let mut files =
|
||||
RocCallResult::new(roc_std::RocResult::err(roc_std::RocStr::empty()));
|
||||
unsafe { make_glue(&mut files, &roc_types) };
|
||||
|
||||
// Roc will free data passed into it. So forget that data.
|
||||
std::mem::forget(roc_types);
|
||||
|
||||
let files: Result<roc_std::RocList<roc_type::File>, roc_std::RocStr> =
|
||||
files.into();
|
||||
match Result::from(files) {
|
||||
Err((msg, tag)) => match tag {
|
||||
CrashTag::Roc => panic!(r#"Roc failed with message: "{msg}""#),
|
||||
CrashTag::User => panic!(r#"User crash with message: "{msg}""#),
|
||||
},
|
||||
Ok(x) => x.into(),
|
||||
};
|
||||
|
||||
let files = files.unwrap_or_else(|err| {
|
||||
eprintln!("Glue generation failed: {err}");
|
||||
|
||||
|
|
|
@ -128,9 +128,13 @@ mod glue_cli_run {
|
|||
multiple_modules:"multiple-modules" => indoc!(r#"
|
||||
combined was: Combined { s1: DepStr1::S("hello"), s2: DepStr2::R("world") }
|
||||
"#),
|
||||
arguments:"arguments" => indoc!(r#"
|
||||
Answer was: 84
|
||||
"#),
|
||||
// issue https://github.com/roc-lang/roc/issues/6121
|
||||
// TODO: re-enable this test. Currently it is flaking on macos x86-64 with a bad exit code.
|
||||
// nested_record:"nested-record" => "Record was: Outer { y: \"foo\", z: [1, 2], x: Inner { b: 24.0, a: 5 } }\n",
|
||||
// enumeration:"enumeration" => "tag_union was: MyEnum::Foo, Bar is: MyEnum::Bar, Baz is: MyEnum::Baz\n",
|
||||
// arguments:"arguments" => indoc!(r#"
|
||||
// Answer was: 84
|
||||
// "#),
|
||||
closures:"closures" => indoc!(r#"
|
||||
Answer was: 672
|
||||
"#),
|
||||
|
|
|
@ -1090,7 +1090,7 @@ pub fn can_problem<'b>(
|
|||
title = "OVERAPPLIED CRASH".to_string();
|
||||
}
|
||||
Problem::FileProblem { filename, error } => {
|
||||
let report = to_file_problem_report(alloc, &filename, error);
|
||||
let report = to_file_problem_report(alloc, filename, error);
|
||||
doc = report.doc;
|
||||
title = report.title;
|
||||
}
|
||||
|
|
|
@ -1101,7 +1101,11 @@ where
|
|||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn to_https_problem_report_string(url: &str, https_problem: Problem) -> String {
|
||||
pub fn to_https_problem_report_string(
|
||||
url: &str,
|
||||
https_problem: Problem,
|
||||
filename: PathBuf,
|
||||
) -> String {
|
||||
let src_lines: Vec<&str> = Vec::new();
|
||||
|
||||
let mut module_ids = ModuleIds::default();
|
||||
|
@ -1115,7 +1119,7 @@ pub fn to_https_problem_report_string(url: &str, https_problem: Problem) -> Stri
|
|||
|
||||
let mut buf = String::new();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
let report = to_https_problem_report(&alloc, url, https_problem);
|
||||
let report = to_https_problem_report(&alloc, url, https_problem, filename);
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
buf
|
||||
|
@ -1126,6 +1130,7 @@ pub fn to_https_problem_report<'b>(
|
|||
alloc: &'b RocDocAllocator<'b>,
|
||||
url: &'b str,
|
||||
https_problem: Problem,
|
||||
filename: PathBuf,
|
||||
) -> Report<'b> {
|
||||
match https_problem {
|
||||
Problem::UnsupportedEncoding(not_supported_encoding) => {
|
||||
|
@ -1154,7 +1159,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "UNSUPPORTED ENCODING".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1189,7 +1194,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "MULTIPLE ENCODINGS".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1224,7 +1229,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "INVALID CONTENT HASH".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1241,7 +1246,7 @@ pub fn to_https_problem_report<'b>(
|
|||
alloc.concat([alloc.tip(), alloc.reflow(r"Is the URL correct?")]),
|
||||
]);
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "NOTFOUND".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1268,7 +1273,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "IO ERROR".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1295,7 +1300,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "IO ERROR".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1324,7 +1329,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "HTTP ERROR".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1365,7 +1370,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "INVALID EXTENSION SUFFIX".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1398,7 +1403,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "INVALID EXTENSION".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1436,7 +1441,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "INVALID FRAGMENT".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1474,7 +1479,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "MISSING PACKAGE HASH".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1500,7 +1505,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "HTTPS MANDATORY".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1543,7 +1548,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "MISLEADING CHARACTERS".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1573,7 +1578,7 @@ pub fn to_https_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "FILE TOO LARGE".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1582,13 +1587,10 @@ pub fn to_https_problem_report<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_file_problem_report_string(filename: &Path, error: io::ErrorKind) -> String {
|
||||
pub fn to_file_problem_report_string(filename: PathBuf, error: io::ErrorKind) -> String {
|
||||
let src_lines: Vec<&str> = Vec::new();
|
||||
|
||||
let mut module_ids = ModuleIds::default();
|
||||
|
||||
let module_id = module_ids.get_or_insert(&"find module name somehow?".into());
|
||||
|
||||
let interns = Interns::default();
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
|
@ -1604,16 +1606,16 @@ pub fn to_file_problem_report_string(filename: &Path, error: io::ErrorKind) -> S
|
|||
|
||||
pub fn to_file_problem_report<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: &Path,
|
||||
filename: PathBuf,
|
||||
error: io::ErrorKind,
|
||||
) -> Report<'b> {
|
||||
let filename: String = filename.to_str().unwrap().to_string();
|
||||
let filename_str: String = filename.to_str().unwrap().to_string();
|
||||
match error {
|
||||
io::ErrorKind::NotFound => {
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I am looking for this file, but it's not there:"),
|
||||
alloc
|
||||
.string(filename)
|
||||
.string(filename_str)
|
||||
.annotate(Annotation::ParserSuggestion)
|
||||
.indent(4),
|
||||
alloc.concat([
|
||||
|
@ -1623,7 +1625,7 @@ pub fn to_file_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "FILE NOT FOUND".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1633,7 +1635,7 @@ pub fn to_file_problem_report<'b>(
|
|||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I don't have the required permissions to read this file:"),
|
||||
alloc
|
||||
.string(filename)
|
||||
.string(filename_str)
|
||||
.annotate(Annotation::ParserSuggestion)
|
||||
.indent(4),
|
||||
alloc
|
||||
|
@ -1641,7 +1643,7 @@ pub fn to_file_problem_report<'b>(
|
|||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "FILE PERMISSION DENIED".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
@ -1652,13 +1654,16 @@ pub fn to_file_problem_report<'b>(
|
|||
let formatted = format!("{error}");
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I tried to read this file:"),
|
||||
alloc.string(filename).annotate(Annotation::Error).indent(4),
|
||||
alloc
|
||||
.string(filename_str)
|
||||
.annotate(Annotation::Error)
|
||||
.indent(4),
|
||||
alloc.reflow(r"But ran into:"),
|
||||
alloc.text(formatted).annotate(Annotation::Error).indent(4),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
filename,
|
||||
doc,
|
||||
title: "FILE PROBLEM".to_string(),
|
||||
severity: Severity::Fatal,
|
||||
|
|
|
@ -12,6 +12,7 @@ use core::{
|
|||
ops::{Deref, DerefMut},
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{roc_alloc, roc_dealloc, roc_realloc, storage::Storage};
|
||||
|
||||
|
@ -208,7 +209,7 @@ impl<T> RocList<T> {
|
|||
}
|
||||
|
||||
/// Useful for doing memcpy on the elements. Returns NULL if list is empty.
|
||||
pub(crate) unsafe fn ptr_to_first_elem(&self) -> *const T {
|
||||
pub(crate) fn ptr_to_first_elem(&self) -> *const T {
|
||||
unsafe { core::mem::transmute(self.elements) }
|
||||
}
|
||||
|
||||
|
@ -222,6 +223,15 @@ impl<T> RocList<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn ptr_to_refcount(&self) -> *mut c_void {
|
||||
if self.is_seamless_slice() {
|
||||
((self.capacity_or_ref_ptr << 1) - std::mem::size_of::<usize>()) as *mut _
|
||||
} else {
|
||||
unsafe { self.ptr_to_first_elem().cast::<usize>().sub(1) as *mut _ }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn elem_ptr_from_alloc_ptr(alloc_ptr: *mut c_void) -> *mut c_void {
|
||||
unsafe {
|
||||
alloc_ptr
|
||||
|
@ -230,6 +240,44 @@ impl<T> RocList<T> {
|
|||
.cast()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, value: T) {
|
||||
self.push(value)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) {
|
||||
if self.capacity() <= self.len() {
|
||||
// reserve space for (at least!) one more element
|
||||
self.reserve(1);
|
||||
}
|
||||
|
||||
let elements = self.elements.unwrap().as_ptr();
|
||||
let append_ptr = unsafe { elements.add(self.len()) };
|
||||
|
||||
unsafe {
|
||||
// Write the element into the slot, without dropping it.
|
||||
ptr::write(append_ptr, ManuallyDrop::new(value));
|
||||
}
|
||||
|
||||
// It's important that the length is increased one by one, to
|
||||
// make sure that we don't drop uninitialized elements, even when
|
||||
// a incrementing the reference count panics.
|
||||
self.length += 1;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// - `bytes` must be allocated for `cap` elements
|
||||
/// - `bytes` must be initialized for `len` elements
|
||||
/// - `bytes` must be preceded by a correctly-aligned refcount (usize)
|
||||
/// - `cap` >= `len`
|
||||
pub unsafe fn from_raw_parts(bytes: *mut T, len: usize, cap: usize) -> Self {
|
||||
Self {
|
||||
elements: NonNull::new(bytes.cast()),
|
||||
length: len,
|
||||
capacity_or_ref_ptr: cap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RocList<T>
|
||||
|
@ -323,6 +371,38 @@ where
|
|||
}
|
||||
|
||||
impl<T> RocList<T> {
|
||||
#[track_caller]
|
||||
pub fn slice_range(&self, range: Range<usize>) -> Self {
|
||||
match self.try_slice_range(range) {
|
||||
Some(x) => x,
|
||||
None => panic!("slice index out of range"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_slice_range(&self, range: Range<usize>) -> Option<Self> {
|
||||
if self.as_slice().get(range.start..range.end).is_none() {
|
||||
None
|
||||
} else {
|
||||
// increment the refcount
|
||||
std::mem::forget(self.clone());
|
||||
|
||||
let element_ptr = self.as_slice()[range.start..]
|
||||
.as_ptr()
|
||||
.cast::<ManuallyDrop<T>>();
|
||||
|
||||
let capacity_or_ref_ptr =
|
||||
(self.ptr_to_first_elem() as usize) >> 1 | isize::MIN as usize;
|
||||
|
||||
let roc_list = RocList {
|
||||
elements: NonNull::new(element_ptr as *mut ManuallyDrop<T>),
|
||||
length: range.end - range.start,
|
||||
capacity_or_ref_ptr,
|
||||
};
|
||||
|
||||
Some(roc_list)
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase a RocList's capacity by at least the requested number of elements (possibly more).
|
||||
///
|
||||
/// May return a new RocList, if the provided one was not unique.
|
||||
|
@ -333,7 +413,7 @@ impl<T> RocList<T> {
|
|||
|
||||
match self.elements_and_storage() {
|
||||
Some((elements, storage)) => {
|
||||
if storage.get().is_unique() {
|
||||
if storage.get().is_unique() && !self.is_seamless_slice() {
|
||||
unsafe {
|
||||
let old_alloc = self.ptr_to_allocation();
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ use core::{
|
|||
|
||||
#[cfg(feature = "std")]
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::{ops::Range, ptr::NonNull};
|
||||
|
||||
use crate::RocList;
|
||||
use crate::{roc_realloc, RocList};
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct RocStr(RocStrInner);
|
||||
|
@ -73,8 +74,34 @@ impl RocStr {
|
|||
Self(RocStrInner { small_string })
|
||||
} else {
|
||||
let heap_allocated = RocList::from_slice(slice);
|
||||
let big_string = unsafe { std::mem::transmute(heap_allocated) };
|
||||
Self(RocStrInner {
|
||||
heap_allocated: ManuallyDrop::new(heap_allocated),
|
||||
heap_allocated: ManuallyDrop::new(big_string),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// - `bytes` must be allocated for `cap` bytes
|
||||
/// - `bytes` must be initialized for `len` bytes
|
||||
/// - `bytes` must be preceded by a correctly-aligned refcount (usize)
|
||||
/// - `bytes` must represent valid UTF-8
|
||||
/// - `cap` >= `len`
|
||||
pub unsafe fn from_raw_parts(bytes: *mut u8, len: usize, cap: usize) -> Self {
|
||||
if len <= SmallString::CAPACITY {
|
||||
unsafe {
|
||||
let slice = std::slice::from_raw_parts(bytes, len);
|
||||
let small_string = SmallString::try_from_utf8_bytes(slice).unwrap_unchecked();
|
||||
Self(RocStrInner { small_string })
|
||||
}
|
||||
} else {
|
||||
Self(RocStrInner {
|
||||
heap_allocated: ManuallyDrop::new(BigString {
|
||||
elements: unsafe { NonNull::new_unchecked(bytes) },
|
||||
length: len,
|
||||
capacity_or_alloc_ptr: cap,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +120,7 @@ impl RocStr {
|
|||
|
||||
pub fn capacity(&self) -> usize {
|
||||
match self.as_enum_ref() {
|
||||
RocStrInnerRef::HeapAllocated(roc_list) => roc_list.capacity(),
|
||||
RocStrInnerRef::HeapAllocated(big_string) => big_string.capacity(),
|
||||
RocStrInnerRef::SmallString(_) => SmallString::CAPACITY,
|
||||
}
|
||||
}
|
||||
|
@ -137,10 +164,12 @@ impl RocStr {
|
|||
/// There is no way to tell how many references it has and if it is safe to free.
|
||||
/// As such, only values that should have a static lifetime for the entire application run
|
||||
/// should be considered for marking read-only.
|
||||
pub unsafe fn set_readonly(&self) {
|
||||
match self.as_enum_ref() {
|
||||
RocStrInnerRef::HeapAllocated(roc_list) => unsafe { roc_list.set_readonly() },
|
||||
RocStrInnerRef::SmallString(_) => {}
|
||||
pub unsafe fn set_readonly(&mut self) {
|
||||
if self.is_small_str() {
|
||||
/* do nothing */
|
||||
} else {
|
||||
let big = unsafe { &mut self.0.heap_allocated };
|
||||
big.set_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +196,7 @@ impl RocStr {
|
|||
} else {
|
||||
// The requested capacity won't fit in a small string; we need to go big.
|
||||
RocStr(RocStrInner {
|
||||
heap_allocated: ManuallyDrop::new(RocList::with_capacity(bytes)),
|
||||
heap_allocated: ManuallyDrop::new(BigString::with_capacity(bytes)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -182,21 +211,33 @@ impl RocStr {
|
|||
|
||||
if target_cap > SmallString::CAPACITY {
|
||||
// The requested capacity won't fit in a small string; we need to go big.
|
||||
let mut roc_list = RocList::with_capacity(target_cap);
|
||||
let mut big_string = BigString::with_capacity(target_cap);
|
||||
|
||||
roc_list.extend_from_slice(small_str.as_bytes());
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
self.as_bytes().as_ptr(),
|
||||
big_string.ptr_to_first_elem(),
|
||||
self.len(),
|
||||
)
|
||||
};
|
||||
|
||||
*self = RocStr(RocStrInner {
|
||||
heap_allocated: ManuallyDrop::new(roc_list),
|
||||
big_string.length = self.len();
|
||||
big_string.capacity_or_alloc_ptr = target_cap;
|
||||
|
||||
let mut updated = RocStr(RocStrInner {
|
||||
heap_allocated: ManuallyDrop::new(big_string),
|
||||
});
|
||||
|
||||
mem::swap(self, &mut updated);
|
||||
mem::forget(updated);
|
||||
}
|
||||
} else {
|
||||
let mut roc_list = unsafe { ManuallyDrop::take(&mut self.0.heap_allocated) };
|
||||
let mut big_string = unsafe { ManuallyDrop::take(&mut self.0.heap_allocated) };
|
||||
|
||||
roc_list.reserve(bytes);
|
||||
big_string.reserve(bytes);
|
||||
|
||||
let mut updated = RocStr(RocStrInner {
|
||||
heap_allocated: ManuallyDrop::new(roc_list),
|
||||
heap_allocated: ManuallyDrop::new(big_string),
|
||||
});
|
||||
|
||||
mem::swap(self, &mut updated);
|
||||
|
@ -204,12 +245,57 @@ impl RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn slice_range(&self, range: Range<usize>) -> Self {
|
||||
match self.try_slice_range(range) {
|
||||
Some(x) => x,
|
||||
None => panic!("slice index out of range"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_slice_range(&self, range: Range<usize>) -> Option<Self> {
|
||||
if self.as_str().get(range.start..range.end).is_none() {
|
||||
None
|
||||
} else if range.end - range.start <= SmallString::CAPACITY && self.is_small_str() {
|
||||
let slice = &self.as_bytes()[range];
|
||||
let small_string =
|
||||
unsafe { SmallString::try_from_utf8_bytes(slice).unwrap_unchecked() };
|
||||
|
||||
// NOTE decrements `self`
|
||||
Some(RocStr(RocStrInner { small_string }))
|
||||
} else {
|
||||
// increment the refcount
|
||||
std::mem::forget(self.clone());
|
||||
|
||||
let big = unsafe { &self.0.heap_allocated };
|
||||
let ptr = unsafe { (self.as_bytes().as_ptr() as *mut u8).add(range.start) };
|
||||
|
||||
let heap_allocated = ManuallyDrop::new(BigString {
|
||||
elements: unsafe { NonNull::new_unchecked(ptr) },
|
||||
length: (isize::MIN as usize) | (range.end - range.start),
|
||||
capacity_or_alloc_ptr: (big.ptr_to_first_elem() as usize) >> 1,
|
||||
});
|
||||
|
||||
Some(RocStr(RocStrInner { heap_allocated }))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_once(&self, delimiter: &str) -> Option<(Self, Self)> {
|
||||
let (a, b) = self.as_str().split_once(delimiter)?;
|
||||
|
||||
let x = self.slice_range(0..a.len());
|
||||
let y = self.slice_range(self.len() - b.len()..self.len());
|
||||
|
||||
Some((x, y))
|
||||
}
|
||||
|
||||
pub fn split_whitespace(&self) -> SplitWhitespace<'_> {
|
||||
SplitWhitespace(self.as_str().char_indices().peekable(), self)
|
||||
}
|
||||
|
||||
/// Returns the index of the first interior \0 byte in the string, or None if there are none.
|
||||
fn first_nul_byte(&self) -> Option<usize> {
|
||||
match self.as_enum_ref() {
|
||||
RocStrInnerRef::HeapAllocated(roc_list) => roc_list.iter().position(|byte| *byte == 0),
|
||||
RocStrInnerRef::SmallString(small_string) => small_string.first_nul_byte(),
|
||||
}
|
||||
self.as_bytes().iter().position(|byte| *byte == 0)
|
||||
}
|
||||
|
||||
// If the string is under this many bytes, the with_terminator family
|
||||
|
@ -267,60 +353,49 @@ impl RocStr {
|
|||
};
|
||||
|
||||
match self.as_enum_ref() {
|
||||
RocStrInnerRef::HeapAllocated(roc_list) => {
|
||||
RocStrInnerRef::HeapAllocated(big_string) => {
|
||||
unsafe {
|
||||
match roc_list.storage() {
|
||||
Some(storage) if storage.is_unique() => {
|
||||
// The backing RocList was unique, so we can mutate it in-place.
|
||||
let len = roc_list.len();
|
||||
let ptr = if len < roc_list.capacity() {
|
||||
// We happen to have excess capacity already, so we will be able
|
||||
// to write the terminator into the first byte of excess capacity.
|
||||
roc_list.ptr_to_first_elem() as *mut u8
|
||||
} else {
|
||||
// We always have an allocation that's even bigger than necessary,
|
||||
// because the refcount bytes take up more than the 1B needed for
|
||||
// the terminator. We just need to shift the bytes over on top
|
||||
// of the refcount.
|
||||
let alloc_ptr = roc_list.ptr_to_allocation() as *mut u8;
|
||||
if big_string.is_unique() {
|
||||
// The backing RocList was unique, so we can mutate it in-place.
|
||||
let len = big_string.len();
|
||||
let ptr = if len < big_string.capacity() {
|
||||
// We happen to have excess capacity already, so we will be able
|
||||
// to write the terminator into the first byte of excess capacity.
|
||||
big_string.ptr_to_first_elem() as *mut u8
|
||||
} else {
|
||||
// We always have an allocation that's even bigger than necessary,
|
||||
// because the refcount bytes take up more than the 1B needed for
|
||||
// the terminator. We just need to shift the bytes over on top
|
||||
// of the refcount.
|
||||
let alloc_ptr = big_string.ptr_to_allocation() as *mut u8;
|
||||
|
||||
// First, copy the bytes over the original allocation - effectively
|
||||
// shifting everything over by one `usize`. Now we no longer have a
|
||||
// refcount (but the terminated won't use that anyway), but we do
|
||||
// have a free `usize` at the end.
|
||||
//
|
||||
// IMPORTANT: Must use ptr::copy instead of ptr::copy_nonoverlapping
|
||||
// because the regions definitely overlap!
|
||||
ptr::copy(roc_list.ptr_to_first_elem() as *mut u8, alloc_ptr, len);
|
||||
|
||||
alloc_ptr
|
||||
};
|
||||
|
||||
terminate(ptr, len)
|
||||
}
|
||||
Some(_) => {
|
||||
let len = roc_list.len();
|
||||
|
||||
// The backing list was not unique, so we can't mutate it in-place.
|
||||
// ask for `len + 1` to store the original string and the terminator
|
||||
with_stack_bytes(len + 1, |alloc_ptr: *mut u8| {
|
||||
let alloc_ptr = alloc_ptr as *mut u8;
|
||||
let elem_ptr = roc_list.ptr_to_first_elem() as *mut u8;
|
||||
|
||||
// memcpy the bytes into the stack allocation
|
||||
ptr::copy_nonoverlapping(elem_ptr, alloc_ptr, len);
|
||||
|
||||
terminate(alloc_ptr, len)
|
||||
})
|
||||
}
|
||||
None => {
|
||||
// The backing list was empty.
|
||||
// First, copy the bytes over the original allocation - effectively
|
||||
// shifting everything over by one `usize`. Now we no longer have a
|
||||
// refcount (but the terminated won't use that anyway), but we do
|
||||
// have a free `usize` at the end.
|
||||
//
|
||||
// No need to do a heap allocation for an empty string - we
|
||||
// can just do a stack allocation that will live for the
|
||||
// duration of the function.
|
||||
func([terminator].as_mut_ptr(), 0)
|
||||
}
|
||||
// IMPORTANT: Must use ptr::copy instead of ptr::copy_nonoverlapping
|
||||
// because the regions definitely overlap!
|
||||
ptr::copy(big_string.ptr_to_first_elem() as *mut u8, alloc_ptr, len);
|
||||
|
||||
alloc_ptr
|
||||
};
|
||||
|
||||
terminate(ptr, len)
|
||||
} else {
|
||||
let len = big_string.len();
|
||||
|
||||
// The backing list was not unique, so we can't mutate it in-place.
|
||||
// ask for `len + 1` to store the original string and the terminator
|
||||
with_stack_bytes(len + 1, |alloc_ptr: *mut u8| {
|
||||
let alloc_ptr = alloc_ptr as *mut u8;
|
||||
let elem_ptr = big_string.ptr_to_first_elem() as *mut u8;
|
||||
|
||||
// memcpy the bytes into the stack allocation
|
||||
std::ptr::copy_nonoverlapping(elem_ptr, alloc_ptr, len);
|
||||
|
||||
terminate(alloc_ptr, len)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -485,57 +560,46 @@ impl RocStr {
|
|||
};
|
||||
|
||||
match self.as_enum_ref() {
|
||||
RocStrInnerRef::HeapAllocated(roc_list) => {
|
||||
let len = roc_list.len();
|
||||
RocStrInnerRef::HeapAllocated(big_string) => {
|
||||
let len = big_string.len();
|
||||
|
||||
unsafe {
|
||||
match roc_list.storage() {
|
||||
Some(storage) if storage.is_unique() => {
|
||||
// The backing RocList was unique, so we can mutate it in-place.
|
||||
if big_string.is_unique() {
|
||||
// The backing RocList was unique, so we can mutate it in-place.
|
||||
|
||||
// We need 1 extra elem for the terminator. It must be an elem,
|
||||
// not a byte, because we'll be providing a pointer to elems.
|
||||
let needed_bytes = (len + 1) * size_of::<E>();
|
||||
// We need 1 extra elem for the terminator. It must be an elem,
|
||||
// not a byte, because we'll be providing a pointer to elems.
|
||||
let needed_bytes = (len + 1) * size_of::<E>();
|
||||
|
||||
// We can use not only the capacity on the heap, but also
|
||||
// the bytes originally used for the refcount.
|
||||
let available_bytes = roc_list.capacity() + size_of::<Storage>();
|
||||
// We can use not only the capacity on the heap, but also
|
||||
// the bytes originally used for the refcount.
|
||||
let available_bytes = big_string.capacity() + size_of::<Storage>();
|
||||
|
||||
if needed_bytes < available_bytes {
|
||||
debug_assert!(align_of::<Storage>() >= align_of::<E>());
|
||||
if needed_bytes < available_bytes {
|
||||
debug_assert!(align_of::<Storage>() >= align_of::<E>());
|
||||
|
||||
// We happen to have sufficient excess capacity already,
|
||||
// so we will be able to write the new elements as well as
|
||||
// the terminator into the existing allocation.
|
||||
let ptr = roc_list.ptr_to_allocation() as *mut E;
|
||||
let answer = terminate(ptr, self.as_str());
|
||||
// We happen to have sufficient excess capacity already,
|
||||
// so we will be able to write the new elements as well as
|
||||
// the terminator into the existing allocation.
|
||||
let ptr = big_string.ptr_to_allocation() as *mut E;
|
||||
let answer = terminate(ptr, self.as_str());
|
||||
|
||||
// We cannot rely on the RocStr::drop implementation, because
|
||||
// it tries to use the refcount - which we just overwrote
|
||||
// with string bytes.
|
||||
mem::forget(self);
|
||||
crate::roc_dealloc(ptr.cast(), mem::align_of::<E>() as u32);
|
||||
// We cannot rely on the RocStr::drop implementation, because
|
||||
// it tries to use the refcount - which we just overwrote
|
||||
// with string bytes.
|
||||
mem::forget(self);
|
||||
crate::roc_dealloc(ptr.cast(), mem::align_of::<E>() as u32);
|
||||
|
||||
answer
|
||||
} else {
|
||||
// We didn't have sufficient excess capacity already,
|
||||
// so we need to do either a new stack allocation or a new
|
||||
// heap allocation.
|
||||
fallback(self.as_str())
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
// The backing list was not unique, so we can't mutate it in-place.
|
||||
answer
|
||||
} else {
|
||||
// We didn't have sufficient excess capacity already,
|
||||
// so we need to do either a new stack allocation or a new
|
||||
// heap allocation.
|
||||
fallback(self.as_str())
|
||||
}
|
||||
None => {
|
||||
// The backing list was empty.
|
||||
//
|
||||
// No need to do a heap allocation for an empty string - we
|
||||
// can just do a stack allocation that will live for the
|
||||
// duration of the function.
|
||||
func([terminator].as_mut_ptr() as *mut E, "")
|
||||
}
|
||||
} else {
|
||||
// The backing list was not unique, so we can't mutate it in-place.
|
||||
fallback(self.as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -558,12 +622,44 @@ impl RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct SplitWhitespace<'a>(std::iter::Peekable<std::str::CharIndices<'a>>, &'a RocStr);
|
||||
|
||||
impl Iterator for SplitWhitespace<'_> {
|
||||
type Item = RocStr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start = 'blk: {
|
||||
while let Some((pos, c)) = self.0.peek() {
|
||||
if c.is_whitespace() {
|
||||
self.0.next();
|
||||
} else {
|
||||
break 'blk *pos;
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
};
|
||||
|
||||
let end = 'blk: {
|
||||
for (pos, c) in self.0.by_ref() {
|
||||
if c.is_whitespace() {
|
||||
break 'blk pos;
|
||||
}
|
||||
}
|
||||
|
||||
break 'blk self.1.len();
|
||||
};
|
||||
|
||||
self.1.try_slice_range(start..end)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RocStr {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self.as_enum_ref() {
|
||||
RocStrInnerRef::HeapAllocated(h) => unsafe { core::str::from_utf8_unchecked(h) },
|
||||
RocStrInnerRef::HeapAllocated(h) => h.as_str(),
|
||||
RocStrInnerRef::SmallString(s) => s,
|
||||
}
|
||||
}
|
||||
|
@ -697,6 +793,203 @@ impl From<SendSafeRocStr> for RocStr {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct BigString {
|
||||
elements: NonNull<u8>,
|
||||
length: usize,
|
||||
capacity_or_alloc_ptr: usize,
|
||||
}
|
||||
|
||||
const SEAMLESS_SLICE_BIT: usize = isize::MIN as usize;
|
||||
|
||||
impl BigString {
|
||||
fn len(&self) -> usize {
|
||||
self.length & !SEAMLESS_SLICE_BIT
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
if self.is_seamless_slice() {
|
||||
self.len()
|
||||
} else {
|
||||
self.capacity_or_alloc_ptr
|
||||
}
|
||||
}
|
||||
|
||||
fn is_seamless_slice(&self) -> bool {
|
||||
(self.length as isize) < 0
|
||||
}
|
||||
|
||||
fn ptr_to_first_elem(&self) -> *mut u8 {
|
||||
unsafe { core::mem::transmute(self.elements) }
|
||||
}
|
||||
|
||||
fn ptr_to_allocation(&self) -> *mut usize {
|
||||
// these are the same because the alignment of u8 is just 1
|
||||
self.ptr_to_refcount()
|
||||
}
|
||||
|
||||
fn ptr_to_refcount(&self) -> *mut usize {
|
||||
if self.is_seamless_slice() {
|
||||
unsafe { ((self.capacity_or_alloc_ptr << 1) as *mut usize).sub(1) }
|
||||
} else {
|
||||
unsafe { self.ptr_to_first_elem().cast::<usize>().sub(1) }
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
unsafe { std::slice::from_raw_parts(self.ptr_to_first_elem(), self.len()) }
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &str {
|
||||
unsafe { std::str::from_utf8_unchecked(self.as_bytes()) }
|
||||
}
|
||||
|
||||
fn is_unique(&self) -> bool {
|
||||
if self.capacity() == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ptr = self.ptr_to_refcount();
|
||||
let rc = unsafe { std::ptr::read(ptr) as isize };
|
||||
|
||||
rc == isize::MIN
|
||||
}
|
||||
|
||||
fn is_readonly(&self) -> bool {
|
||||
if self.capacity() == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let ptr = self.ptr_to_refcount();
|
||||
let rc = unsafe { std::ptr::read(ptr) as isize };
|
||||
|
||||
rc == 0
|
||||
}
|
||||
|
||||
fn set_readonly(&mut self) {
|
||||
assert_ne!(self.capacity(), 0);
|
||||
|
||||
let ptr = self.ptr_to_refcount();
|
||||
unsafe { std::ptr::write(ptr, 0) }
|
||||
}
|
||||
|
||||
fn inc(&mut self, n: usize) {
|
||||
let ptr = self.ptr_to_refcount();
|
||||
unsafe {
|
||||
let value = std::ptr::read(ptr);
|
||||
std::ptr::write(ptr, Ord::max(0, ((value as isize) + n as isize) as usize));
|
||||
}
|
||||
}
|
||||
|
||||
fn dec(&mut self) {
|
||||
if self.capacity() == 0 {
|
||||
// no valid allocation, elements pointer is dangling
|
||||
return;
|
||||
}
|
||||
|
||||
let ptr = self.ptr_to_refcount();
|
||||
unsafe {
|
||||
let value = std::ptr::read(ptr) as isize;
|
||||
match value {
|
||||
0 => {
|
||||
// static lifetime, do nothing
|
||||
}
|
||||
isize::MIN => {
|
||||
// refcount becomes zero; free allocation
|
||||
crate::roc_dealloc(self.ptr_to_allocation().cast(), 1);
|
||||
}
|
||||
_ => {
|
||||
std::ptr::write(ptr, (value - 1) as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_capacity(cap: usize) -> Self {
|
||||
let mut this = Self {
|
||||
elements: NonNull::dangling(),
|
||||
length: 0,
|
||||
capacity_or_alloc_ptr: 0,
|
||||
};
|
||||
|
||||
this.reserve(cap);
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Increase a BigString's capacity by at least the requested number of elements (possibly more).
|
||||
///
|
||||
/// May return a new BigString, if the provided one was not unique.
|
||||
fn reserve(&mut self, n: usize) {
|
||||
let align = std::mem::size_of::<usize>();
|
||||
let desired_cap = self.len() + n;
|
||||
let desired_alloc = align + desired_cap;
|
||||
|
||||
if self.is_unique() && !self.is_seamless_slice() {
|
||||
if self.capacity() >= desired_cap {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_alloc = unsafe {
|
||||
roc_realloc(
|
||||
self.ptr_to_allocation().cast(),
|
||||
desired_alloc as _,
|
||||
align + self.capacity(),
|
||||
align as _,
|
||||
)
|
||||
};
|
||||
|
||||
let elements = unsafe { NonNull::new_unchecked(new_alloc.cast::<u8>().add(align)) };
|
||||
|
||||
let mut this = Self {
|
||||
elements,
|
||||
length: self.len(),
|
||||
capacity_or_alloc_ptr: desired_cap,
|
||||
};
|
||||
|
||||
std::mem::swap(&mut this, self);
|
||||
std::mem::forget(this);
|
||||
} else {
|
||||
let ptr = unsafe { crate::roc_alloc(desired_alloc, align as _) } as *mut u8;
|
||||
let elements = unsafe { NonNull::new_unchecked(ptr.cast::<u8>().add(align)) };
|
||||
|
||||
unsafe {
|
||||
// Copy the old elements to the new allocation.
|
||||
std::ptr::copy_nonoverlapping(self.ptr_to_first_elem(), ptr.add(align), self.len());
|
||||
}
|
||||
|
||||
let mut this = Self {
|
||||
elements,
|
||||
length: self.len(),
|
||||
capacity_or_alloc_ptr: desired_cap,
|
||||
};
|
||||
|
||||
std::mem::swap(&mut this, self);
|
||||
std::mem::drop(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BigString {
|
||||
fn clone(&self) -> Self {
|
||||
let mut this = Self {
|
||||
elements: self.elements,
|
||||
length: self.length,
|
||||
capacity_or_alloc_ptr: self.capacity_or_alloc_ptr,
|
||||
};
|
||||
|
||||
this.inc(1);
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BigString {
|
||||
fn drop(&mut self) {
|
||||
self.dec()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
union RocStrInner {
|
||||
// TODO: this really should be separated from the List type.
|
||||
|
@ -704,12 +997,12 @@ union RocStrInner {
|
|||
// Currently, there are work arounds in RocList to handle both via removing the highest bit of length in many cases.
|
||||
// With glue changes, we should probably rewrite these cleanly to match what is in the zig bitcode.
|
||||
// It is definitely a bit stale now and I think the storage mechanism can be quite confusing with our extra pieces of state.
|
||||
heap_allocated: ManuallyDrop<RocList<u8>>,
|
||||
heap_allocated: ManuallyDrop<BigString>,
|
||||
small_string: SmallString,
|
||||
}
|
||||
|
||||
enum RocStrInnerRef<'a> {
|
||||
HeapAllocated(&'a RocList<u8>),
|
||||
HeapAllocated(&'a BigString),
|
||||
SmallString(&'a SmallString),
|
||||
}
|
||||
|
||||
|
@ -756,17 +1049,6 @@ impl SmallString {
|
|||
fn len(&self) -> usize {
|
||||
usize::from(self.len & !RocStr::MASK)
|
||||
}
|
||||
|
||||
/// Returns the index of the first interior \0 byte in the string, or None if there are none.
|
||||
fn first_nul_byte(&self) -> Option<usize> {
|
||||
for (index, byte) in self.bytes[0..self.len()].iter().enumerate() {
|
||||
if *byte == 0 {
|
||||
return Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SmallString {
|
||||
|
|
|
@ -358,6 +358,79 @@ mod test_roc_std {
|
|||
let roc_list = RocList::<RocStr>::empty();
|
||||
assert!(roc_list.is_unique());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slicing_and_dicing_list() {
|
||||
let example = RocList::from_slice(b"chaos is a ladder");
|
||||
|
||||
// basic slice from the start
|
||||
assert_eq!(example.slice_range(0..5).as_slice(), b"chaos");
|
||||
|
||||
// slice in the middle
|
||||
assert_eq!(example.slice_range(6..10).as_slice(), b"is a");
|
||||
|
||||
// slice of slice
|
||||
let first = example.slice_range(0..5);
|
||||
assert_eq!(first.slice_range(0..3).as_slice(), b"cha");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slicing_and_dicing_str() {
|
||||
let example = RocStr::from("chaos is a ladder");
|
||||
|
||||
// basic slice from the start
|
||||
assert_eq!(example.slice_range(0..5).as_str(), "chaos");
|
||||
|
||||
// slice in the middle
|
||||
assert_eq!(example.slice_range(6..10).as_str(), "is a");
|
||||
|
||||
// slice of slice
|
||||
let first = example.slice_range(0..5);
|
||||
assert_eq!(first.slice_range(0..3).as_str(), "cha");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roc_list_push() {
|
||||
let mut example = RocList::from_slice(&[1, 2, 3]);
|
||||
|
||||
// basic slice from the start
|
||||
example.push(4);
|
||||
assert_eq!(example.as_slice(), &[1, 2, 3, 4]);
|
||||
|
||||
// slice in the middle
|
||||
let mut sliced = example.slice_range(0..3);
|
||||
sliced.push(5);
|
||||
assert_eq!(sliced.as_slice(), &[1, 2, 3, 5]);
|
||||
|
||||
// original did not change
|
||||
assert_eq!(example.as_slice(), &[1, 2, 3, 4]);
|
||||
|
||||
drop(sliced);
|
||||
|
||||
let mut sliced = example.slice_range(0..3);
|
||||
// make the slice unique
|
||||
drop(example);
|
||||
|
||||
sliced.push(5);
|
||||
assert_eq!(sliced.as_slice(), &[1, 2, 3, 5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_whitespace() {
|
||||
let example = RocStr::from("chaos is a ladder");
|
||||
|
||||
let split: Vec<_> = example.split_whitespace().collect();
|
||||
|
||||
assert_eq!(
|
||||
split,
|
||||
vec![
|
||||
RocStr::from("chaos"),
|
||||
RocStr::from("is"),
|
||||
RocStr::from("a"),
|
||||
RocStr::from("ladder"),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -2076,8 +2076,8 @@ Here are various Roc expressions involving operators, and what they desugar to.
|
|||
| `a && b` | `Bool.and a b` |
|
||||
| <code>a \|\| b</code> | `Bool.or a b` |
|
||||
| `!a` | `Bool.not a` |
|
||||
| <code>a \|> b</code> | `b a` |
|
||||
| <code>a b c \|> f x y</code> | `f (a b c) x y` |
|
||||
| <code>a \|> f</code> | `f a` |
|
||||
| <code>f a b \|> g x y</code> | `g (f a b) x y` |
|
||||
|
||||
|
||||
</section>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue