Merge remote-tracking branch 'origin/trunk' into assoc-list-dict

This commit is contained in:
Folkert 2022-07-13 20:44:28 +02:00
commit 5763248b44
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
41 changed files with 949 additions and 131 deletions

View file

@ -121,6 +121,7 @@ comptime {
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
exportStrFn(str.getCapacity, "capacity");
exportStrFn(str.startsWith, "starts_with");
exportStrFn(str.startsWithScalar, "starts_with_scalar");
exportStrFn(str.endsWith, "ends_with");

View file

@ -1210,6 +1210,10 @@ pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
return string.len();
}
pub fn getCapacity(string: RocStr) callconv(.C) usize {
return string.getCapacity();
}
pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr {
const slice = string.asSlice()[start .. start + length];
return RocStr.fromSlice(slice);

View file

@ -314,6 +314,7 @@ pub const STR_STR_SPLIT: &str = "roc_builtins.str.str_split";
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_CAPACITY: &str = "roc_builtins.str.capacity";
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";

View file

@ -124,6 +124,7 @@ map_symbol_to_lowlevel_and_arity! {
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
StrToNum; STR_TO_NUM; 1,
StrGetCapacity; STR_CAPACITY; 1,
ListLen; LIST_LEN; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1,
@ -142,6 +143,7 @@ map_symbol_to_lowlevel_and_arity! {
ListSublist; LIST_SUBLIST_LOWLEVEL; 3,
ListDropAt; LIST_DROP_AT; 2,
ListSwap; LIST_SWAP; 3,
ListGetCapacity; LIST_CAPACITY; 1,
NumAdd; NUM_ADD; 2,
NumAddWrap; NUM_ADD_WRAP; 2,

View file

@ -220,6 +220,9 @@ pub enum Expr {
lambda_set_variables: Vec<LambdaSet>,
},
// Opaque as a function, e.g. @Id as a shorthand for \x -> @Id x
OpaqueWrapFunction(OpaqueWrapFunctionData),
// Test
Expect {
loc_condition: Box<Loc<Expr>>,
@ -269,6 +272,9 @@ impl Expr {
args_count: 0,
},
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
&Self::OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
Category::OpaqueWrap(opaque_name)
}
Self::Expect { .. } => Category::Expect,
// these nodes place no constraints on the expression's type
@ -380,6 +386,76 @@ impl AccessorData {
}
}
/// An opaque wrapper like `@Foo`, which is equivalent to `\p -> @Foo p`
/// These are desugared to closures, but we distinguish them so we can have
/// better error messages during constraint generation.
#[derive(Clone, Debug)]
pub struct OpaqueWrapFunctionData {
pub opaque_name: Symbol,
pub opaque_var: Variable,
// The following fields help link the concrete opaque type; see
// `Expr::OpaqueRef` for more info on how they're used.
pub specialized_def_type: Type,
pub type_arguments: Vec<OptAbleVar>,
pub lambda_set_variables: Vec<LambdaSet>,
pub function_name: Symbol,
pub function_var: Variable,
pub argument_var: Variable,
pub closure_var: Variable,
}
impl OpaqueWrapFunctionData {
pub fn to_closure_data(self, argument_symbol: Symbol) -> ClosureData {
let OpaqueWrapFunctionData {
opaque_name,
opaque_var,
specialized_def_type,
type_arguments,
lambda_set_variables,
function_name,
function_var,
argument_var,
closure_var,
} = self;
// IDEA: convert
//
// @Foo
//
// into
//
// (\p -> @Foo p)
let body = Expr::OpaqueRef {
opaque_var,
name: opaque_name,
argument: Box::new((argument_var, Loc::at_zero(Expr::Var(argument_symbol)))),
specialized_def_type: Box::new(specialized_def_type),
type_arguments,
lambda_set_variables,
};
let loc_body = Loc::at_zero(body);
let arguments = vec![(
argument_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(argument_symbol)),
)];
ClosureData {
function_type: function_var,
closure_type: closure_var,
return_type: opaque_var,
name: function_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(loc_body),
}
}
}
#[derive(Clone, Debug)]
pub struct Field {
pub var: Variable,
@ -835,15 +911,41 @@ pub fn canonicalize_expr<'a>(
Output::default(),
)
}
ast::Expr::OpaqueRef(opaque_ref) => {
ast::Expr::OpaqueRef(name) => {
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
// arguments are handled in the Apply branch.
let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
region,
(*opaque_ref).into(),
));
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
// Treat this as a function \payload -> @Opaque payload
match scope.lookup_opaque_ref(name, region) {
Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error.clone()));
(RuntimeError(runtime_error), Output::default())
}
Ok((name, opaque_def)) => {
let mut output = Output::default();
output.references.insert_type_lookup(name);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
let fn_symbol = scope.gen_unique_symbol();
(
OpaqueWrapFunction(OpaqueWrapFunctionData {
opaque_name: name,
opaque_var: var_store.fresh(),
specialized_def_type,
type_arguments,
lambda_set_variables,
function_name: fn_symbol,
function_var: var_store.fresh(),
argument_var: var_store.fresh(),
closure_var: var_store.fresh(),
}),
output,
)
}
}
}
ast::Expr::Expect(condition, continuation) => {
let mut output = Output::default();
@ -1420,7 +1522,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ AbilityMember(..)
| other @ RunLowLevel { .. }
| other @ TypedHole { .. }
| other @ ForeignCall { .. } => other,
| other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_) => other,
List {
elem_var,
@ -2387,7 +2490,8 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
| Expr::SingleQuote(_)
| Expr::EmptyRecord
| Expr::TypedHole(_)
| Expr::RuntimeError(_) => {}
| Expr::RuntimeError(_)
| Expr::OpaqueWrapFunction(_) => {}
}
}

View file

@ -962,5 +962,6 @@ fn fix_values_captured_in_closure_expr(
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
OpaqueWrapFunction(_) => {}
}
}

View file

