mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge branch 'trunk' into shrink-type-error
This commit is contained in:
commit
892447b08c
24 changed files with 744 additions and 502 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3140,6 +3140,7 @@ dependencies = [
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_load",
|
"roc_load",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
|
"roc_parse",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use libloading::Library;
|
use libloading::Library;
|
||||||
use roc_collections::all::MutMap;
|
|
||||||
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::TagName;
|
||||||
use roc_module::operator::CalledVia;
|
use roc_module::operator::CalledVia;
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_mono::ir::ProcLayout;
|
use roc_mono::ir::ProcLayout;
|
||||||
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
|
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
|
||||||
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
|
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable};
|
||||||
use roc_types::types::RecordField;
|
|
||||||
|
|
||||||
struct Env<'a, 'env> {
|
struct Env<'a, 'env> {
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
|
@ -155,9 +153,12 @@ fn jit_to_ast_help<'a>(
|
||||||
Content::Structure(FlatType::Record(fields, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
Ok(struct_to_ast(env, ptr, field_layouts, fields))
|
Ok(struct_to_ast(env, ptr, field_layouts, fields))
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::EmptyRecord) => {
|
Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
|
||||||
Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default()))
|
env,
|
||||||
}
|
ptr,
|
||||||
|
field_layouts,
|
||||||
|
&RecordFields::with_capacity(0),
|
||||||
|
)),
|
||||||
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
debug_assert_eq!(tags.len(), 1);
|
debug_assert_eq!(tags.len(), 1);
|
||||||
|
|
||||||
|
@ -437,7 +438,7 @@ fn ptr_to_ast<'a>(
|
||||||
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[])
|
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[])
|
||||||
}
|
}
|
||||||
Content::Structure(FlatType::EmptyRecord) => {
|
Content::Structure(FlatType::EmptyRecord) => {
|
||||||
struct_to_ast(env, ptr, &[], &MutMap::default())
|
struct_to_ast(env, ptr, &[], &RecordFields::with_capacity(0))
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
unreachable!(
|
unreachable!(
|
||||||
|
@ -556,26 +557,15 @@ fn struct_to_ast<'a>(
|
||||||
env: &Env<'a, '_>,
|
env: &Env<'a, '_>,
|
||||||
ptr: *const u8,
|
ptr: *const u8,
|
||||||
field_layouts: &'a [Layout<'a>],
|
field_layouts: &'a [Layout<'a>],
|
||||||
fields: &MutMap<Lowercase, RecordField<Variable>>,
|
sorted_fields: &RecordFields,
|
||||||
) -> Expr<'a> {
|
) -> Expr<'a> {
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let subs = env.subs;
|
let subs = env.subs;
|
||||||
let mut output = Vec::with_capacity_in(field_layouts.len(), arena);
|
let mut output = Vec::with_capacity_in(field_layouts.len(), arena);
|
||||||
|
|
||||||
// The fields, sorted alphabetically
|
|
||||||
let mut sorted_fields = {
|
|
||||||
let mut vec = fields
|
|
||||||
.iter()
|
|
||||||
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
|
|
||||||
|
|
||||||
vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
|
||||||
|
|
||||||
vec
|
|
||||||
};
|
|
||||||
|
|
||||||
if sorted_fields.len() == 1 {
|
if sorted_fields.len() == 1 {
|
||||||
// this is a 1-field wrapper record around another record or 1-tag tag union
|
// this is a 1-field wrapper record around another record or 1-tag tag union
|
||||||
let (label, field) = sorted_fields.pop().unwrap();
|
let (label, field) = sorted_fields.into_iter().next().unwrap();
|
||||||
|
|
||||||
let inner_content = env.subs.get_content_without_compacting(field.into_inner());
|
let inner_content = env.subs.get_content_without_compacting(field.into_inner());
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ pub fn build_zig_host(
|
||||||
.args(&[
|
.args(&[
|
||||||
"build-obj",
|
"build-obj",
|
||||||
zig_host_src,
|
zig_host_src,
|
||||||
&emit_bin,
|
emit_bin,
|
||||||
"--pkg-begin",
|
"--pkg-begin",
|
||||||
"str",
|
"str",
|
||||||
zig_str_path,
|
zig_str_path,
|
||||||
|
|
|
@ -73,7 +73,7 @@ xor : Bool, Bool -> Bool
|
||||||
## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
|
## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
|
||||||
## 3. Records are equal if all their fields are equal.
|
## 3. Records are equal if all their fields are equal.
|
||||||
## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
|
## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
|
||||||
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See [Num.isNaN] for more about *NaN*.
|
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
|
||||||
##
|
##
|
||||||
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
|
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
|
||||||
## accept arguments whose types contain functions.
|
## accept arguments whose types contain functions.
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub struct Env<'a> {
|
||||||
/// are assumed to be relative to this path.
|
/// are assumed to be relative to this path.
|
||||||
pub home: ModuleId,
|
pub home: ModuleId,
|
||||||
|
|
||||||
pub dep_idents: MutMap<ModuleId, IdentIds>,
|
pub dep_idents: &'a MutMap<ModuleId, IdentIds>,
|
||||||
|
|
||||||
pub module_ids: &'a ModuleIds,
|
pub module_ids: &'a ModuleIds,
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ pub struct Env<'a> {
|
||||||
impl<'a> Env<'a> {
|
impl<'a> Env<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
dep_idents: &'a MutMap<ModuleId, IdentIds>,
|
||||||
module_ids: &'a ModuleIds,
|
module_ids: &'a ModuleIds,
|
||||||
exposed_ident_ids: IdentIds,
|
exposed_ident_ids: IdentIds,
|
||||||
) -> Env<'a> {
|
) -> Env<'a> {
|
||||||
|
|
|
@ -47,7 +47,7 @@ pub fn canonicalize_module_defs<'a, F>(
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
module_ids: &ModuleIds,
|
module_ids: &ModuleIds,
|
||||||
exposed_ident_ids: IdentIds,
|
exposed_ident_ids: IdentIds,
|
||||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
dep_idents: &'a MutMap<ModuleId, IdentIds>,
|
||||||
aliases: MutMap<Symbol, Alias>,
|
aliases: MutMap<Symbol, Alias>,
|
||||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||||
exposed_symbols: &MutSet<Symbol>,
|
exposed_symbols: &MutSet<Symbol>,
|
||||||
|
@ -139,7 +139,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (defs, _scope, output, symbols_introduced) = canonicalize_defs(
|
let (defs, scope, output, symbols_introduced) = canonicalize_defs(
|
||||||
&mut env,
|
&mut env,
|
||||||
Output::default(),
|
Output::default(),
|
||||||
var_store,
|
var_store,
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
||||||
|
|
||||||
let mut scope = Scope::new(home, &mut var_store);
|
let mut scope = Scope::new(home, &mut var_store);
|
||||||
let dep_idents = IdentIds::exposed_builtins(0);
|
let dep_idents = IdentIds::exposed_builtins(0);
|
||||||
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default());
|
||||||
let (loc_expr, output) = canonicalize_expr(
|
let (loc_expr, output) = canonicalize_expr(
|
||||||
&mut env,
|
&mut env,
|
||||||
&mut var_store,
|
&mut var_store,
|
||||||
|
|
|
@ -524,6 +524,7 @@ fn start_phase<'a>(
|
||||||
var_store,
|
var_store,
|
||||||
imported_modules,
|
imported_modules,
|
||||||
declarations,
|
declarations,
|
||||||
|
dep_idents,
|
||||||
..
|
..
|
||||||
} = constrained;
|
} = constrained;
|
||||||
|
|
||||||
|
@ -536,6 +537,7 @@ fn start_phase<'a>(
|
||||||
imported_modules,
|
imported_modules,
|
||||||
&mut state.exposed_types,
|
&mut state.exposed_types,
|
||||||
state.stdlib,
|
state.stdlib,
|
||||||
|
dep_idents,
|
||||||
declarations,
|
declarations,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -621,6 +623,7 @@ pub struct LoadedModule {
|
||||||
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
||||||
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
|
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
|
||||||
pub exposed_to_host: MutMap<Symbol, Variable>,
|
pub exposed_to_host: MutMap<Symbol, Variable>,
|
||||||
|
pub dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
pub exposed_aliases: MutMap<Symbol, Alias>,
|
pub exposed_aliases: MutMap<Symbol, Alias>,
|
||||||
pub exposed_values: Vec<Symbol>,
|
pub exposed_values: Vec<Symbol>,
|
||||||
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||||
|
@ -676,6 +679,7 @@ struct ConstrainedModule {
|
||||||
constraint: Constraint,
|
constraint: Constraint,
|
||||||
ident_ids: IdentIds,
|
ident_ids: IdentIds,
|
||||||
var_store: VarStore,
|
var_store: VarStore,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
module_timing: ModuleTiming,
|
module_timing: ModuleTiming,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -759,6 +763,7 @@ enum Msg<'a> {
|
||||||
solved_module: SolvedModule,
|
solved_module: SolvedModule,
|
||||||
solved_subs: Solved<Subs>,
|
solved_subs: Solved<Subs>,
|
||||||
decls: Vec<Declaration>,
|
decls: Vec<Declaration>,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
module_timing: ModuleTiming,
|
module_timing: ModuleTiming,
|
||||||
unused_imports: MutMap<ModuleId, Region>,
|
unused_imports: MutMap<ModuleId, Region>,
|
||||||
},
|
},
|
||||||
|
@ -767,6 +772,7 @@ enum Msg<'a> {
|
||||||
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||||
exposed_values: Vec<Symbol>,
|
exposed_values: Vec<Symbol>,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||||
},
|
},
|
||||||
FoundSpecializations {
|
FoundSpecializations {
|
||||||
|
@ -985,6 +991,7 @@ enum BuildTask<'a> {
|
||||||
constraint: Constraint,
|
constraint: Constraint,
|
||||||
var_store: VarStore,
|
var_store: VarStore,
|
||||||
declarations: Vec<Declaration>,
|
declarations: Vec<Declaration>,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
unused_imports: MutMap<ModuleId, Region>,
|
unused_imports: MutMap<ModuleId, Region>,
|
||||||
},
|
},
|
||||||
BuildPendingSpecializations {
|
BuildPendingSpecializations {
|
||||||
|
@ -1516,6 +1523,7 @@ where
|
||||||
exposed_vars_by_symbol,
|
exposed_vars_by_symbol,
|
||||||
exposed_aliases_by_symbol,
|
exposed_aliases_by_symbol,
|
||||||
exposed_values,
|
exposed_values,
|
||||||
|
dep_idents,
|
||||||
documentation,
|
documentation,
|
||||||
} => {
|
} => {
|
||||||
// We're done! There should be no more messages pending.
|
// We're done! There should be no more messages pending.
|
||||||
|
@ -1534,6 +1542,7 @@ where
|
||||||
exposed_values,
|
exposed_values,
|
||||||
exposed_aliases_by_symbol,
|
exposed_aliases_by_symbol,
|
||||||
exposed_vars_by_symbol,
|
exposed_vars_by_symbol,
|
||||||
|
dep_idents,
|
||||||
documentation,
|
documentation,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -1892,6 +1901,7 @@ fn update<'a>(
|
||||||
solved_module,
|
solved_module,
|
||||||
solved_subs,
|
solved_subs,
|
||||||
decls,
|
decls,
|
||||||
|
dep_idents,
|
||||||
mut module_timing,
|
mut module_timing,
|
||||||
mut unused_imports,
|
mut unused_imports,
|
||||||
} => {
|
} => {
|
||||||
|
@ -1949,6 +1959,7 @@ fn update<'a>(
|
||||||
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
||||||
exposed_values: solved_module.exposed_symbols,
|
exposed_values: solved_module.exposed_symbols,
|
||||||
exposed_aliases_by_symbol: solved_module.aliases,
|
exposed_aliases_by_symbol: solved_module.aliases,
|
||||||
|
dep_idents,
|
||||||
documentation,
|
documentation,
|
||||||
})
|
})
|
||||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||||
|
@ -2283,6 +2294,7 @@ fn finish(
|
||||||
exposed_values: Vec<Symbol>,
|
exposed_values: Vec<Symbol>,
|
||||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||||
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||||
) -> LoadedModule {
|
) -> LoadedModule {
|
||||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||||
|
@ -2316,6 +2328,7 @@ fn finish(
|
||||||
can_problems: state.module_cache.can_problems,
|
can_problems: state.module_cache.can_problems,
|
||||||
type_problems: state.module_cache.type_problems,
|
type_problems: state.module_cache.type_problems,
|
||||||
declarations_by_id: state.declarations_by_id,
|
declarations_by_id: state.declarations_by_id,
|
||||||
|
dep_idents,
|
||||||
exposed_aliases: exposed_aliases_by_symbol,
|
exposed_aliases: exposed_aliases_by_symbol,
|
||||||
exposed_values,
|
exposed_values,
|
||||||
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
||||||
|
@ -3234,6 +3247,7 @@ impl<'a> BuildTask<'a> {
|
||||||
imported_modules: MutMap<ModuleId, Region>,
|
imported_modules: MutMap<ModuleId, Region>,
|
||||||
exposed_types: &mut SubsByModule,
|
exposed_types: &mut SubsByModule,
|
||||||
stdlib: &StdLib,
|
stdlib: &StdLib,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
declarations: Vec<Declaration>,
|
declarations: Vec<Declaration>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let home = module.module_id;
|
let home = module.module_id;
|
||||||
|
@ -3261,6 +3275,7 @@ impl<'a> BuildTask<'a> {
|
||||||
constraint,
|
constraint,
|
||||||
var_store,
|
var_store,
|
||||||
declarations,
|
declarations,
|
||||||
|
dep_idents,
|
||||||
module_timing,
|
module_timing,
|
||||||
unused_imports,
|
unused_imports,
|
||||||
}
|
}
|
||||||
|
@ -3276,6 +3291,7 @@ fn run_solve<'a>(
|
||||||
constraint: Constraint,
|
constraint: Constraint,
|
||||||
mut var_store: VarStore,
|
mut var_store: VarStore,
|
||||||
decls: Vec<Declaration>,
|
decls: Vec<Declaration>,
|
||||||
|
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||||
unused_imports: MutMap<ModuleId, Region>,
|
unused_imports: MutMap<ModuleId, Region>,
|
||||||
) -> Msg<'a> {
|
) -> Msg<'a> {
|
||||||
// We have more constraining work to do now, so we'll add it to our timings.
|
// We have more constraining work to do now, so we'll add it to our timings.
|
||||||
|
@ -3330,6 +3346,7 @@ fn run_solve<'a>(
|
||||||
solved_subs,
|
solved_subs,
|
||||||
ident_ids,
|
ident_ids,
|
||||||
decls,
|
decls,
|
||||||
|
dep_idents,
|
||||||
solved_module,
|
solved_module,
|
||||||
module_timing,
|
module_timing,
|
||||||
unused_imports,
|
unused_imports,
|
||||||
|
@ -3478,7 +3495,8 @@ fn fabricate_effects_module<'a>(
|
||||||
let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
|
let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
|
||||||
|
|
||||||
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
|
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
|
||||||
let mut can_env = roc_can::env::Env::new(module_id, dep_idents, &module_ids, exposed_ident_ids);
|
let mut can_env =
|
||||||
|
roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids);
|
||||||
|
|
||||||
let effect_symbol = scope
|
let effect_symbol = scope
|
||||||
.introduce(
|
.introduce(
|
||||||
|
@ -3611,6 +3629,7 @@ fn fabricate_effects_module<'a>(
|
||||||
var_store,
|
var_store,
|
||||||
constraint,
|
constraint,
|
||||||
ident_ids: module_output.ident_ids,
|
ident_ids: module_output.ident_ids,
|
||||||
|
dep_idents,
|
||||||
module_timing,
|
module_timing,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3690,7 +3709,7 @@ where
|
||||||
module_id,
|
module_id,
|
||||||
module_ids,
|
module_ids,
|
||||||
exposed_ident_ids,
|
exposed_ident_ids,
|
||||||
dep_idents,
|
&dep_idents,
|
||||||
aliases,
|
aliases,
|
||||||
exposed_imports,
|
exposed_imports,
|
||||||
&exposed_symbols,
|
&exposed_symbols,
|
||||||
|
@ -3738,6 +3757,7 @@ where
|
||||||
var_store,
|
var_store,
|
||||||
constraint,
|
constraint,
|
||||||
ident_ids: module_output.ident_ids,
|
ident_ids: module_output.ident_ids,
|
||||||
|
dep_idents,
|
||||||
module_timing,
|
module_timing,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4210,6 +4230,7 @@ where
|
||||||
var_store,
|
var_store,
|
||||||
ident_ids,
|
ident_ids,
|
||||||
declarations,
|
declarations,
|
||||||
|
dep_idents,
|
||||||
unused_imports,
|
unused_imports,
|
||||||
} => Ok(run_solve(
|
} => Ok(run_solve(
|
||||||
module,
|
module,
|
||||||
|
@ -4219,6 +4240,7 @@ where
|
||||||
constraint,
|
constraint,
|
||||||
var_store,
|
var_store,
|
||||||
declarations,
|
declarations,
|
||||||
|
dep_idents,
|
||||||
unused_imports,
|
unused_imports,
|
||||||
)),
|
)),
|
||||||
BuildPendingSpecializations {
|
BuildPendingSpecializations {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation};
|
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation};
|
||||||
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
|
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
|
||||||
use crate::ident::{lowercase_ident, parse_ident_help, Ident};
|
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||||
use crate::keyword;
|
use crate::keyword;
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
|
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
|
||||||
|
@ -2097,7 +2097,7 @@ fn if_expr_help<'a>(
|
||||||
/// 5. A reserved keyword (e.g. `if ` or `case `), meaning we should do something else.
|
/// 5. A reserved keyword (e.g. `if ` or `case `), meaning we should do something else.
|
||||||
|
|
||||||
fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a>> {
|
fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a>> {
|
||||||
crate::ident::parse_ident_help
|
crate::ident::parse_ident
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -2238,7 +2238,7 @@ fn record_field_help<'a>(
|
||||||
fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> {
|
fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> {
|
||||||
specialize(
|
specialize(
|
||||||
|_, r, c| ERecord::Updateable(r, c),
|
|_, r, c| ERecord::Updateable(r, c),
|
||||||
map_with_arena!(parse_ident_help, ident_to_expr),
|
map_with_arena!(parse_ident, ident_to_expr),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,13 +138,10 @@ macro_rules! advance_state {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ident_help<'a>(
|
pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
|
||||||
arena: &'a Bump,
|
|
||||||
state: State<'a>,
|
|
||||||
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
|
|
||||||
let initial = state;
|
let initial = state;
|
||||||
|
|
||||||
match parse_ident_help_help(arena, state) {
|
match parse_ident_help(arena, state) {
|
||||||
Ok((progress, ident, state)) => {
|
Ok((progress, ident, state)) => {
|
||||||
if let Ident::Access { module_name, parts } = ident {
|
if let Ident::Access { module_name, parts } = ident {
|
||||||
if module_name.is_empty() {
|
if module_name.is_empty() {
|
||||||
|
@ -526,7 +523,7 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ident_help_help<'a>(
|
fn parse_ident_help<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
mut state: State<'a>,
|
mut state: State<'a>,
|
||||||
) -> ParseResult<'a, Ident<'a>, BadIdent> {
|
) -> ParseResult<'a, Ident<'a>, BadIdent> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::ast::Pattern;
|
use crate::ast::Pattern;
|
||||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||||
use crate::ident::{lowercase_ident, parse_ident_help, Ident};
|
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||||
use crate::parser::Progress::{self, *};
|
use crate::parser::Progress::{self, *};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
|
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
|
||||||
|
@ -172,8 +172,7 @@ fn loc_ident_pattern_help<'a>(
|
||||||
let original_state = state;
|
let original_state = state;
|
||||||
|
|
||||||
let (_, loc_ident, state) =
|
let (_, loc_ident, state) =
|
||||||
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help))
|
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident)).parse(arena, state)?;
|
||||||
.parse(arena, state)?;
|
|
||||||
|
|
||||||
match loc_ident.value {
|
match loc_ident.value {
|
||||||
Ident::GlobalTag(tag) => {
|
Ident::GlobalTag(tag) => {
|
||||||
|
|
|
@ -145,7 +145,7 @@ pub fn can_expr_with<'a>(
|
||||||
|
|
||||||
let mut scope = Scope::new(home, &mut var_store);
|
let mut scope = Scope::new(home, &mut var_store);
|
||||||
let dep_idents = IdentIds::exposed_builtins(0);
|
let dep_idents = IdentIds::exposed_builtins(0);
|
||||||
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default());
|
||||||
let (loc_expr, output) = canonicalize_expr(
|
let (loc_expr, output) = canonicalize_expr(
|
||||||
&mut env,
|
&mut env,
|
||||||
&mut var_store,
|
&mut var_store,
|
||||||
|
|
|
@ -2156,8 +2156,8 @@ mod test_reporting {
|
||||||
This is usually a typo. Here are the `x` fields that are most similar:
|
This is usually a typo. Here are the `x` fields that are most similar:
|
||||||
|
|
||||||
{ fo : Num c
|
{ fo : Num c
|
||||||
, foobar : Num a
|
, foobar : Num d
|
||||||
, bar : Num e
|
, bar : Num a
|
||||||
, baz : Num b
|
, baz : Num b
|
||||||
, ...
|
, ...
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use roc_region::all::{Located, Region};
|
||||||
use roc_types::solved_types::Solved;
|
use roc_types::solved_types::Solved;
|
||||||
use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable};
|
use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable};
|
||||||
use roc_types::types::Type::{self, *};
|
use roc_types::types::Type::{self, *};
|
||||||
use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField};
|
use roc_types::types::{Alias, Category, ErrorType, PatternCategory};
|
||||||
use roc_unify::unify::unify;
|
use roc_unify::unify::unify;
|
||||||
use roc_unify::unify::Unified::*;
|
use roc_unify::unify::Unified::*;
|
||||||
|
|
||||||
|
@ -673,13 +673,8 @@ fn type_to_variable(
|
||||||
let mut field_vars = MutMap::with_capacity_and_hasher(fields.len(), default_hasher());
|
let mut field_vars = MutMap::with_capacity_and_hasher(fields.len(), default_hasher());
|
||||||
|
|
||||||
for (field, field_type) in fields {
|
for (field, field_type) in fields {
|
||||||
use RecordField::*;
|
let field_var =
|
||||||
|
field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ));
|
||||||
let field_var = match field_type {
|
|
||||||
Required(typ) => Required(type_to_variable(subs, rank, pools, cached, typ)),
|
|
||||||
Optional(typ) => Optional(type_to_variable(subs, rank, pools, cached, typ)),
|
|
||||||
Demanded(typ) => Demanded(type_to_variable(subs, rank, pools, cached, typ)),
|
|
||||||
};
|
|
||||||
|
|
||||||
field_vars.insert(field.clone(), field_var);
|
field_vars.insert(field.clone(), field_var);
|
||||||
}
|
}
|
||||||
|
@ -694,7 +689,8 @@ fn type_to_variable(
|
||||||
Err((new, _)) => new,
|
Err((new, _)) => new,
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = Content::Structure(FlatType::Record(field_vars, new_ext_var));
|
let record_fields = field_vars.into_iter().collect();
|
||||||
|
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
|
@ -1084,14 +1080,9 @@ fn adjust_rank_content(
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
|
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
|
||||||
|
|
||||||
for var in fields.values() {
|
for var in fields.iter_variables() {
|
||||||
rank = rank.max(adjust_rank(
|
rank =
|
||||||
subs,
|
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
|
||||||
young_mark,
|
|
||||||
visit_mark,
|
|
||||||
group_rank,
|
|
||||||
var.into_inner(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rank
|
rank
|
||||||
|
@ -1238,14 +1229,8 @@ fn instantiate_rigids_help(
|
||||||
EmptyRecord | EmptyTagUnion | Erroneous(_) => {}
|
EmptyRecord | EmptyTagUnion | Erroneous(_) => {}
|
||||||
|
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
for (_, field) in fields {
|
for var in fields.iter_variables() {
|
||||||
use RecordField::*;
|
instantiate_rigids_help(subs, max_rank, pools, *var);
|
||||||
|
|
||||||
match field {
|
|
||||||
Demanded(var) => instantiate_rigids_help(subs, max_rank, pools, var),
|
|
||||||
Required(var) => instantiate_rigids_help(subs, max_rank, pools, var),
|
|
||||||
Optional(var) => instantiate_rigids_help(subs, max_rank, pools, var),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate_rigids_help(subs, max_rank, pools, ext_var);
|
instantiate_rigids_help(subs, max_rank, pools, ext_var);
|
||||||
|
@ -1381,31 +1366,12 @@ fn deep_copy_var_help(
|
||||||
|
|
||||||
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
||||||
|
|
||||||
Record(fields, ext_var) => {
|
Record(mut fields, ext_var) => {
|
||||||
let mut new_fields = MutMap::default();
|
for var in fields.iter_variables_mut() {
|
||||||
|
*var = deep_copy_var_help(subs, max_rank, pools, *var);
|
||||||
for (label, field) in fields {
|
|
||||||
use RecordField::*;
|
|
||||||
|
|
||||||
let new_field = match field {
|
|
||||||
Demanded(var) => {
|
|
||||||
Demanded(deep_copy_var_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
Required(var) => {
|
|
||||||
Required(deep_copy_var_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
Optional(var) => {
|
|
||||||
Optional(deep_copy_var_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new_fields.insert(label, new_field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Record(
|
Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
|
||||||
new_fields,
|
|
||||||
deep_copy_var_help(subs, max_rank, pools, ext_var),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagUnion(tags, ext_var) => {
|
TagUnion(tags, ext_var) => {
|
||||||
|
|
|
@ -152,19 +152,9 @@ fn find_names_needed(
|
||||||
|
|
||||||
find_names_needed(*ret_var, subs, roots, root_appearances, names_taken);
|
find_names_needed(*ret_var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
Structure(Record(fields, ext_var)) => {
|
Structure(Record(sorted_fields, ext_var)) => {
|
||||||
let mut sorted_fields: Vec<_> = fields.iter().collect();
|
for var in sorted_fields.iter_variables() {
|
||||||
|
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
||||||
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
|
||||||
|
|
||||||
for (_, field) in sorted_fields {
|
|
||||||
find_names_needed(
|
|
||||||
field.into_inner(),
|
|
||||||
subs,
|
|
||||||
roots,
|
|
||||||
root_appearances,
|
|
||||||
names_taken,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
|
find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
|
||||||
|
@ -420,7 +410,10 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
|
||||||
use crate::types::{gather_fields, RecordStructure};
|
use crate::types::{gather_fields, RecordStructure};
|
||||||
|
|
||||||
// If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them
|
// If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them
|
||||||
let RecordStructure { fields, ext } = gather_fields(subs, fields, *ext_var);
|
let RecordStructure {
|
||||||
|
fields: sorted_fields,
|
||||||
|
ext,
|
||||||
|
} = gather_fields(subs, fields.clone(), *ext_var);
|
||||||
let ext_var = ext;
|
let ext_var = ext;
|
||||||
|
|
||||||
if fields.is_empty() {
|
if fields.is_empty() {
|
||||||
|
@ -428,12 +421,6 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
|
||||||
} else {
|
} else {
|
||||||
buf.push_str("{ ");
|
buf.push_str("{ ");
|
||||||
|
|
||||||
// Sort the fields so they always end up in the same order.
|
|
||||||
let mut sorted_fields = Vec::with_capacity(fields.len());
|
|
||||||
|
|
||||||
sorted_fields.extend(fields);
|
|
||||||
sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
|
|
||||||
let mut any_written_yet = false;
|
let mut any_written_yet = false;
|
||||||
|
|
||||||
for (label, field_var) in sorted_fields {
|
for (label, field_var) in sorted_fields {
|
||||||
|
@ -592,7 +579,7 @@ pub fn chase_ext_record(
|
||||||
match subs.get_content_without_compacting(var) {
|
match subs.get_content_without_compacting(var) {
|
||||||
Structure(Record(sub_fields, sub_ext)) => {
|
Structure(Record(sub_fields, sub_ext)) => {
|
||||||
for (field_name, record_field) in sub_fields {
|
for (field_name, record_field) in sub_fields {
|
||||||
fields.insert(field_name.clone(), *record_field);
|
fields.insert(field_name.clone(), record_field);
|
||||||
}
|
}
|
||||||
|
|
||||||
chase_ext_record(subs, *sub_ext, fields)
|
chase_ext_record(subs, *sub_ext, fields)
|
||||||
|
|
|
@ -400,13 +400,8 @@ impl SolvedType {
|
||||||
let mut new_fields = Vec::with_capacity(fields.len());
|
let mut new_fields = Vec::with_capacity(fields.len());
|
||||||
|
|
||||||
for (label, field) in fields {
|
for (label, field) in fields {
|
||||||
use RecordField::*;
|
let solved_type =
|
||||||
|
field.map(|var| Self::from_var_help(subs, recursion_vars, *var));
|
||||||
let solved_type = match field {
|
|
||||||
Optional(var) => Optional(Self::from_var_help(subs, recursion_vars, *var)),
|
|
||||||
Required(var) => Required(Self::from_var_help(subs, recursion_vars, *var)),
|
|
||||||
Demanded(var) => Demanded(Self::from_var_help(subs, recursion_vars, *var)),
|
|
||||||
};
|
|
||||||
|
|
||||||
new_fields.push((label.clone(), solved_type));
|
new_fields.push((label.clone(), solved_type));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt};
|
||||||
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter::{once, Iterator};
|
use std::iter::{once, Extend, FromIterator, Iterator, Map, Zip};
|
||||||
use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
|
use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
@ -535,7 +536,7 @@ impl From<usize> for Rank {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone)]
|
||||||
pub struct Descriptor {
|
pub struct Descriptor {
|
||||||
pub content: Content,
|
pub content: Content,
|
||||||
pub rank: Rank,
|
pub rank: Rank,
|
||||||
|
@ -573,7 +574,7 @@ impl From<Content> for Descriptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Content {
|
pub enum Content {
|
||||||
/// A type variable which the user did not name in an annotation,
|
/// A type variable which the user did not name in an annotation,
|
||||||
///
|
///
|
||||||
|
@ -619,11 +620,11 @@ impl Content {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum FlatType {
|
pub enum FlatType {
|
||||||
Apply(Symbol, Vec<Variable>),
|
Apply(Symbol, Vec<Variable>),
|
||||||
Func(Vec<Variable>, Variable, Variable),
|
Func(Vec<Variable>, Variable, Variable),
|
||||||
Record(MutMap<Lowercase, RecordField<Variable>>, Variable),
|
Record(RecordFields, Variable),
|
||||||
TagUnion(MutMap<TagName, Vec<Variable>>, Variable),
|
TagUnion(MutMap<TagName, Vec<Variable>>, Variable),
|
||||||
FunctionOrTagUnion(TagName, Symbol, Variable),
|
FunctionOrTagUnion(TagName, Symbol, Variable),
|
||||||
RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable),
|
RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable),
|
||||||
|
@ -640,6 +641,232 @@ pub enum Builtin {
|
||||||
EmptyRecord,
|
EmptyRecord,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RecordFields {
|
||||||
|
field_names: Vec<Lowercase>,
|
||||||
|
variables: Vec<Variable>,
|
||||||
|
field_type: Vec<RecordField<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecordFields {
|
||||||
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
field_names: Vec::with_capacity(capacity),
|
||||||
|
variables: Vec::with_capacity(capacity),
|
||||||
|
field_type: Vec::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let answer = self.field_names.len();
|
||||||
|
|
||||||
|
debug_assert_eq!(answer, self.variables.len());
|
||||||
|
debug_assert_eq!(answer, self.field_type.len());
|
||||||
|
|
||||||
|
answer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_variables(&self) -> impl Iterator<Item = &Variable> {
|
||||||
|
self.variables.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_variables_mut(&mut self) -> impl Iterator<Item = &mut Variable> {
|
||||||
|
self.variables.iter_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> {
|
||||||
|
self.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_only_optional_fields(&self) -> bool {
|
||||||
|
self.field_type
|
||||||
|
.iter()
|
||||||
|
.all(|field| matches!(field, RecordField::Optional(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(mut vec: Vec<(Lowercase, RecordField<Variable>)>) -> Self {
|
||||||
|
// we assume there are no duplicate field names in there
|
||||||
|
vec.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2));
|
||||||
|
|
||||||
|
Self::from_sorted_vec(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_sorted_vec(vec: Vec<(Lowercase, RecordField<Variable>)>) -> Self {
|
||||||
|
let mut result = RecordFields::with_capacity(vec.len());
|
||||||
|
|
||||||
|
result.extend(vec);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge(self, other: Self) -> Self {
|
||||||
|
if other.is_empty() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maximum final size (if there is no overlap at all)
|
||||||
|
let final_size = self.len() + other.len();
|
||||||
|
|
||||||
|
let mut result = Self::with_capacity(final_size);
|
||||||
|
|
||||||
|
let mut it1 = self.into_iter().peekable();
|
||||||
|
let mut it2 = other.into_iter().peekable();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let which = match (it1.peek(), it2.peek()) {
|
||||||
|
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
|
||||||
|
(Some(_), None) => Some(Ordering::Less),
|
||||||
|
(None, Some(_)) => Some(Ordering::Greater),
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let next_element = match which {
|
||||||
|
Some(Ordering::Less) => it1.next(),
|
||||||
|
Some(Ordering::Equal) => {
|
||||||
|
let _ = it2.next();
|
||||||
|
it1.next()
|
||||||
|
}
|
||||||
|
Some(Ordering::Greater) => it2.next(),
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
result.extend([next_element.unwrap()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn separate(self, other: Self) -> SeparateRecordFields {
|
||||||
|
let max_common = self.len().min(other.len());
|
||||||
|
|
||||||
|
let mut result = SeparateRecordFields {
|
||||||
|
only_in_1: RecordFields::with_capacity(self.len()),
|
||||||
|
only_in_2: RecordFields::with_capacity(other.len()),
|
||||||
|
in_both: Vec::with_capacity(max_common),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut it1 = self.into_iter().peekable();
|
||||||
|
let mut it2 = other.into_iter().peekable();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let which = match (it1.peek(), it2.peek()) {
|
||||||
|
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
|
||||||
|
(Some(_), None) => Some(Ordering::Less),
|
||||||
|
(None, Some(_)) => Some(Ordering::Greater),
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match which {
|
||||||
|
Some(Ordering::Less) => result.only_in_1.extend(it1.next()),
|
||||||
|
Some(Ordering::Equal) => {
|
||||||
|
let (label, field1) = it1.next().unwrap();
|
||||||
|
let (_, field2) = it2.next().unwrap();
|
||||||
|
|
||||||
|
result.in_both.push((label, field1, field2));
|
||||||
|
}
|
||||||
|
Some(Ordering::Greater) => result.only_in_2.extend(it2.next()),
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SeparateRecordFields {
|
||||||
|
pub only_in_1: RecordFields,
|
||||||
|
pub only_in_2: RecordFields,
|
||||||
|
pub in_both: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extend<(Lowercase, RecordField<Variable>)> for RecordFields {
|
||||||
|
fn extend<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(&mut self, iter: T) {
|
||||||
|
for (name, record_field) in iter.into_iter() {
|
||||||
|
self.field_names.push(name);
|
||||||
|
self.field_type.push(record_field.map(|_| ()));
|
||||||
|
self.variables.push(record_field.into_inner());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(Lowercase, RecordField<Variable>)> for RecordFields {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
|
||||||
|
let vec: Vec<_> = iter.into_iter().collect();
|
||||||
|
Self::from_vec(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromIterator<(&'a Lowercase, RecordField<Variable>)> for RecordFields {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (&'a Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
|
||||||
|
let vec: Vec<_> = iter.into_iter().map(|(a, b)| (a.clone(), b)).collect();
|
||||||
|
Self::from_vec(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for RecordFields {
|
||||||
|
type Item = (Lowercase, RecordField<Variable>);
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
type IntoIter = Map<
|
||||||
|
Zip<
|
||||||
|
Zip<std::vec::IntoIter<Lowercase>, std::vec::IntoIter<Variable>>,
|
||||||
|
std::vec::IntoIter<RecordField<()>>,
|
||||||
|
>,
|
||||||
|
fn(((Lowercase, Variable), RecordField<()>)) -> (Lowercase, RecordField<Variable>),
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.field_names
|
||||||
|
.into_iter()
|
||||||
|
.zip(self.variables.into_iter())
|
||||||
|
.zip(self.field_type.into_iter())
|
||||||
|
.map(record_fields_into_iterator_help)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_fields_into_iterator_help(
|
||||||
|
arg: ((Lowercase, Variable), RecordField<()>),
|
||||||
|
) -> (Lowercase, RecordField<Variable>) {
|
||||||
|
let ((name, var), field_type) = arg;
|
||||||
|
|
||||||
|
(name, field_type.map(|_| var))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a RecordFields {
|
||||||
|
type Item = (&'a Lowercase, RecordField<Variable>);
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
type IntoIter = Map<
|
||||||
|
Zip<
|
||||||
|
Zip<std::slice::Iter<'a, Lowercase>, std::slice::Iter<'a, Variable>>,
|
||||||
|
std::slice::Iter<'a, RecordField<()>>,
|
||||||
|
>,
|
||||||
|
fn(
|
||||||
|
((&'a Lowercase, &Variable), &RecordField<()>),
|
||||||
|
) -> (&'a Lowercase, RecordField<Variable>),
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.field_names
|
||||||
|
.iter()
|
||||||
|
.zip(self.variables.iter())
|
||||||
|
.zip(self.field_type.iter())
|
||||||
|
.map(ref_record_fields_into_iterator_help)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ref_record_fields_into_iterator_help<'a>(
|
||||||
|
arg: ((&'a Lowercase, &Variable), &RecordField<()>),
|
||||||
|
) -> (&'a Lowercase, RecordField<Variable>) {
|
||||||
|
let ((name, var), field_type) = arg;
|
||||||
|
|
||||||
|
(name, field_type.map(|_| *var))
|
||||||
|
}
|
||||||
|
|
||||||
fn occurs(
|
fn occurs(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
seen: &ImSet<Variable>,
|
seen: &ImSet<Variable>,
|
||||||
|
@ -670,12 +897,7 @@ fn occurs(
|
||||||
short_circuit(subs, root_var, &new_seen, it)
|
short_circuit(subs, root_var, &new_seen, it)
|
||||||
}
|
}
|
||||||
Record(vars_by_field, ext_var) => {
|
Record(vars_by_field, ext_var) => {
|
||||||
let it =
|
let it = once(ext_var).chain(vars_by_field.iter_variables());
|
||||||
once(ext_var).chain(vars_by_field.values().map(|field| match field {
|
|
||||||
RecordField::Optional(var) => var,
|
|
||||||
RecordField::Required(var) => var,
|
|
||||||
RecordField::Demanded(var) => var,
|
|
||||||
}));
|
|
||||||
short_circuit(subs, root_var, &new_seen, it)
|
short_circuit(subs, root_var, &new_seen, it)
|
||||||
}
|
}
|
||||||
TagUnion(tags, ext_var) => {
|
TagUnion(tags, ext_var) => {
|
||||||
|
@ -799,21 +1021,10 @@ fn explicit_substitute(
|
||||||
Record(mut vars_by_field, ext_var) => {
|
Record(mut vars_by_field, ext_var) => {
|
||||||
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
|
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
|
||||||
|
|
||||||
for (_, field) in vars_by_field.iter_mut() {
|
for var in vars_by_field.variables.iter_mut() {
|
||||||
use RecordField::*;
|
*var = explicit_substitute(subs, from, to, *var, seen);
|
||||||
|
|
||||||
*field = match field {
|
|
||||||
Optional(var) => {
|
|
||||||
Optional(explicit_substitute(subs, from, to, *var, seen))
|
|
||||||
}
|
|
||||||
Required(var) => {
|
|
||||||
Required(explicit_substitute(subs, from, to, *var, seen))
|
|
||||||
}
|
|
||||||
Demanded(var) => {
|
|
||||||
Demanded(explicit_substitute(subs, from, to, *var, seen))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var)));
|
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1284,8 +1495,8 @@ fn restore_content(subs: &mut Subs, content: &Content) {
|
||||||
EmptyTagUnion => (),
|
EmptyTagUnion => (),
|
||||||
|
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
for field in fields.values() {
|
for var in fields.iter_variables() {
|
||||||
subs.restore(field.into_inner());
|
subs.restore(*var);
|
||||||
}
|
}
|
||||||
|
|
||||||
subs.restore(*ext_var);
|
subs.restore(*ext_var);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::pretty_print::Parens;
|
use crate::pretty_print::Parens;
|
||||||
use crate::subs::{LambdaSet, Subs, VarStore, Variable};
|
use crate::subs::{LambdaSet, RecordFields, Subs, VarStore, Variable};
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
use roc_collections::all::{ImMap, ImSet, Index, MutMap, MutSet, SendMap};
|
use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap};
|
||||||
use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
|
use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
|
@ -980,7 +980,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RecordStructure {
|
pub struct RecordStructure {
|
||||||
pub fields: MutMap<Lowercase, RecordField<Variable>>,
|
pub fields: RecordFields,
|
||||||
pub ext: Variable,
|
pub ext: Variable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1522,20 +1522,18 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lower
|
||||||
|
|
||||||
pub fn gather_fields(
|
pub fn gather_fields(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
other_fields: &MutMap<Lowercase, RecordField<Variable>>,
|
other_fields: RecordFields,
|
||||||
mut var: Variable,
|
mut var: Variable,
|
||||||
) -> RecordStructure {
|
) -> RecordStructure {
|
||||||
use crate::subs::Content::*;
|
use crate::subs::Content::*;
|
||||||
use crate::subs::FlatType::*;
|
use crate::subs::FlatType::*;
|
||||||
|
|
||||||
let mut result = other_fields.clone();
|
let mut result = other_fields;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match subs.get_content_without_compacting(var) {
|
match subs.get_content_without_compacting(var) {
|
||||||
Structure(Record(sub_fields, sub_ext)) => {
|
Structure(Record(sub_fields, sub_ext)) => {
|
||||||
for (lowercase, record_field) in sub_fields {
|
result = RecordFields::merge(result, sub_fields.clone());
|
||||||
result.insert(lowercase.clone(), *record_field);
|
|
||||||
}
|
|
||||||
|
|
||||||
var = *sub_ext;
|
var = *sub_ext;
|
||||||
}
|
}
|
||||||
|
@ -1554,3 +1552,46 @@ pub fn gather_fields(
|
||||||
ext: var,
|
ext: var,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gather_fields_ref(
|
||||||
|
subs: &Subs,
|
||||||
|
other_fields: &RecordFields,
|
||||||
|
mut var: Variable,
|
||||||
|
) -> RecordStructure {
|
||||||
|
use crate::subs::Content::*;
|
||||||
|
use crate::subs::FlatType::*;
|
||||||
|
|
||||||
|
let mut from_ext = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match subs.get_content_without_compacting(var) {
|
||||||
|
Structure(Record(sub_fields, sub_ext)) => {
|
||||||
|
from_ext.extend(sub_fields.into_iter());
|
||||||
|
|
||||||
|
var = *sub_ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alias(_, _, actual_var) => {
|
||||||
|
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||||
|
var = *actual_var;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if from_ext.is_empty() {
|
||||||
|
RecordStructure {
|
||||||
|
fields: other_fields.clone(),
|
||||||
|
ext: var,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RecordStructure {
|
||||||
|
fields: other_fields
|
||||||
|
.into_iter()
|
||||||
|
.chain(from_ext.into_iter())
|
||||||
|
.collect(),
|
||||||
|
ext: var,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ use roc_collections::all::{default_hasher, get_shared, relative_complement, unio
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_types::subs::Content::{self, *};
|
use roc_types::subs::Content::{self, *};
|
||||||
use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
|
use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, RecordFields, Subs, Variable};
|
||||||
use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordField, RecordStructure};
|
use roc_types::types::{gather_fields_ref, ErrorType, Mismatch, RecordField, RecordStructure};
|
||||||
|
|
||||||
macro_rules! mismatch {
|
macro_rules! mismatch {
|
||||||
() => {{
|
() => {{
|
||||||
|
@ -262,28 +262,27 @@ fn unify_record(
|
||||||
) -> Outcome {
|
) -> Outcome {
|
||||||
let fields1 = rec1.fields;
|
let fields1 = rec1.fields;
|
||||||
let fields2 = rec2.fields;
|
let fields2 = rec2.fields;
|
||||||
let shared_fields = get_shared(&fields1, &fields2);
|
|
||||||
// NOTE: don't use `difference` here. In contrast to Haskell, im's `difference` is symmetric
|
|
||||||
let unique_fields1 = relative_complement(&fields1, &fields2);
|
|
||||||
let unique_fields2 = relative_complement(&fields2, &fields1);
|
|
||||||
|
|
||||||
if unique_fields1.is_empty() {
|
let separate = RecordFields::separate(fields1, fields2);
|
||||||
if unique_fields2.is_empty() {
|
|
||||||
|
let shared_fields = separate.in_both;
|
||||||
|
|
||||||
|
if separate.only_in_1.is_empty() {
|
||||||
|
if separate.only_in_2.is_empty() {
|
||||||
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
|
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
|
||||||
|
|
||||||
if !ext_problems.is_empty() {
|
if !ext_problems.is_empty() {
|
||||||
return ext_problems;
|
return ext_problems;
|
||||||
}
|
}
|
||||||
|
|
||||||
let other_fields = MutMap::default();
|
|
||||||
let mut field_problems =
|
let mut field_problems =
|
||||||
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, rec1.ext);
|
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, rec1.ext);
|
||||||
|
|
||||||
field_problems.extend(ext_problems);
|
field_problems.extend(ext_problems);
|
||||||
|
|
||||||
field_problems
|
field_problems
|
||||||
} else {
|
} else {
|
||||||
let flat_type = FlatType::Record(unique_fields2, rec2.ext);
|
let flat_type = FlatType::Record(separate.only_in_2, rec2.ext);
|
||||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||||
let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record);
|
let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record);
|
||||||
|
|
||||||
|
@ -291,16 +290,21 @@ fn unify_record(
|
||||||
return ext_problems;
|
return ext_problems;
|
||||||
}
|
}
|
||||||
|
|
||||||
let other_fields = MutMap::default();
|
let mut field_problems = unify_shared_fields(
|
||||||
let mut field_problems =
|
subs,
|
||||||
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record);
|
pool,
|
||||||
|
ctx,
|
||||||
|
shared_fields,
|
||||||
|
OtherFields::None,
|
||||||
|
sub_record,
|
||||||
|
);
|
||||||
|
|
||||||
field_problems.extend(ext_problems);
|
field_problems.extend(ext_problems);
|
||||||
|
|
||||||
field_problems
|
field_problems
|
||||||
}
|
}
|
||||||
} else if unique_fields2.is_empty() {
|
} else if separate.only_in_2.is_empty() {
|
||||||
let flat_type = FlatType::Record(unique_fields1, rec1.ext);
|
let flat_type = FlatType::Record(separate.only_in_1, rec1.ext);
|
||||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||||
let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext);
|
let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext);
|
||||||
|
|
||||||
|
@ -308,19 +312,29 @@ fn unify_record(
|
||||||
return ext_problems;
|
return ext_problems;
|
||||||
}
|
}
|
||||||
|
|
||||||
let other_fields = MutMap::default();
|
let mut field_problems = unify_shared_fields(
|
||||||
let mut field_problems =
|
subs,
|
||||||
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record);
|
pool,
|
||||||
|
ctx,
|
||||||
|
shared_fields,
|
||||||
|
OtherFields::None,
|
||||||
|
sub_record,
|
||||||
|
);
|
||||||
|
|
||||||
field_problems.extend(ext_problems);
|
field_problems.extend(ext_problems);
|
||||||
|
|
||||||
field_problems
|
field_problems
|
||||||
} else {
|
} else {
|
||||||
let other_fields = union(unique_fields1.clone(), &unique_fields2);
|
let it = (&separate.only_in_1)
|
||||||
|
.into_iter()
|
||||||
|
.chain((&separate.only_in_2).into_iter());
|
||||||
|
let other: RecordFields = it.collect();
|
||||||
|
|
||||||
|
let other_fields = OtherFields::Other(other);
|
||||||
|
|
||||||
let ext = fresh(subs, pool, ctx, Content::FlexVar(None));
|
let ext = fresh(subs, pool, ctx, Content::FlexVar(None));
|
||||||
let flat_type1 = FlatType::Record(unique_fields1, ext);
|
let flat_type1 = FlatType::Record(separate.only_in_1, ext);
|
||||||
let flat_type2 = FlatType::Record(unique_fields2, ext);
|
let flat_type2 = FlatType::Record(separate.only_in_2, ext);
|
||||||
|
|
||||||
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
|
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
|
||||||
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
|
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
|
||||||
|
@ -346,18 +360,23 @@ fn unify_record(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum OtherFields {
|
||||||
|
None,
|
||||||
|
Other(RecordFields),
|
||||||
|
}
|
||||||
|
|
||||||
fn unify_shared_fields(
|
fn unify_shared_fields(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
pool: &mut Pool,
|
pool: &mut Pool,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
shared_fields: MutMap<Lowercase, (RecordField<Variable>, RecordField<Variable>)>,
|
shared_fields: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
|
||||||
other_fields: MutMap<Lowercase, RecordField<Variable>>,
|
other_fields: OtherFields,
|
||||||
ext: Variable,
|
ext: Variable,
|
||||||
) -> Outcome {
|
) -> Outcome {
|
||||||
let mut matching_fields = MutMap::default();
|
let mut matching_fields = Vec::with_capacity(shared_fields.len());
|
||||||
let num_shared_fields = shared_fields.len();
|
let num_shared_fields = shared_fields.len();
|
||||||
|
|
||||||
for (name, (actual, expected)) in shared_fields {
|
for (name, actual, expected) in shared_fields {
|
||||||
let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner());
|
let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner());
|
||||||
|
|
||||||
if local_problems.is_empty() {
|
if local_problems.is_empty() {
|
||||||
|
@ -383,18 +402,36 @@ fn unify_shared_fields(
|
||||||
(Optional(val), Optional(_)) => Optional(val),
|
(Optional(val), Optional(_)) => Optional(val),
|
||||||
};
|
};
|
||||||
|
|
||||||
let existing = matching_fields.insert(name, actual);
|
matching_fields.push((name, actual));
|
||||||
debug_assert_eq!(existing, None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if num_shared_fields == matching_fields.len() {
|
if num_shared_fields == matching_fields.len() {
|
||||||
// pull fields in from the ext_var
|
// pull fields in from the ext_var
|
||||||
let mut fields = union(matching_fields, &other_fields);
|
|
||||||
|
|
||||||
let new_ext_var = match roc_types::pretty_print::chase_ext_record(subs, ext, &mut fields) {
|
let mut ext_fields = MutMap::default();
|
||||||
Ok(()) => Variable::EMPTY_RECORD,
|
let new_ext_var =
|
||||||
Err((new, _)) => new,
|
match roc_types::pretty_print::chase_ext_record(subs, ext, &mut ext_fields) {
|
||||||
|
Ok(()) => Variable::EMPTY_RECORD,
|
||||||
|
Err((new, _)) => new,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fields: RecordFields = match other_fields {
|
||||||
|
OtherFields::None => {
|
||||||
|
if ext_fields.is_empty() {
|
||||||
|
RecordFields::from_sorted_vec(matching_fields)
|
||||||
|
} else {
|
||||||
|
matching_fields
|
||||||
|
.into_iter()
|
||||||
|
.chain(ext_fields.into_iter())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OtherFields::Other(other_fields) => matching_fields
|
||||||
|
.into_iter()
|
||||||
|
.chain(other_fields.into_iter())
|
||||||
|
.chain(ext_fields.into_iter())
|
||||||
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let flat_type = FlatType::Record(fields, new_ext_var);
|
let flat_type = FlatType::Record(fields, new_ext_var);
|
||||||
|
@ -460,8 +497,8 @@ fn unify_tag_union(
|
||||||
if tags1.len() == 1
|
if tags1.len() == 1
|
||||||
&& tags2.len() == 1
|
&& tags2.len() == 1
|
||||||
&& tags1 == tags2
|
&& tags1 == tags2
|
||||||
&& subs.get_content_without_compacting(rec1.ext)
|
&& subs.get_root_key_without_compacting(rec1.ext)
|
||||||
== subs.get_content_without_compacting(rec2.ext)
|
== subs.get_root_key_without_compacting(rec2.ext)
|
||||||
{
|
{
|
||||||
return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var);
|
return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var);
|
||||||
}
|
}
|
||||||
|
@ -933,18 +970,6 @@ fn unify_shared_tags_merge(
|
||||||
merge(subs, ctx, Structure(flat_type))
|
merge(subs, ctx, Structure(flat_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_only_optional_fields<'a, I, T>(fields: &mut I) -> bool
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a RecordField<T>>,
|
|
||||||
T: 'a,
|
|
||||||
{
|
|
||||||
fields.all(|field| match field {
|
|
||||||
RecordField::Required(_) => false,
|
|
||||||
RecordField::Demanded(_) => false,
|
|
||||||
RecordField::Optional(_) => true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn unify_flat_type(
|
fn unify_flat_type(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
|
@ -958,17 +983,17 @@ fn unify_flat_type(
|
||||||
match (left, right) {
|
match (left, right) {
|
||||||
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())),
|
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())),
|
||||||
|
|
||||||
(Record(fields, ext), EmptyRecord) if has_only_optional_fields(&mut fields.values()) => {
|
(Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields() => {
|
||||||
unify_pool(subs, pool, *ext, ctx.second)
|
unify_pool(subs, pool, *ext, ctx.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
(EmptyRecord, Record(fields, ext)) if has_only_optional_fields(&mut fields.values()) => {
|
(EmptyRecord, Record(fields, ext)) if fields.has_only_optional_fields() => {
|
||||||
unify_pool(subs, pool, ctx.first, *ext)
|
unify_pool(subs, pool, ctx.first, *ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
(Record(fields1, ext1), Record(fields2, ext2)) => {
|
(Record(fields1, ext1), Record(fields2, ext2)) => {
|
||||||
let rec1 = gather_fields(subs, fields1, *ext1);
|
let rec1 = gather_fields_ref(subs, fields1, *ext1);
|
||||||
let rec2 = gather_fields(subs, fields2, *ext2);
|
let rec2 = gather_fields_ref(subs, fields2, *ext2);
|
||||||
|
|
||||||
unify_record(subs, pool, ctx, rec1, rec2)
|
unify_record(subs, pool, ctx, rec1, rec2)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ roc_can = { path = "../compiler/can" }
|
||||||
roc_module = { path = "../compiler/module" }
|
roc_module = { path = "../compiler/module" }
|
||||||
roc_region = { path = "../compiler/region" }
|
roc_region = { path = "../compiler/region" }
|
||||||
roc_types = { path = "../compiler/types" }
|
roc_types = { path = "../compiler/types" }
|
||||||
|
roc_parse = { path = "../compiler/parse" }
|
||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
bumpalo = { version = "3.2", features = ["collections"] }
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
|
|
||||||
|
|
466
docs/src/lib.rs
466
docs/src/lib.rs
|
@ -1,22 +1,24 @@
|
||||||
extern crate pulldown_cmark;
|
extern crate pulldown_cmark;
|
||||||
use roc_builtins::std::StdLib;
|
|
||||||
use roc_can::builtins::builtin_defs_map;
|
|
||||||
use roc_load::docs::{DocEntry, TypeAnnotation};
|
|
||||||
use roc_load::docs::{ModuleDocumentation, RecordField};
|
|
||||||
use roc_load::file::{LoadedModule, LoadingProblem};
|
|
||||||
use roc_module::symbol::Interns;
|
|
||||||
|
|
||||||
use std::fs;
|
|
||||||
extern crate roc_load;
|
extern crate roc_load;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use roc_builtins::std::StdLib;
|
||||||
|
use roc_can::builtins::builtin_defs_map;
|
||||||
use roc_can::scope::Scope;
|
use roc_can::scope::Scope;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_load::docs::DocEntry::DocDef;
|
use roc_load::docs::DocEntry::DocDef;
|
||||||
|
use roc_load::docs::{DocEntry, TypeAnnotation};
|
||||||
|
use roc_load::docs::{ModuleDocumentation, RecordField};
|
||||||
|
use roc_load::file::{LoadedModule, LoadingProblem};
|
||||||
|
use roc_module::symbol::{IdentIds, Interns, ModuleId};
|
||||||
|
use roc_parse::ident::{parse_ident, Ident};
|
||||||
|
use roc_parse::parser::State;
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||||
let files_docs = files_to_documentations(filenames, std_lib);
|
let files_docs = files_to_documentations(filenames, std_lib);
|
||||||
|
let mut arena = Bump::new();
|
||||||
|
|
||||||
//
|
//
|
||||||
// TODO: get info from a file like "elm.json"
|
// TODO: get info from a file like "elm.json"
|
||||||
|
@ -52,22 +54,33 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||||
|
|
||||||
let template_html = include_str!("./static/index.html").replace(
|
let template_html = include_str!("./static/index.html").replace(
|
||||||
"<!-- Module links -->",
|
"<!-- Module links -->",
|
||||||
render_sidebar(
|
render_sidebar(package.modules.iter().flat_map(|loaded_module| {
|
||||||
package
|
loaded_module.documentation.values().map(move |d| {
|
||||||
.modules
|
let exposed_values = loaded_module
|
||||||
.iter()
|
.exposed_values
|
||||||
.flat_map(|loaded_module| loaded_module.documentation.values()),
|
.iter()
|
||||||
)
|
.map(|symbol| symbol.ident_string(&loaded_module.interns).to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
(exposed_values, d)
|
||||||
|
})
|
||||||
|
}))
|
||||||
.as_str(),
|
.as_str(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write each package's module docs html file
|
// Write each package's module docs html file
|
||||||
for loaded_module in package.modules.iter_mut() {
|
for loaded_module in package.modules.iter_mut() {
|
||||||
let exports = loaded_module
|
arena.reset();
|
||||||
.exposed_values
|
|
||||||
.iter()
|
let mut exports: bumpalo::collections::Vec<&str> =
|
||||||
.map(|symbol| symbol.ident_string(&loaded_module.interns).to_string())
|
bumpalo::collections::Vec::with_capacity_in(loaded_module.exposed_values.len(), &arena);
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
// TODO should this also include exposed_aliases?
|
||||||
|
for symbol in loaded_module.exposed_values.iter() {
|
||||||
|
exports.push(symbol.ident_string(&loaded_module.interns));
|
||||||
|
}
|
||||||
|
|
||||||
|
let exports = exports.into_bump_slice();
|
||||||
|
|
||||||
for module in loaded_module.documentation.values_mut() {
|
for module in loaded_module.documentation.values_mut() {
|
||||||
let module_dir = build_dir.join(module.name.replace(".", "/").as_str());
|
let module_dir = build_dir.join(module.name.replace(".", "/").as_str());
|
||||||
|
@ -83,7 +96,14 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"<!-- Module Docs -->",
|
"<!-- Module Docs -->",
|
||||||
render_main_content(&loaded_module.interns, &exports, module).as_str(),
|
render_main_content(
|
||||||
|
loaded_module.module_id,
|
||||||
|
exports,
|
||||||
|
&loaded_module.dep_idents,
|
||||||
|
&loaded_module.interns,
|
||||||
|
module,
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::write(module_dir.join("index.html"), rendered_module)
|
fs::write(module_dir.join("index.html"), rendered_module)
|
||||||
|
@ -95,8 +115,10 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_main_content(
|
fn render_main_content(
|
||||||
|
home: ModuleId,
|
||||||
|
exposed_values: &[&str],
|
||||||
|
dep_idents: &MutMap<ModuleId, IdentIds>,
|
||||||
interns: &Interns,
|
interns: &Interns,
|
||||||
exposed_values: &[String],
|
|
||||||
module: &mut ModuleDocumentation,
|
module: &mut ModuleDocumentation,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
@ -115,7 +137,7 @@ fn render_main_content(
|
||||||
|
|
||||||
if let DocDef(def) = entry {
|
if let DocDef(def) = entry {
|
||||||
// We dont want to render entries that arent exposed
|
// We dont want to render entries that arent exposed
|
||||||
should_render_entry = exposed_values.contains(&def.name);
|
should_render_entry = exposed_values.contains(&def.name.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_render_entry {
|
if should_render_entry {
|
||||||
|
@ -158,12 +180,27 @@ fn render_main_content(
|
||||||
|
|
||||||
if let Some(docs) = &doc_def.docs {
|
if let Some(docs) = &doc_def.docs {
|
||||||
buf.push_str(
|
buf.push_str(
|
||||||
markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(),
|
markdown_to_html(
|
||||||
|
home,
|
||||||
|
exposed_values,
|
||||||
|
dep_idents,
|
||||||
|
&mut module.scope,
|
||||||
|
interns,
|
||||||
|
docs.to_string(),
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocEntry::DetachedDoc(docs) => {
|
DocEntry::DetachedDoc(docs) => {
|
||||||
let markdown = markdown_to_html(&mut module.scope, interns, docs.to_string());
|
let markdown = markdown_to_html(
|
||||||
|
home,
|
||||||
|
exposed_values,
|
||||||
|
dep_idents,
|
||||||
|
&mut module.scope,
|
||||||
|
interns,
|
||||||
|
docs.to_string(),
|
||||||
|
);
|
||||||
buf.push_str(markdown.as_str());
|
buf.push_str(markdown.as_str());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -237,10 +274,12 @@ fn render_name_and_version(name: &str, version: &str) -> String {
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -> String {
|
fn render_sidebar<'a, I: Iterator<Item = (Vec<String>, &'a ModuleDocumentation)>>(
|
||||||
|
modules: I,
|
||||||
|
) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
for module in modules {
|
for (exposed_values, module) in modules {
|
||||||
let mut sidebar_entry_content = String::new();
|
let mut sidebar_entry_content = String::new();
|
||||||
|
|
||||||
let name = module.name.as_str();
|
let name = module.name.as_str();
|
||||||
|
@ -266,20 +305,22 @@ fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -
|
||||||
|
|
||||||
for entry in &module.entries {
|
for entry in &module.entries {
|
||||||
if let DocEntry::DocDef(doc_def) = entry {
|
if let DocEntry::DocDef(doc_def) = entry {
|
||||||
let mut entry_href = String::new();
|
if exposed_values.contains(&doc_def.name) {
|
||||||
|
let mut entry_href = String::new();
|
||||||
|
|
||||||
entry_href.push_str(href.as_str());
|
entry_href.push_str(href.as_str());
|
||||||
entry_href.push('#');
|
entry_href.push('#');
|
||||||
entry_href.push_str(doc_def.name.as_str());
|
entry_href.push_str(doc_def.name.as_str());
|
||||||
|
|
||||||
entries_buf.push_str(
|
entries_buf.push_str(
|
||||||
html_node(
|
html_node(
|
||||||
"a",
|
"a",
|
||||||
vec![("href", entry_href.as_str())],
|
vec![("href", entry_href.as_str())],
|
||||||
doc_def.name.as_str(),
|
doc_def.name.as_str(),
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,161 +642,234 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_doc_links(scope: &mut Scope, interns: &Interns, markdown: String) -> String {
|
struct DocUrl {
|
||||||
let buf = &markdown;
|
url: String,
|
||||||
let mut result = String::new();
|
title: String,
|
||||||
|
|
||||||
let mut chomping_from: Option<usize> = None;
|
|
||||||
|
|
||||||
let mut chars = buf.chars().enumerate().peekable();
|
|
||||||
|
|
||||||
while let Some((index, char)) = chars.next() {
|
|
||||||
match chomping_from {
|
|
||||||
None => {
|
|
||||||
let next_is_alphabetic = match chars.peek() {
|
|
||||||
None => false,
|
|
||||||
Some((_, next_char)) => next_char.is_alphabetic(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if char == '#' && next_is_alphabetic {
|
|
||||||
chomping_from = Some(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(from) => {
|
|
||||||
if !(char.is_alphabetic() || char == '.') {
|
|
||||||
let after_link = buf.chars().skip(from + buf.len());
|
|
||||||
|
|
||||||
result = buf.chars().take(from).collect();
|
|
||||||
|
|
||||||
let doc_link = make_doc_link(
|
|
||||||
scope,
|
|
||||||
interns,
|
|
||||||
&buf.chars()
|
|
||||||
.skip(from + 1)
|
|
||||||
.take(index - from - 1)
|
|
||||||
.collect::<String>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
result.insert_str(from, doc_link.as_str());
|
|
||||||
|
|
||||||
let remainder = insert_doc_links(scope, interns, after_link.collect());
|
|
||||||
|
|
||||||
result.push_str(remainder.as_str());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if chomping_from == None {
|
|
||||||
markdown
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_doc_link(scope: &mut Scope, interns: &Interns, doc_item: &str) -> String {
|
fn doc_url<'a>(
|
||||||
match scope.lookup(&doc_item.into(), Region::zero()) {
|
home: ModuleId,
|
||||||
Ok(symbol) => {
|
exposed_values: &[&str],
|
||||||
let module_str = symbol.module_string(interns);
|
dep_idents: &MutMap<ModuleId, IdentIds>,
|
||||||
|
scope: &mut Scope,
|
||||||
let ident_str = symbol.ident_string(interns);
|
interns: &'a Interns,
|
||||||
|
mut module_name: &'a str,
|
||||||
let mut link = String::new();
|
ident: &str,
|
||||||
|
) -> DocUrl {
|
||||||
link.push('/');
|
if module_name.is_empty() {
|
||||||
link.push_str(module_str);
|
// This is an unqualified lookup, so look for the ident
|
||||||
link.push('#');
|
// in scope!
|
||||||
link.push_str(ident_str);
|
match scope.lookup(&ident.into(), Region::zero()) {
|
||||||
|
Ok(symbol) => {
|
||||||
let mut buf = String::new();
|
// Get the exact module_name from scope. It could be the
|
||||||
|
// current module's name, but it also could be a different
|
||||||
buf.push('[');
|
// module - for example, if this is in scope from an
|
||||||
buf.push_str(doc_item);
|
// unqualified import.
|
||||||
buf.push_str("](");
|
module_name = symbol.module_string(interns);
|
||||||
|
}
|
||||||
buf.push_str(link.as_str());
|
Err(_) => {
|
||||||
buf.push(')');
|
// TODO return Err here
|
||||||
|
panic!(
|
||||||
buf
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
panic!(
|
|
||||||
"Tried to generate an automatic link in docs for symbol `{}`, but that symbol was not in scope in this module. Scope was: {:?}",
|
"Tried to generate an automatic link in docs for symbol `{}`, but that symbol was not in scope in this module. Scope was: {:?}",
|
||||||
doc_item, scope
|
ident, scope
|
||||||
)
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
match interns.module_ids.get_id(&module_name.into()) {
|
||||||
|
Some(&module_id) => {
|
||||||
|
// You can do qualified lookups on your own module, e.g.
|
||||||
|
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
|
||||||
|
if module_id == home {
|
||||||
|
// Check to see if the value is exposed in this module.
|
||||||
|
// If it's not exposed, then we can't link to it!
|
||||||
|
if !exposed_values.contains(&ident) {
|
||||||
|
// TODO return Err here
|
||||||
|
panic!(
|
||||||
|
"Tried to generate an automatic link in docs for `{}.{}`, but `{}` is not declared in `{}`.",
|
||||||
|
module_name, ident, ident, module_name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is not the home module
|
||||||
|
match dep_idents
|
||||||
|
.get(&module_id)
|
||||||
|
.and_then(|exposed_ids| exposed_ids.get_id(&ident.into()))
|
||||||
|
{
|
||||||
|
Some(_) => {
|
||||||
|
// This is a valid symbol for this dependency,
|
||||||
|
// so proceed using the current module's name.
|
||||||
|
//
|
||||||
|
// TODO: In the future, this is where we'll
|
||||||
|
// incorporate the package name into the link.
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// TODO return Err here
|
||||||
|
panic!(
|
||||||
|
"Tried to generate an automatic link in docs for `{}.{}`, but `{}` is not exposed in `{}`.",
|
||||||
|
module_name, ident, ident, module_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// TODO return Err here
|
||||||
|
panic!("Tried to generate a doc link for `{}.{}` but the `{}` module was not imported!", module_name, ident, module_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut url = String::new();
|
||||||
|
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// module_name: "Str", ident: "join" => "/Str#join"
|
||||||
|
url.push('/');
|
||||||
|
url.push_str(module_name);
|
||||||
|
url.push('#');
|
||||||
|
url.push_str(ident);
|
||||||
|
|
||||||
|
DocUrl {
|
||||||
|
url,
|
||||||
|
title: format!("Docs for {}.{}", module_name, ident),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn markdown_to_html(scope: &mut Scope, interns: &Interns, markdown: String) -> String {
|
fn markdown_to_html(
|
||||||
use pulldown_cmark::CodeBlockKind;
|
home: ModuleId,
|
||||||
use pulldown_cmark::CowStr;
|
exposed_values: &[&str],
|
||||||
use pulldown_cmark::Event;
|
dep_idents: &MutMap<ModuleId, IdentIds>,
|
||||||
use pulldown_cmark::Tag::*;
|
scope: &mut Scope,
|
||||||
|
interns: &Interns,
|
||||||
|
markdown: String,
|
||||||
|
) -> String {
|
||||||
|
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Tag::*};
|
||||||
|
|
||||||
let markdown_with_links = insert_doc_links(scope, interns, markdown);
|
let mut arena = Bump::new();
|
||||||
|
let mut broken_link_callback = |link: BrokenLink| {
|
||||||
|
// A shortcut link - see https://spec.commonmark.org/0.30/#shortcut-reference-link -
|
||||||
|
// is something like `[foo]` in markdown. If you have a shortcut link
|
||||||
|
// without a corresponding `[foo]: https://foo.com` entry
|
||||||
|
// at the end of the document, we resolve it as an identifier based on
|
||||||
|
// what's currently in scope, so you write things like [Str.join] or
|
||||||
|
// [myFunction] and have them resolve to the docs for what you wrote.
|
||||||
|
match link.link_type {
|
||||||
|
LinkType::Shortcut => {
|
||||||
|
let state = State::new(link.reference.as_bytes());
|
||||||
|
|
||||||
|
// Reset the bump arena so we aren't constantly reallocating
|
||||||
|
// more memory.
|
||||||
|
arena.reset();
|
||||||
|
|
||||||
|
match parse_ident(&arena, state) {
|
||||||
|
Ok((_, Ident::Access { module_name, parts }, _)) => {
|
||||||
|
let mut iter = parts.iter();
|
||||||
|
|
||||||
|
match iter.next() {
|
||||||
|
Some(symbol_name) if iter.next().is_none() => {
|
||||||
|
let DocUrl { url, title } = doc_url(
|
||||||
|
home,
|
||||||
|
exposed_values,
|
||||||
|
dep_idents,
|
||||||
|
scope,
|
||||||
|
interns,
|
||||||
|
module_name,
|
||||||
|
symbol_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some((url.into(), title.into()))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// This had record field access,
|
||||||
|
// e.g. [foo.bar] - which we
|
||||||
|
// can't create a doc link to!
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((_, Ident::GlobalTag(type_name), _)) => {
|
||||||
|
// This looks like a global tag name, but it could
|
||||||
|
// be a type alias that's in scope, e.g. [I64]
|
||||||
|
let DocUrl { url, title } = doc_url(
|
||||||
|
home,
|
||||||
|
exposed_values,
|
||||||
|
dep_idents,
|
||||||
|
scope,
|
||||||
|
interns,
|
||||||
|
"",
|
||||||
|
type_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some((url.into(), title.into()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let markdown_options = pulldown_cmark::Options::empty();
|
let markdown_options = pulldown_cmark::Options::empty();
|
||||||
let mut docs_parser = vec![];
|
let mut docs_parser = vec![];
|
||||||
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown_with_links, markdown_options).fold(
|
let (_, _) = pulldown_cmark::Parser::new_with_broken_link_callback(
|
||||||
(0, 0),
|
&markdown,
|
||||||
|(start_quote_count, end_quote_count), event| {
|
markdown_options,
|
||||||
match event {
|
Some(&mut broken_link_callback),
|
||||||
// Replace this sequence (`>>>` syntax):
|
)
|
||||||
// Start(BlockQuote)
|
.fold((0, 0), |(start_quote_count, end_quote_count), event| {
|
||||||
// Start(BlockQuote)
|
match event {
|
||||||
// Start(BlockQuote)
|
// Replace this sequence (`>>>` syntax):
|
||||||
// Start(Paragraph)
|
// Start(BlockQuote)
|
||||||
// For `Start(CodeBlock(Fenced(Borrowed("roc"))))`
|
// Start(BlockQuote)
|
||||||
Event::Start(BlockQuote) => {
|
// Start(BlockQuote)
|
||||||
|
// Start(Paragraph)
|
||||||
|
// For `Start(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||||
|
Event::Start(BlockQuote) => {
|
||||||
|
docs_parser.push(event);
|
||||||
|
(start_quote_count + 1, 0)
|
||||||
|
}
|
||||||
|
Event::Start(Paragraph) => {
|
||||||
|
if start_quote_count == 3 {
|
||||||
|
docs_parser.pop();
|
||||||
|
docs_parser.pop();
|
||||||
|
docs_parser.pop();
|
||||||
|
docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced(
|
||||||
|
CowStr::Borrowed("roc"),
|
||||||
|
))));
|
||||||
|
} else {
|
||||||
docs_parser.push(event);
|
docs_parser.push(event);
|
||||||
(start_quote_count + 1, 0)
|
|
||||||
}
|
}
|
||||||
Event::Start(Paragraph) => {
|
(0, 0)
|
||||||
if start_quote_count == 3 {
|
}
|
||||||
docs_parser.pop();
|
// Replace this sequence (`>>>` syntax):
|
||||||
docs_parser.pop();
|
// End(Paragraph)
|
||||||
docs_parser.pop();
|
// End(BlockQuote)
|
||||||
docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced(
|
// End(BlockQuote)
|
||||||
CowStr::Borrowed("roc"),
|
// End(BlockQuote)
|
||||||
))));
|
// For `End(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||||
} else {
|
Event::End(Paragraph) => {
|
||||||
docs_parser.push(event);
|
docs_parser.push(event);
|
||||||
}
|
(0, 1)
|
||||||
|
}
|
||||||
|
Event::End(BlockQuote) => {
|
||||||
|
if end_quote_count == 3 {
|
||||||
|
docs_parser.pop();
|
||||||
|
docs_parser.pop();
|
||||||
|
docs_parser.pop();
|
||||||
|
docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced(
|
||||||
|
CowStr::Borrowed("roc"),
|
||||||
|
))));
|
||||||
(0, 0)
|
(0, 0)
|
||||||
}
|
} else {
|
||||||
// Replace this sequence (`>>>` syntax):
|
|
||||||
// End(Paragraph)
|
|
||||||
// End(BlockQuote)
|
|
||||||
// End(BlockQuote)
|
|
||||||
// End(BlockQuote)
|
|
||||||
// For `End(CodeBlock(Fenced(Borrowed("roc"))))`
|
|
||||||
Event::End(Paragraph) => {
|
|
||||||
docs_parser.push(event);
|
docs_parser.push(event);
|
||||||
(0, 1)
|
(0, end_quote_count + 1)
|
||||||
}
|
|
||||||
Event::End(BlockQuote) => {
|
|
||||||
if end_quote_count == 3 {
|
|
||||||
docs_parser.pop();
|
|
||||||
docs_parser.pop();
|
|
||||||
docs_parser.pop();
|
|
||||||
docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced(
|
|
||||||
CowStr::Borrowed("roc"),
|
|
||||||
))));
|
|
||||||
(0, 0)
|
|
||||||
} else {
|
|
||||||
docs_parser.push(event);
|
|
||||||
(0, end_quote_count + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
docs_parser.push(event);
|
|
||||||
(0, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
_ => {
|
||||||
);
|
docs_parser.push(event);
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut docs_html = String::new();
|
let mut docs_html = String::new();
|
||||||
|
|
||||||
|
|
26
docs/tests/fixtures/Interface.roc
vendored
26
docs/tests/fixtures/Interface.roc
vendored
|
@ -1,26 +0,0 @@
|
||||||
interface Test
|
|
||||||
exposes [ singleline, multiline, multiparagraph, codeblock ]
|
|
||||||
imports []
|
|
||||||
|
|
||||||
## This is a block
|
|
||||||
Block elem : [ Block elem ]
|
|
||||||
|
|
||||||
## Single line documentation.
|
|
||||||
singleline : Bool -> Bool
|
|
||||||
|
|
||||||
## Multiline documentation.
|
|
||||||
## Without any complex syntax yet!
|
|
||||||
multiline : Bool -> Bool
|
|
||||||
|
|
||||||
## Multiparagraph documentation.
|
|
||||||
##
|
|
||||||
## Without any complex syntax yet!
|
|
||||||
multiparagraph : Bool -> Bool
|
|
||||||
|
|
||||||
## No documentation for not exposed function.
|
|
||||||
notExposed : Bool -> Bool
|
|
||||||
|
|
||||||
## Turns >>> into code block for now.
|
|
||||||
##
|
|
||||||
## >>> codeblock
|
|
||||||
codeblock : Bool -> Bool
|
|
|
@ -1,43 +0,0 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate pretty_assertions;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod insert_doc_links {
|
|
||||||
use roc_can::env::Env;
|
|
||||||
use roc_can::scope::Scope;
|
|
||||||
use roc_collections::all::MutMap;
|
|
||||||
use roc_docs::insert_doc_links;
|
|
||||||
use roc_module::symbol::{IdentIds, Interns, ModuleIds};
|
|
||||||
use roc_types::subs::VarStore;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_doc_links() {
|
|
||||||
let home = ModuleIds::default().get_or_insert(&"Test".into());
|
|
||||||
|
|
||||||
let module_ids = ModuleIds::default();
|
|
||||||
|
|
||||||
let dep_idents = IdentIds::exposed_builtins(0);
|
|
||||||
|
|
||||||
let env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
|
||||||
|
|
||||||
let all_ident_ids = MutMap::default();
|
|
||||||
|
|
||||||
let interns = Interns {
|
|
||||||
module_ids: env.module_ids.clone(),
|
|
||||||
all_ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
let var_store = &mut VarStore::default();
|
|
||||||
let scope = &mut Scope::new(home, var_store);
|
|
||||||
|
|
||||||
let markdown = r#"
|
|
||||||
# Hello
|
|
||||||
Hello thanks for using my package
|
|
||||||
"#;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
markdown,
|
|
||||||
insert_doc_links(scope, &interns, markdown.to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -760,7 +760,9 @@ fn type_to_variable<'a>(
|
||||||
Err((new, _)) => new,
|
Err((new, _)) => new,
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = Content::Structure(FlatType::Record(field_vars, new_ext_var));
|
let record_fields = field_vars.into_iter().collect();
|
||||||
|
|
||||||
|
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register(subs, rank, pools, content)
|
||||||
}
|
}
|
||||||
|
@ -1212,14 +1214,9 @@ fn adjust_rank_content(
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
|
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
|
||||||
|
|
||||||
for var in fields.values() {
|
for var in fields.iter_variables() {
|
||||||
rank = rank.max(adjust_rank(
|
rank =
|
||||||
subs,
|
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
|
||||||
young_mark,
|
|
||||||
visit_mark,
|
|
||||||
group_rank,
|
|
||||||
var.into_inner(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rank
|
rank
|
||||||
|
@ -1372,28 +1369,12 @@ fn instantiate_rigids_help(
|
||||||
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
||||||
|
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
let mut new_fields = MutMap::default();
|
for var in fields.iter_variables() {
|
||||||
|
instantiate_rigids_help(subs, max_rank, pools, *var);
|
||||||
for (label, field) in fields {
|
|
||||||
use RecordField::*;
|
|
||||||
|
|
||||||
let new_field = match field {
|
|
||||||
Demanded(var) => {
|
|
||||||
Demanded(instantiate_rigids_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
Required(var) => {
|
|
||||||
Required(instantiate_rigids_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
Optional(var) => {
|
|
||||||
Optional(instantiate_rigids_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new_fields.insert(label, new_field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Record(
|
Record(
|
||||||
new_fields,
|
fields,
|
||||||
instantiate_rigids_help(subs, max_rank, pools, ext_var),
|
instantiate_rigids_help(subs, max_rank, pools, ext_var),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1566,31 +1547,12 @@ fn deep_copy_var_help(
|
||||||
|
|
||||||
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
||||||
|
|
||||||
Record(fields, ext_var) => {
|
Record(mut fields, ext_var) => {
|
||||||
let mut new_fields = MutMap::default();
|
for var in fields.iter_variables_mut() {
|
||||||
|
*var = deep_copy_var_help(subs, max_rank, pools, *var);
|
||||||
for (label, field) in fields {
|
|
||||||
use RecordField::*;
|
|
||||||
|
|
||||||
let new_field = match field {
|
|
||||||
Demanded(var) => {
|
|
||||||
Demanded(deep_copy_var_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
Required(var) => {
|
|
||||||
Required(deep_copy_var_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
Optional(var) => {
|
|
||||||
Optional(deep_copy_var_help(subs, max_rank, pools, var))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new_fields.insert(label, new_field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Record(
|
Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
|
||||||
new_fields,
|
|
||||||
deep_copy_var_help(subs, max_rank, pools, ext_var),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagUnion(tags, ext_var) => {
|
TagUnion(tags, ext_var) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue