Merge branch 'trunk' into shrink-type-error

This commit is contained in:
Folkert de Vries 2021-08-02 23:02:28 +02:00 committed by GitHub
commit 892447b08c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 744 additions and 502 deletions

1
Cargo.lock generated
View file

@ -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",
] ]

View file

@ -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());

View file

@ -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,

View file

@ -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.

View file

@ -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> {

View file

@ -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,

View file

@ -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,

View file

@ -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 {

View file

@ -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),
) )
} }

View file

@ -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> {

View file

@ -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) => {

View file

@ -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,

View file

@ -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
, ... , ...
} }

View file

@ -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) => {

View file

@ -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)

View file

@ -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));
} }

View file

@ -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);

View file

@ -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,
}
}
}

View file

@ -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)
} }

View file

@ -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"] }

View file

@ -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();

View file

@ -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

View file

@ -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()),
);
}
}

View file

@ -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) => {