@ -7,7 +7,10 @@ use roc_types::subs::Variable;
use crate::{
abilities::AbilitiesStore,
def::{Annotation, Declaration, Def},
expr::{self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field},
expr::{
self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field,
OpaqueWrapFunctionData,
},
pattern::{DestructType, Pattern, RecordDestruct},
};
@ -220,6 +223,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
Expr::Accessor(AccessorData { .. }) => { /* terminal */ }
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
Expr::Update {
record_var: _,
ext_var: _,

View file

@ -13,7 +13,7 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{
AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, Field,
FunctionDef, WhenBranch,
FunctionDef, OpaqueWrapFunctionData, WhenBranch,
};
use roc_can::pattern::Pattern;
use roc_can::traverse::symbols_introduced_from_pattern;
@ -1114,6 +1114,100 @@ pub fn constrain_expr(
constraints.exists_many(vars, [arg_con, opaque_con, link_type_variables_con])
}
OpaqueWrapFunction(OpaqueWrapFunctionData {
opaque_name,
opaque_var,
specialized_def_type,
type_arguments,
lambda_set_variables,
function_name,
function_var,
argument_var,
closure_var,
}) => {
let argument_type = Type::Variable(*argument_var);
let opaque_type = Type::Alias {
symbol: *opaque_name,
type_arguments: type_arguments
.iter()
.map(|v| OptAbleType {
typ: Type::Variable(v.var),
opt_ability: v.opt_ability,
})
.collect(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(argument_type.clone()),
kind: AliasKind::Opaque,
};
// Tie the opaque type to the opaque_var
let opaque_con = constraints.equal_types_var(
*opaque_var,
Expected::NoExpectation(opaque_type),
Category::OpaqueWrap(*opaque_name),
region,
);
// Tie the type of the value wrapped by the opaque to the opaque's type variables.
let link_type_variables_con = constraints.equal_types(
argument_type.clone(),
Expected::NoExpectation((*specialized_def_type).clone()),
Category::OpaqueArg,
region,
);
let lambda_set = Type::ClosureTag {
name: *function_name,
captures: vec![],
ambient_function: *function_var,
};
let closure_type = Type::Variable(*closure_var);
let opaque_type = Type::Variable(*opaque_var);
let function_type = Type::Function(
vec![argument_type],
Box::new(closure_type),
Box::new(opaque_type),
);
let cons = [
opaque_con,
link_type_variables_con,
constraints.equal_types_var(
*closure_var,
NoExpectation(lambda_set),
Category::OpaqueWrap(*opaque_name),
region,
),
constraints.equal_types_var(
*function_var,
Expected::NoExpectation(function_type),
Category::OpaqueWrap(*opaque_name),
region,
),
constraints.equal_types_var(
*function_var,
expected,
Category::OpaqueWrap(*opaque_name),
region,
),
];
let mut vars = vec![*argument_var, *opaque_var];
// Also add the fresh variables we created for the type argument and lambda sets
vars.extend(type_arguments.iter().map(|v| v.var));
vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
vars.extend([*function_var, *closure_var]);
constraints.exists_many(vars, cons)
}
RunLowLevel { args, ret_var, op } => {
// This is a modified version of what we do for function calls.

View file

@ -3,9 +3,9 @@ use crate::llvm::bitcode::{
call_str_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build_list::{
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_concat, list_drop_at,
list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend,
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_capacity, list_concat,
list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4,
list_prepend, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, pass_update_mode,
};
use crate::llvm::build_str::{str_from_float, str_from_int};
@ -5524,12 +5524,19 @@ fn run_low_level<'a, 'ctx, 'env>(
call_bitcode_fn(env, &[string, index], bitcode::STR_GET_SCALAR_UNSAFE)
}
StrCountUtf8Bytes => {
// Str.countGraphemes : Str -> Nat
// Str.countUtf8Bytes : 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)
}
StrGetCapacity => {
// Str.capacity : Str -> Nat
debug_assert_eq!(args.len(), 1);
let string = load_symbol(scope, &args[0]);
call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY)
}
StrSubstringUnsafe => {
// Str.substringUnsafe : Str, Nat, Nat -> Str
debug_assert_eq!(args.len(), 3);
@ -5577,13 +5584,21 @@ fn run_low_level<'a, 'ctx, 'env>(
call_str_bitcode_fn(env, &[string], bitcode::STR_TRIM_RIGHT)
}
ListLen => {
// List.len : List * -> Int
// List.len : List * -> Nat
debug_assert_eq!(args.len(), 1);
let arg = load_symbol(scope, &args[0]);
list_len(env.builder, arg.into_struct_value()).into()
}
ListGetCapacity => {
// List.capacity : List * -> Nat
debug_assert_eq!(args.len(), 1);
let arg = load_symbol(scope, &args[0]);
list_capacity(env.builder, arg.into_struct_value()).into()
}
ListWithCapacity => {
// List.withCapacity : Nat -> List a
debug_assert_eq!(args.len(), 1);

View file

@ -10,6 +10,7 @@ use crate::llvm::build_list::{layout_width, pass_as_opaque};
use crate::llvm::convert::{basic_type_from_layout, zig_dict_type};
use crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::BasicType;
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue};
@ -679,6 +680,28 @@ pub fn dict_values<'a, 'ctx, 'env>(
)
}
/// Dict.capacity : Dict * * -> Nat
pub fn dict_capacity<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "dict_capacity")
.unwrap()
.into_int_value()
}
/// Set.capacity : Set * -> Nat
pub fn set_capacity<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "set_capacity")
.unwrap()
.into_int_value()
}
#[allow(clippy::too_many_arguments)]
pub fn set_from_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -351,7 +351,7 @@ fn bounds_check_comparison<'ctx>(
builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check")
}
/// List.len : List elem -> Int
/// List.len : List * -> Nat
pub fn list_len<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
@ -362,6 +362,17 @@ pub fn list_len<'ctx>(
.into_int_value()
}
/// List.capacity : List * -> Nat
pub fn list_capacity<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "list_capacity")
.unwrap()
.into_int_value()
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
pub fn list_sort_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -243,6 +243,7 @@ impl<'a> LowLevelCall<'a> {
StrCountUtf8Bytes => {
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES)
}
StrGetCapacity => self.load_args_and_call_zig(backend, bitcode::STR_CAPACITY),
StrToNum => {
let number_layout = match self.ret_layout {
Layout::Struct { field_layouts, .. } => field_layouts[0],
@ -308,7 +309,37 @@ impl<'a> LowLevelCall<'a> {
let (local_id, offset) =
location.local_and_offset(backend.storage.stack_frame_pointer);
backend.code_builder.get_local(local_id);
backend.code_builder.i32_load(Align::Bytes4, offset + 4);
// List is stored as (pointer, length, capacity),
// with each of those fields being 4 bytes on wasm.
// So the length is 4 bytes after the start of the struct.
//
// WRAPPER_LEN represents the index of the length field
// (which is 1 as of the writing of this comment). If the field order
// ever changes, WRAPPER_LEN should be updated and this logic should
// continue to work even though this comment may become inaccurate.
backend
.code_builder
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN));
}
_ => internal_error!("invalid storage for List"),
},
ListGetCapacity => match backend.storage.get(&self.arguments[0]) {
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) =
location.local_and_offset(backend.storage.stack_frame_pointer);
backend.code_builder.get_local(local_id);
// List is stored as (pointer, length, capacity),
// with each of those fields being 4 bytes on wasm.
// So the capacity is 8 bytes after the start of the struct.
//
// WRAPPER_CAPACITY represents the index of the capacity field
// (which is 2 as of the writing of this comment). If the field order
// ever changes, WRAPPER_CAPACITY should be updated and this logic should
// continue to work even though this comment may become inaccurate.
backend
.code_builder
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_CAPACITY));
}
_ => internal_error!("invalid storage for List"),
},

View file

@ -29,6 +29,7 @@ pub enum LowLevel {
StrReserve,
StrAppendScalar,
StrGetScalarUnsafe,
StrGetCapacity,
ListLen,
ListWithCapacity,
ListReserve,
@ -46,6 +47,7 @@ pub enum LowLevel {
ListDropAt,
ListSwap,
ListIsUnique,
ListGetCapacity,
NumAdd,
NumAddWrap,
NumAddChecked,
@ -246,7 +248,9 @@ map_symbol_to_lowlevel! {
StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE,
StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE,
StrToNum <= STR_TO_NUM,
StrGetCapacity <= STR_CAPACITY,
ListLen <= LIST_LEN,
ListGetCapacity <= LIST_CAPACITY,
ListWithCapacity <= LIST_WITH_CAPACITY,
ListReserve <= LIST_RESERVE,
ListIsUnique <= LIST_IS_UNIQUE,

View file

@ -1215,6 +1215,7 @@ define_builtins! {
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
47 STR_TO_NUM: "strToNum"
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
49 STR_CAPACITY: "capacity"
}
5 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -1285,6 +1286,7 @@ define_builtins! {
65 LIST_RESERVE: "reserve"
66 LIST_APPEND_UNSAFE: "appendUnsafe"
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
68 LIST_CAPACITY: "capacity"
}
6 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias
@ -1320,6 +1322,7 @@ define_builtins! {
14 DICT_REMOVE_ALL: "removeAll" // difference
15 DICT_WITH_CAPACITY: "withCapacity"
16 DICT_CAPACITY: "capacity"
}
8 SET: "Set" => {
0 SET_SET: "Set" // the Set.Set type alias
@ -1337,6 +1340,7 @@ define_builtins! {
12 SET_WALK_USER_FUNCTION: "#walk_user_function"
13 SET_CONTAINS: "contains"
14 SET_TO_DICT: "toDict"
15 SET_CAPACITY: "capacity"
}
9 BOX: "Box" => {
0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type

View file

@ -874,9 +874,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// - other refcounted arguments are Borrowed
match op {
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => {
arena.alloc_slice_copy(&[borrowed])
}
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes
| StrGetCapacity | ListGetCapacity => arena.alloc_slice_copy(&[borrowed]),
ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),

View file

@ -1,6 +1,6 @@
use roc_can::{
def::Def,
expr::{AccessorData, ClosureData, Expr, Field, WhenBranch},
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranch},
};
use roc_module::ident::{Lowercase, TagName};
use roc_types::{
@ -543,6 +543,29 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
lambda_set_variables: lambda_set_variables.clone(),
},
OpaqueWrapFunction(OpaqueWrapFunctionData {
opaque_name,
opaque_var,
specialized_def_type,
type_arguments,
lambda_set_variables,
function_name,
function_var,
argument_var,
closure_var,
}) => OpaqueWrapFunction(OpaqueWrapFunctionData {
opaque_name: *opaque_name,
opaque_var: sub!(*opaque_var),
function_name: *function_name,
function_var: sub!(*function_var),
argument_var: sub!(*argument_var),
closure_var: sub!(*closure_var),
// The following three are only used for constraining
specialized_def_type: specialized_def_type.clone(),
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
}),
Expect {
loc_condition,
loc_continuation,

View file

@ -4387,6 +4387,60 @@ pub fn with_hole<'a>(
}
}
OpaqueWrapFunction(wrap_fn_data) => {
let opaque_var = wrap_fn_data.opaque_var;
let arg_symbol = env.unique_symbol();
let ClosureData {
name,
function_type,
arguments,
loc_body,
..
} = wrap_fn_data.to_closure_data(arg_symbol);
match procs.insert_anonymous(
env,
LambdaName::no_niche(name),
function_type,
arguments,
*loc_body,
CapturedSymbols::None,
opaque_var,
layout_cache,
) {
Ok(_) => {
let raw_layout = return_on_layout_error!(
env,
layout_cache.raw_from_var(env.arena, function_type, env.subs),
"Expr::OpaqueWrapFunction"
);
match raw_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
let lambda_name =
find_lambda_name(env, layout_cache, lambda_set, name, &[]);
construct_closure_data(
env,
lambda_set,
lambda_name,
&[],
assigned,
hole,
)
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
internal_error!("should not be a thunk!")
}
}
}
Err(_error) => Stmt::RuntimeError(
"TODO convert anonymous function error to a RuntimeError string",
),
}
}
Update {
record_var,
symbol: structure,

View file

@ -1809,7 +1809,7 @@ impl<'a> Builtin<'a> {
pub const STR_WORDS: u32 = 3;
pub const LIST_WORDS: u32 = 3;
/// Layout of collection wrapper for List and Str - a struct of (pointer, length, capacity).
/// Layout of collection wrapper for List, Str, Dict, and Set - a struct of (pointer, length, capacity).
pub const WRAPPER_PTR: u32 = 0;
pub const WRAPPER_LEN: u32 = 1;
pub const WRAPPER_CAPACITY: u32 = 2;

View file

@ -7341,4 +7341,30 @@ mod solve_expr {
"Rose I64",
);
}
#[test]
fn opaque_wrap_function() {
infer_eq_without_problem(
indoc!(
r#"
A := U8
List.map [1, 2, 3] @A
"#
),
"List A",
);
}
#[test]
fn opaque_wrap_function_with_inferred_arg() {
infer_eq_without_problem(
indoc!(
r#"
A a := a
List.map [1u8, 2u8, 3u8] @A
"#
),
"List (A U8)",
);
}
}

View file

@ -2,7 +2,7 @@
use roc_can::def::Def;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, WhenBranch};
use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch};
use roc_can::pattern::{Pattern, RecordDestruct};
use roc_module::symbol::Interns;
@ -191,6 +191,9 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
} => expr(c, CallArg, f, &loc_expr.value)
.append(f.text(format!(".{}", field.as_str())))
.group(),
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
f.text(format!("@{}", opaque_name.as_str(c.interns)))
}
Accessor(_) => todo!(),
Update { .. } => todo!(),
Tag { .. } => todo!(),

View file

@ -1,10 +1,10 @@
procedure Dict.1 ():
let Dict.101 : List {[], []} = Array [];
ret Dict.101;
let Dict.102 : List {[], []} = Array [];
ret Dict.102;
procedure Dict.7 (Dict.95):
let Dict.100 : U64 = CallByName List.6 Dict.95;
ret Dict.100;
procedure Dict.7 (Dict.96):
let Dict.101 : U64 = CallByName List.6 Dict.96;
ret Dict.101;
procedure List.6 (#Attr.2):
let List.295 : U64 = lowlevel ListLen #Attr.2;

View file

@ -1,8 +1,8 @@
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
procedure List.2 (List.79, List.80):
let List.300 : U64 = CallByName List.6 List.79;
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
if List.297 then
let List.299 : {} = CallByName List.60 List.78 List.79;
let List.299 : {} = CallByName List.60 List.79 List.80;
let List.298 : [C {}, C {}] = TagId(1) List.299;
ret List.298;
else

View file

@ -1,7 +1,7 @@
procedure List.4 (List.89, List.90):
procedure List.4 (List.90, List.91):
let List.297 : U64 = 1i64;
let List.296 : List U8 = CallByName List.65 List.89 List.297;
let List.295 : List U8 = CallByName List.66 List.296 List.90;
let List.296 : List U8 = CallByName List.65 List.90 List.297;
let List.295 : List U8 = CallByName List.66 List.296 List.91;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):

View file

@ -2,11 +2,11 @@ procedure Bool.7 (#Attr.2, #Attr.3):
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.9;
procedure List.2 (List.78, List.79):
let List.309 : U64 = CallByName List.6 List.78;
let List.305 : Int1 = CallByName Num.22 List.79 List.309;
procedure List.2 (List.79, List.80):
let List.309 : U64 = CallByName List.6 List.79;
let List.305 : Int1 = CallByName Num.22 List.80 List.309;
if List.305 then
let List.307 : I64 = CallByName List.60 List.78 List.79;
let List.307 : I64 = CallByName List.60 List.79 List.80;
let List.306 : [C {}, C I64] = TagId(1) List.307;
ret List.306;
else
@ -22,15 +22,15 @@ procedure List.60 (#Attr.2, #Attr.3):
let List.308 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.308;
procedure List.9 (List.206):
procedure List.9 (List.207):
let List.302 : U64 = 0i64;
let List.295 : [C {}, C I64] = CallByName List.2 List.206 List.302;
let List.295 : [C {}, C I64] = CallByName List.2 List.207 List.302;
let List.299 : U8 = 1i64;
let List.300 : U8 = GetTagId List.295;
let List.301 : Int1 = lowlevel Eq List.299 List.300;
if List.301 then
let List.207 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
let List.296 : [C Int1, C I64] = TagId(1) List.207;
let List.208 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
let List.296 : [C Int1, C I64] = TagId(1) List.208;
ret List.296;
else
let List.298 : Int1 = true;
@ -41,27 +41,27 @@ procedure Num.22 (#Attr.2, #Attr.3):
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Str.27 (Str.88):
let Str.194 : [C Int1, C I64] = CallByName Str.67 Str.88;
ret Str.194;
procedure Str.27 (Str.89):
let Str.195 : [C Int1, C I64] = CallByName Str.68 Str.89;
ret Str.195;
procedure Str.47 (#Attr.2):
let Str.202 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.202;
let Str.203 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.203;
procedure Str.67 (Str.189):
let Str.190 : {I64, U8} = CallByName Str.47 Str.189;
let Str.200 : U8 = StructAtIndex 1 Str.190;
let Str.201 : U8 = 0i64;
let Str.197 : Int1 = CallByName Bool.7 Str.200 Str.201;
if Str.197 then
let Str.199 : I64 = StructAtIndex 0 Str.190;
let Str.198 : [C Int1, C I64] = TagId(1) Str.199;
ret Str.198;
procedure Str.68 (Str.190):
let Str.191 : {I64, U8} = CallByName Str.47 Str.190;
let Str.201 : U8 = StructAtIndex 1 Str.191;
let Str.202 : U8 = 0i64;
let Str.198 : Int1 = CallByName Bool.7 Str.201 Str.202;
if Str.198 then
let Str.200 : I64 = StructAtIndex 0 Str.191;
let Str.199 : [C Int1, C I64] = TagId(1) Str.200;
ret Str.199;
else
let Str.196 : Int1 = false;
let Str.195 : [C Int1, C I64] = TagId(0) Str.196;
ret Str.195;
let Str.197 : Int1 = false;
let Str.196 : [C Int1, C I64] = TagId(0) Str.197;
ret Str.196;
procedure Test.0 ():
let Test.4 : Int1 = true;

View file

@ -1,7 +1,7 @@
procedure List.4 (List.89, List.90):
procedure List.4 (List.90, List.91):
let List.297 : U64 = 1i64;
let List.296 : List I64 = CallByName List.65 List.89 List.297;
let List.295 : List I64 = CallByName List.66 List.296 List.90;
let List.296 : List I64 = CallByName List.65 List.90 List.297;
let List.295 : List I64 = CallByName List.66 List.296 List.91;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):

View file

@ -1,7 +1,7 @@
procedure List.4 (List.89, List.90):
procedure List.4 (List.90, List.91):
let List.297 : U64 = 1i64;
let List.296 : List I64 = CallByName List.65 List.89 List.297;
let List.295 : List I64 = CallByName List.66 List.296 List.90;
let List.296 : List I64 = CallByName List.65 List.90 List.297;
let List.295 : List I64 = CallByName List.66 List.296 List.91;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):

View file

@ -1,18 +1,18 @@
procedure List.3 (List.86, List.87, List.88):
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
procedure List.3 (List.87, List.88, List.89):
let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
let List.297 : List I64 = StructAtIndex 0 List.298;
inc List.297;
dec List.298;
ret List.297;
procedure List.57 (List.83, List.84, List.85):
let List.303 : U64 = CallByName List.6 List.83;
let List.300 : Int1 = CallByName Num.22 List.84 List.303;
procedure List.57 (List.84, List.85, List.86):
let List.303 : U64 = CallByName List.6 List.84;
let List.300 : Int1 = CallByName Num.22 List.85 List.303;
if List.300 then
let List.301 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
let List.301 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
ret List.301;
else
let List.299 : {List I64, I64} = Struct {List.83, List.85};
let List.299 : {List I64, I64} = Struct {List.84, List.86};
ret List.299;
procedure List.6 (#Attr.2):

View file

@ -1,8 +1,8 @@
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
procedure List.2 (List.79, List.80):
let List.300 : U64 = CallByName List.6 List.79;
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
if List.297 then
let List.299 : I64 = CallByName List.60 List.78 List.79;
let List.299 : I64 = CallByName List.60 List.79 List.80;
let List.298 : [C {}, C I64] = TagId(1) List.299;
ret List.298;
else

View file

@ -1,8 +1,8 @@
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
procedure List.2 (List.79, List.80):
let List.300 : U64 = CallByName List.6 List.79;
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
if List.297 then
let List.299 : Str = CallByName List.60 List.78 List.79;
let List.299 : Str = CallByName List.60 List.79 List.80;
let List.298 : [C {}, C Str] = TagId(1) List.299;
ret List.298;
else
@ -27,12 +27,12 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.257;
procedure Str.16 (#Attr.2, #Attr.3):
let Str.194 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.194;
let Str.195 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.195;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.195;
let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.196;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -1,8 +1,8 @@
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
procedure List.2 (List.79, List.80):
let List.300 : U64 = CallByName List.6 List.79;
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
if List.297 then
let List.299 : Str = CallByName List.60 List.78 List.79;
let List.299 : Str = CallByName List.60 List.79 List.80;
let List.298 : [C {}, C Str] = TagId(1) List.299;
ret List.298;
else
@ -29,8 +29,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.257;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.195;
let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.196;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -1,18 +1,18 @@
procedure List.3 (List.86, List.87, List.88):
let List.296 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
procedure List.3 (List.87, List.88, List.89):
let List.296 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
let List.295 : List I64 = StructAtIndex 0 List.296;
inc List.295;
dec List.296;
ret List.295;
procedure List.57 (List.83, List.84, List.85):
let List.301 : U64 = CallByName List.6 List.83;
let List.298 : Int1 = CallByName Num.22 List.84 List.301;
procedure List.57 (List.84, List.85, List.86):
let List.301 : U64 = CallByName List.6 List.84;
let List.298 : Int1 = CallByName Num.22 List.85 List.301;
if List.298 then
let List.299 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
let List.299 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
ret List.299;
else
let List.297 : {List I64, I64} = Struct {List.83, List.85};
let List.297 : {List I64, I64} = Struct {List.84, List.86};
ret List.297;
procedure List.6 (#Attr.2):

View file

@ -7,9 +7,9 @@ procedure List.28 (#Attr.2, #Attr.3):
decref #Attr.2;
ret List.297;
procedure List.54 (List.201):
procedure List.54 (List.202):
let List.296 : {} = Struct {};
let List.295 : List I64 = CallByName List.28 List.201 List.296;
let List.295 : List I64 = CallByName List.28 List.202 List.296;
ret List.295;
procedure Num.46 (#Attr.2, #Attr.3):

View file

@ -1,8 +1,8 @@
procedure List.2 (List.78, List.79):
let List.310 : U64 = CallByName List.6 List.78;
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
procedure List.2 (List.79, List.80):
let List.310 : U64 = CallByName List.6 List.79;
let List.307 : Int1 = CallByName Num.22 List.80 List.310;
if List.307 then
let List.309 : I64 = CallByName List.60 List.78 List.79;
let List.309 : I64 = CallByName List.60 List.79 List.80;
let List.308 : [C {}, C I64] = TagId(1) List.309;
ret List.308;
else
@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79):
let List.305 : [C {}, C I64] = TagId(0) List.306;
ret List.305;
procedure List.3 (List.86, List.87, List.88):
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
procedure List.3 (List.87, List.88, List.89):
let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
let List.297 : List I64 = StructAtIndex 0 List.298;
inc List.297;
dec List.298;
ret List.297;
procedure List.57 (List.83, List.84, List.85):
let List.315 : U64 = CallByName List.6 List.83;
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
procedure List.57 (List.84, List.85, List.86):
let List.315 : U64 = CallByName List.6 List.84;
let List.312 : Int1 = CallByName Num.22 List.85 List.315;
if List.312 then
let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
let List.313 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
ret List.313;
else
let List.311 : {List I64, I64} = Struct {List.83, List.85};
let List.311 : {List I64, I64} = Struct {List.84, List.86};
ret List.311;
procedure List.6 (#Attr.2):

View file

@ -1,8 +1,8 @@
procedure List.2 (List.78, List.79):
let List.310 : U64 = CallByName List.6 List.78;
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
procedure List.2 (List.79, List.80):
let List.310 : U64 = CallByName List.6 List.79;
let List.307 : Int1 = CallByName Num.22 List.80 List.310;
if List.307 then
let List.309 : I64 = CallByName List.60 List.78 List.79;
let List.309 : I64 = CallByName List.60 List.79 List.80;
let List.308 : [C {}, C I64] = TagId(1) List.309;
ret List.308;
else
@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79):
let List.305 : [C {}, C I64] = TagId(0) List.306;
ret List.305;
procedure List.3 (List.86, List.87, List.88):
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
procedure List.3 (List.87, List.88, List.89):
let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
let List.297 : List I64 = StructAtIndex 0 List.298;
inc List.297;
dec List.298;
ret List.297;
procedure List.57 (List.83, List.84, List.85):
let List.315 : U64 = CallByName List.6 List.83;
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
procedure List.57 (List.84, List.85, List.86):
let List.315 : U64 = CallByName List.6 List.84;
let List.312 : Int1 = CallByName Num.22 List.85 List.315;
if List.312 then
let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
let List.313 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
ret List.313;
else
let List.311 : {List I64, I64} = Struct {List.83, List.85};
let List.311 : {List I64, I64} = Struct {List.84, List.86};
ret List.311;
procedure List.6 (#Attr.2):

View file

@ -12,7 +12,7 @@ pub static WILDCARD: &str = "*";
static EMPTY_RECORD: &str = "{}";
static EMPTY_TAG_UNION: &str = "[]";
/// Rerquirements for parentheses.
/// Requirements for parentheses.
///
/// If we're inside a function (that is, this is either an argument or a return
/// value), we may need to use parens. Examples:

View file

@ -1179,3 +1179,16 @@ fn render_nullable_unwrapped_passing_through_alias() {
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
)
}
#[test]
fn opaque_wrap_function() {
expect_success(
indoc!(
r#"
A a := a
List.map [1u8, 2u8, 3u8] @A
"#
),
"[1, 2, 3] : List (A U8)",
);
}

View file

@ -7483,14 +7483,14 @@ All branches in an `if` must have the same type!
// and checking it during can. The reason the error appears is because it is parsed as
// Apply(Error(OtherModule), [@Age, 21])
@r###"
OPAQUE TYPE NOT APPLIED /code/proj/Main.roc
OPAQUE TYPE NOT DEFINED /code/proj/Main.roc
This opaque type is not applied to an argument:
The opaque type Age referenced here is not defined:
4 OtherModule.@Age 21
^^^^
Note: Opaque types always wrap exactly one argument!
Note: It looks like there are no opaque types declared in this scope yet!
SYNTAX PROBLEM /code/proj/Main.roc
@ -9551,4 +9551,30 @@ All branches in an `if` must have the same type!
an instance of this opaque type by doing @Age 23.
"###
);
test_report!(
opaque_wrap_function_mismatch,
indoc!(
r#"
A := U8
List.map [1u16, 2u16, 3u16] @A
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
The 2nd argument to `map` is not what I expect:
5 List.map [1u16, 2u16, 3u16] @A
^^
This A opaque wrapping has the type:
U8 -> A
But `map` needs the 2nd argument to be:
U16 -> A
"###
);
}

View file

@ -0,0 +1,373 @@
interface Path
exposes [
Path,
PathComponent,
WindowsRoot,
toComponents,
walkComponents,
fromStr,
fromBytes,
]
imports []
## You can canonicalize a [Path] using [Path.canonicalize].
##
## Comparing canonical paths is often more reliable than comparing raw ones.
## For example, `Path.fromStr "foo/bar/../baz" == Path.fromStr "foo/baz"` will return `False`,
## because those are different paths even though their canonical equivalents would be equal.
##
## Also note that canonicalization reads from the file system (in order to resolve symbolic
## links, and to convert relative paths into absolute ones). This means that it is not only
## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
## may give different answers. An example of a way this could happen is if a symbolic link
## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
##
## Similarly, remember that canonical paths are not guaranteed to refer to a valid file. They
## might have referred to one when they were canonicalized, but that file may have moved or
## been deleted since the canonical path was created. So you might [canonicalize] a [Path],
## and then immediately use that [Path] to read a file from disk, and still get back an error
## because something relevant changed on the filesystem between the two operations.
##
## Also note that different filesystems have different rules for syntactically valid paths.
## Suppose you're on a machine with two disks, one formatted as ext4 and another as FAT32.
## It's possible to list the contents of a directory on the ext4 disk, and get a [CanPath] which
## is valid on that disk, but invalid on the other disk. One way this could happen is if the
## directory on the ext4 disk has a filename containing a `:` in it. `:` is allowed in ext4
## paths but is considered invalid in FAT32 paths.
Path := [
# We store these separately for two reasons:
# 1. If I'm calling an OS API, passing a path I got from the OS is definitely safe.
# However, passing a Path I got from a RocStr might be unsafe; it may contain \0
# characters, which would result in the operation happening on a totally different
# path. As such, we need to check for \0s and fail without calling the OS API if we
# find one in the path.
# 2. If I'm converting the Path to a Str, doing that conversion on a Path that was
# created from a RocStr needs no further processing. However, if it came from the OS,
# then we need to know what charset to assume it had, in order to decode it properly.
# These come from the OS (e.g. when reading a directory, calling `canonicalize`,
# or reading an environment variable - which, incidentally, are nul-terminated),
# so we know they are both nul-terminated and do not contain interior nuls.
# As such, they can be passed directly to OS APIs.
#
# Note that the nul terminator byte is right after the end of the length (into the
# unused capacity), so this can both be compared directly to other `List U8`s that
# aren't nul-terminated, while also being able to be passed directly to OS APIs.
FromOperatingSystem (List U8),
# These come from userspace (e.g. Path.fromBytes), so they need to be checked for interior
# nuls and then nul-terminated before the host can pass them to OS APIs.
ArbitraryBytes (List U8),
# This was created as a RocStr, so it might have interior nul bytes but it's definitely UTF-8.
# That means we can `toStr` it trivially, but have to validate before sending it to OS
# APIs that expect a nul-terminated `char*`.
#
# Note that both UNIX and Windows APIs will accept UTF-8, because on Windows the host calls
# `_setmbcp(_MB_CP_UTF8);` to set the process's Code Page to UTF-8 before doing anything else.
# See https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page#-a-vs--w-apis
# and https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmbcp?view=msvc-170
# for more details on the UTF-8 Code Page in Windows.
FromStr Str,
]
## Note that the path may not be valid depending on the filesystem where it is used.
## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not
## on FAT ones. So if you have multiple disks on the same machine, but they have
## different filesystems, then this path could be valid on one but invalid on another!
##
## It's safest to assume paths are invalid (even syntactically) until given to an operation
## which uses them to open a file. If that operation succeeds, then the path was valid
## (at the time). Otherwise, error handling can happen for that operation rather than validating
## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.).
fromStr : Str -> Path
fromStr = \str -> @Path (FromStr str)
## Not all filesystems use Unicode paths. This function can be used to create a path which
## is not valid Unicode (like a Roc [Str] is), but which is valid for a particular filesystem.
##
## Note that if the list contains any `0` bytes, sending this path to any file operations
## (e.g. [File.read] or [WriteStream.openPath]) will fail.
fromBytes : List U8 -> Path
fromBytes = \bytes -> @Path (ArbitraryBytes bytes)
## Note that canonicalization reads from the file system (in order to resolve symbolic
## links, and to convert relative paths into absolute ones). This means that it is not only
## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
## may give different answers. An example of a way this could happen is if a symbolic link
## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
##
## Returns an effect type of `[Metadata, Cwd]` because it can resolve symbolic links
## and can access the current working directory by turning a relative path into an
## absolute one (which can prepend the absolute path of the current working directory to
## the relative path).
canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
## Unfortunately, operating system paths do not include information about which charset
## they were originally encoded with. It's most common (but not guaranteed) that they will
## have been encoded with the same charset as the operating system's curent locale (which
## typically does not change after it is set during installation of the OS), so
## this should convert a [Path] to a valid string as long as the path was created
## with the given [Charset]. (Use [Env.charset] to get the current system charset.)
##
## For a conversion to [Str] that is lossy but does not return a [Result], see
## [displayUtf8].
toInner : Path -> [Str Str, Bytes (List U8)]
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
## and converts it to a string using [Str.displayUtf8].
##
## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens,
## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
## instead of returning an error. As such, it's rarely a good idea to use the [Str] returned
## by this function for any purpose other than displaying it to a user.
##
## When you don't know for sure what a path's encoding is, UTF-8 is a popular guess because
## it's the default on UNIX and also is the encoding used in Roc strings. This platform also
## automatically runs applications under the [UTF-8 code page](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)
## on Windows.
##
## Converting paths to strings can be an unreliable operation, because operating systems
## don't record the paths' encodings. This means it's possible for the path to have been
## encoded with a different character set than UTF-8 even if UTF-8 is the system default,
## which means when [displayUtf8] converts them to a string, the string may include gibberish.
## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863)
##
## If you happen to know the [Charset] that was used to encode the path, you can use
## [toStrUsingCharset] instead of [displayUtf8].
displayUtf8 : Path -> Str
displayUtf8 = \@Path path ->
when path is
FromStr str -> str
NoInteriorNul bytes | ArbitraryBytes bytes ->
Str.displayUtf8 bytes
isEq : Path, Path -> Bool
isEq = \@Path p1, @Path p2 ->
when p1 is
NoInteriorNul bytes1 | ArbitraryBytes bytes1 ->
when p2 is
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2
# We can't know the encoding that was originally used in the path, so we convert
# the string to bytes and see if those bytes are equal to the path's bytes.
#
# This may sound unreliable, but it's how all paths are compared; since the OS
# doesn't record which encoding was used to encode the path name, the only
# reasonable# definition for path equality is byte-for-byte equality.
FromStr str2 -> Str.isEqUtf8 str2 bytes1
FromStr str1 ->
when p2 is
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2
FromStr str2 -> str1 == str2
compare : Path, Path -> [Lt, Eq, Gt]
compare = \@Path p1, @Path p2 ->
when p1 is
NoInteriorNul bytes1 | ArbitraryBytes bytes1 ->
when p2 is
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2
FromStr str2 -> Str.compareUtf8 str2 bytes1
FromStr str1 ->
when p2 is
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare str1 bytes2
FromStr str2 -> str1 == str2
## ## Path Components
PathComponent : [
ParentDir, # e.g. ".." on UNIX or Windows
CurrentDir, # e.g. "." on UNIX
Named Str, # e.g. "stuff" on UNIX
DirSep Str, # e.g. "/" on UNIX, "\" or "/" on Windows. Or, sometimes, "¥" on Windows - see
# https://docs.microsoft.com/en-us/windows/win32/intl/character-sets-used-in-file-names
#
# This is included as an option so if you're transforming part of a path,
# you can write back whatever separator was originally used.
]
## Note that a root of Slash (`/`) has different meanings on UNIX and on Windows.
## * On UNIX, `/` at the beginning of the path refers to the filesystem root, and means the path is absolute.
## * On Windows, `/` at the beginning of the path refers to the current disk drive, and means the path is relative.
PathRoot : [
WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows
Slash,
None,
]
# TODO see https://doc.rust-lang.org/std/path/enum.Prefix.html
WindowsRoot : []
## Returns the root of the path.
root : Path -> PathRoot
components : Path -> { root : PathRoot, components : List PathComponent }
## Walk over the path's [components].
walk :
Path,
# None means it's a relative path
(PathRoot -> state),
(state, PathComponent -> state)
-> state
## Returns the path without its last [`component`](#components).
##
## If the path was empty or contained only a [root](#PathRoot), returns the original path.
dropLast : Path -> Path
# TODO see https://doc.rust-lang.org/std/path/struct.Path.html#method.join for
# the definition of the term "adjoin" - should we use that term?
append : Path, Path -> Path
append = \@Path prefix, @Path suffix ->
content =
when prefix is
NoInteriorNul prefixBytes ->
when suffix is
NoInteriorNul suffixBytes ->
# Neither prefix nor suffix had interior nuls, so the answer won't either
List.append prefixBytes suffixBytes
|> NoInteriorNul
ArbitraryBytes suffixBytes ->
List.append prefixBytes suffixBytes
|> ArbitraryBytes
FromStr suffixStr ->
# Append suffixStr by writing it to the end of prefixBytes
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|> ArbitraryBytes
ArbitraryBytes prefixBytes ->
when suffix is
ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes ->
List.append prefixBytes suffixBytes
|> ArbitraryBytes
FromStr suffixStr ->
# Append suffixStr by writing it to the end of prefixBytes
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|> ArbitraryBytes
FromStr prefixStr ->
when suffix is
ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes ->
List.append (Str.toUtf8 prefixStr) suffixBytes
|> ArbitraryBytes
FromStr suffixStr ->
Str.append prefixStr suffixStr
|> FromStr
@Path content
appendStr : Path, Str -> Path
appendStr = \@Path prefix, suffixStr ->
content =
when prefix is
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
# Append suffixStr by writing it to the end of prefixBytes
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|> ArbitraryBytes
FromStr prefixStr ->
Str.append prefixStr suffixStr
|> FromStr
@Path content
## Returns `True` if the first path begins with the second.
startsWith : Path, Path -> Bool
startsWith = \@Path path, @Path prefix ->
when path is
NoInteriorNul pathBytes | ArbitraryBytes pathBytes ->
when prefix is
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
List.startsWith pathBytes prefixBytes
FromStr prefixStr ->
strLen = Str.byteCount str
if strLen == List.len pathBytes then
# Grab the first N bytes of the list, where N = byte length of string.
bytesPrefix = List.takeAt pathBytes 0 strLen
# Compare the two for equality.
Str.isEqUtf8 prefixStr bytesPrefix
else
False
FromStr pathStr ->
when prefix is
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
Str.startsWithUtf8 pathStr prefixBytes
FromStr prefixStr ->
Str.startsWith pathStr prefixStr
## Returns `True` if the first path ends with the second.
endsWith : Path, Path -> Bool
endsWith = \@Path path, @Path prefix ->
when path is
NoInteriorNul pathBytes | ArbitraryBytes pathBytes ->
when suffix is
NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes ->
List.endsWith pathBytes suffixBytes
FromStr suffixStr ->
strLen = Str.byteCount suffixStr
if strLen == List.len pathBytes then
# Grab the last N bytes of the list, where N = byte length of string.
bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen
# Compare the two for equality.
Str.startsWithUtf8 suffixStr bytesSuffix
else
False
FromStr pathStr ->
when suffix is
NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes ->
Str.endsWithUtf8 pathStr suffixBytes
FromStr suffixStr ->
Str.endsWith pathStr suffixStr
# TODO https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
# TODO idea: what if it's File.openRead and File.openWrite? And then e.g. File.metadata,
# File.isDir, etc.
## If the last component of this path has no `.`, appends `.` followed by the given string.
## Otherwise, replaces everything after the last `.` with the given string.
##
## Examples:
##
## Path.fromStr "foo/bar/baz" |> Path.withExtension "txt" # foo/bar/baz.txt
## Path.fromStr "foo/bar/baz." |> Path.withExtension "txt" # foo/bar/baz.txt
## Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt" # foo/bar/baz.txt
withExtension : Path, Str -> Path
withExtension = \@Path path, extension ->
when path is
NoInteriorNul bytes | ArbitraryBytes bytes ->
beforeDot =
when List.splitLast '.' is
Ok { before } -> before
Err NotFound -> list
beforeDot
|> List.reserve (1 + List.len bytes)
|> List.append '.'
|> List.concat bytes
FromStr str ->
beforeDot =
when Str.splitLast str "." is
Ok { before } -> before
Err NotFound -> str
beforeDot
|> Str.reserve (1 + Str.byteCount str)
|> Str.append "."
|> Str.concat str
# NOTE: no withExtensionBytes because it's too narrow. If you really need to get some
# non-Unicode in there, do it with

View file

@ -19,7 +19,9 @@ wget https://github.com/rtfeldman/elm-css/files/8849069/roc-source-code.zip
# TODO: When roc repo is public, download it from nightly builds.
wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip
unzip deploy.zip
mv mock-repl-deploy/* .
# Explicitly list the filenames, failing the build if they're not found
mv mock-repl-deploy/roc_repl_wasm.js repl/
mv mock-repl-deploy/roc_repl_wasm_bg.wasm repl/
rmdir mock-repl-deploy
rm deploy.zip

View file

@ -3,7 +3,7 @@
<html>
<head>
<title>Roc REPL</title>
<link rel="stylesheet" href="./repl.css" />
<link rel="stylesheet" href="/repl/repl.css" />
</head>
<body>
@ -28,6 +28,6 @@
></textarea>
</section>
</div>
<script type="module" src="./repl.js"></script>
<script type="module" src="/repl/repl.js"></script>
</body>
</html>

View file

@ -41,7 +41,7 @@ const repl = {
// Initialise
repl.elemSourceInput.addEventListener("change", onInputChange);
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
roc_repl_wasm.default('./roc_repl_wasm_bg.wasm').then((instance) => {
roc_repl_wasm.default('/repl/roc_repl_wasm_bg.wasm').then((instance) => {
repl.compiler = instance;
});