Merge branch 'main' into fix_6606

This commit is contained in:
Anton-4 2024-05-04 16:26:40 +02:00 committed by GitHub
commit 388d43fc34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
287 changed files with 7602 additions and 4265 deletions

View file

@ -526,7 +526,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly-2023-08-20", "cargo"]);
cmd.args(["run", "nightly-2023-12-21", "cargo"]);
cmd
} else {

View file

@ -1,6 +1,4 @@
interface Bool
exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
imports []
module [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
## Defines a type that can be compared for total equality.
##

View file

@ -2,9 +2,7 @@
## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform).
## - To improve performance in rare cases.
##
interface Box
exposes [box, unbox]
imports []
module [box, unbox]
## Allocates a value on the heap. Boxing is an expensive process as it copies
## the value from the stack to the heap. This may provide a performance

View file

@ -1,55 +1,53 @@
interface Decode
exposes [
DecodeError,
DecodeResult,
Decoder,
Decoding,
DecoderFormatting,
decoder,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tuple,
custom,
decodeWith,
fromBytesPartial,
fromBytes,
mapResult,
]
imports [
List,
Result.{ Result },
Num.{
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
F32,
F64,
Dec,
},
Bool.{ Bool },
]
module [
DecodeError,
DecodeResult,
Decoder,
Decoding,
DecoderFormatting,
decoder,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tuple,
custom,
decodeWith,
fromBytesPartial,
fromBytes,
mapResult,
]
import List
import Result exposing [Result]
import Num exposing [
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
F32,
F64,
Dec,
]
import Bool exposing [Bool]
## Error types when decoding a `List U8` of utf-8 bytes using a [Decoder]
DecodeError : [TooShort]

View file

@ -1,43 +1,41 @@
interface Dict
exposes [
Dict,
empty,
withCapacity,
single,
clear,
capacity,
reserve,
releaseExcessCapacity,
len,
isEmpty,
get,
contains,
insert,
remove,
update,
walk,
walkUntil,
keepIf,
dropIf,
toList,
fromList,
keys,
values,
insertAll,
keepShared,
removeAll,
map,
joinMap,
]
imports [
Bool.{ Bool, Eq },
Result.{ Result },
List,
Str,
Num.{ U64, F32, U32, U8, I8 },
Hash.{ Hasher, Hash },
Inspect.{ Inspect, Inspector, InspectFormatter },
]
module [
Dict,
empty,
withCapacity,
single,
clear,
capacity,
reserve,
releaseExcessCapacity,
len,
isEmpty,
get,
contains,
insert,
remove,
update,
walk,
walkUntil,
keepIf,
dropIf,
toList,
fromList,
keys,
values,
insertAll,
keepShared,
removeAll,
map,
joinMap,
]
import Bool exposing [Bool, Eq]
import Result exposing [Result]
import List
import Str
import Num exposing [U64, F32, U32, U8]
import Hash exposing [Hasher, Hash]
import Inspect exposing [Inspect, Inspector, InspectFormatter]
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you
## associate keys with values.

View file

@ -1,51 +1,49 @@
interface Encode
exposes [
Encoder,
Encoding,
toEncoder,
EncoderFormatting,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tag,
tuple,
custom,
appendWith,
append,
toBytes,
]
imports [
Num.{
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
F32,
F64,
Dec,
},
Bool.{ Bool },
]
module [
Encoder,
Encoding,
toEncoder,
EncoderFormatting,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128,
f32,
f64,
dec,
bool,
string,
list,
record,
tag,
tuple,
custom,
appendWith,
append,
toBytes,
]
import Num exposing [
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
F32,
F64,
Dec,
]
import Bool exposing [Bool]
Encoder fmt := List U8, fmt -> List U8 where fmt implements EncoderFormatting

View file

@ -1,31 +1,42 @@
interface Hash
exposes [
Hash,
Hasher,
hash,
addBytes,
addU8,
addU16,
addU32,
addU64,
addU128,
hashBool,
hashI8,
hashI16,
hashI32,
hashI64,
hashI128,
hashDec,
complete,
hashStrBytes,
hashList,
hashUnordered,
] imports [
Bool.{ Bool },
List,
Str,
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Dec },
]
module [
Hash,
Hasher,
hash,
addBytes,
addU8,
addU16,
addU32,
addU64,
addU128,
hashBool,
hashI8,
hashI16,
hashI32,
hashI64,
hashI128,
hashDec,
complete,
hashStrBytes,
hashList,
hashUnordered,
]
import Bool exposing [Bool]
import List
import Str
import Num exposing [
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Dec,
]
## A value that can be hashed.
Hash implements

View file

@ -1,46 +1,44 @@
interface Inspect
exposes [
Inspect,
Inspector,
InspectFormatter,
ElemWalker,
KeyValWalker,
inspect,
init,
list,
set,
dict,
tag,
tuple,
record,
bool,
str,
function,
opaque,
u8,
i8,
u16,
i16,
u32,
i32,
u64,
i64,
u128,
i128,
f32,
f64,
dec,
custom,
apply,
toInspector,
toStr,
]
imports [
Bool.{ Bool },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
List,
Str,
]
module [
Inspect,
Inspector,
InspectFormatter,
ElemWalker,
KeyValWalker,
inspect,
init,
list,
set,
dict,
tag,
tuple,
record,
bool,
str,
function,
opaque,
u8,
i8,
u16,
i16,
u32,
i32,
u64,
i64,
u128,
i128,
f32,
f64,
dec,
custom,
apply,
toInspector,
toStr,
]
import Bool exposing [Bool]
import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
import List
import Str
KeyValWalker state collection key val : collection, state, (state, key, val -> state) -> state
ElemWalker state collection elem : collection, state, (state, elem -> state) -> state

View file

@ -1,81 +1,79 @@
interface List
exposes [
isEmpty,
get,
set,
replace,
update,
append,
appendIfOk,
prepend,
prependIfOk,
map,
len,
withCapacity,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
keepErrs,
mapWithIndex,
map2,
map3,
product,
walkWithIndex,
walkUntil,
walkWithIndexUntil,
walkFrom,
walkFromUntil,
range,
sortWith,
swap,
dropAt,
min,
max,
map4,
mapTry,
walkTry,
joinMap,
any,
takeFirst,
takeLast,
dropFirst,
dropLast,
findFirst,
findLast,
findFirstIndex,
findLastIndex,
sublist,
intersperse,
split,
splitFirst,
splitLast,
startsWith,
endsWith,
all,
dropIf,
sortAsc,
sortDesc,
reserve,
releaseExcessCapacity,
walkBackwardsUntil,
countIf,
chunksOf,
]
imports [
Bool.{ Bool, Eq },
Result.{ Result },
Num.{ U64, Num, Int },
]
module [
isEmpty,
get,
set,
replace,
update,
append,
appendIfOk,
prepend,
prependIfOk,
map,
len,
withCapacity,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
keepErrs,
mapWithIndex,
map2,
map3,
product,
walkWithIndex,
walkUntil,
walkWithIndexUntil,
walkFrom,
walkFromUntil,
range,
sortWith,
swap,
dropAt,
min,
max,
map4,
mapTry,
walkTry,
joinMap,
any,
takeFirst,
takeLast,
dropFirst,
dropLast,
findFirst,
findLast,
findFirstIndex,
findLastIndex,
sublist,
intersperse,
split,
splitFirst,
splitLast,
startsWith,
endsWith,
all,
dropIf,
sortAsc,
sortDesc,
reserve,
releaseExcessCapacity,
walkBackwardsUntil,
countIf,
chunksOf,
]
import Bool exposing [Bool, Eq]
import Result exposing [Result]
import Num exposing [U64, Num]
## ## Types
##

View file

@ -1,166 +1,164 @@
interface Num
exposes [
Num,
Int,
Frac,
Integer,
FloatingPoint,
I128,
I64,
I32,
I16,
I8,
U128,
U64,
U32,
U16,
U8,
Signed128,
Signed64,
Signed32,
Signed16,
Signed8,
Unsigned128,
Unsigned64,
Unsigned32,
Unsigned16,
Unsigned8,
Dec,
F64,
F32,
Decimal,
Binary32,
Binary64,
e,
pi,
tau,
abs,
absDiff,
neg,
add,
sub,
mul,
min,
max,
isLt,
isLte,
isGt,
isGte,
isApproxEq,
sin,
cos,
tan,
atan,
acos,
asin,
isZero,
isEven,
isOdd,
toFrac,
isPositive,
isNegative,
isNaN,
isInfinite,
isFinite,
rem,
remChecked,
div,
divChecked,
sqrt,
sqrtChecked,
log,
logChecked,
round,
ceiling,
floor,
compare,
pow,
powInt,
countLeadingZeroBits,
countTrailingZeroBits,
countOneBits,
addWrap,
addChecked,
addSaturated,
bitwiseAnd,
bitwiseXor,
bitwiseOr,
bitwiseNot,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
subWrap,
subChecked,
subSaturated,
mulWrap,
mulSaturated,
mulChecked,
intCast,
divCeil,
divCeilChecked,
divTrunc,
divTruncChecked,
toStr,
isMultipleOf,
minI8,
maxI8,
minU8,
maxU8,
minI16,
maxI16,
minU16,
maxU16,
minI32,
maxI32,
minU32,
maxU32,
minI64,
maxI64,
minU64,
maxU64,
minI128,
maxI128,
minU128,
maxU128,
minF32,
maxF32,
minF64,
maxF64,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toF32,
toF32Checked,
toF64,
toF64Checked,
withoutDecimalPoint,
withDecimalPoint,
f32ToParts,
f64ToParts,
f32FromParts,
f64FromParts,
]
imports [
Bool.{ Bool },
Result.{ Result },
]
module [
Num,
Int,
Frac,
Integer,
FloatingPoint,
I128,
I64,
I32,
I16,
I8,
U128,
U64,
U32,
U16,
U8,
Signed128,
Signed64,
Signed32,
Signed16,
Signed8,
Unsigned128,
Unsigned64,
Unsigned32,
Unsigned16,
Unsigned8,
Dec,
F64,
F32,
Decimal,
Binary32,
Binary64,
e,
pi,
tau,
abs,
absDiff,
neg,
add,
sub,
mul,
min,
max,
isLt,
isLte,
isGt,
isGte,
isApproxEq,
sin,
cos,
tan,
atan,
acos,
asin,
isZero,
isEven,
isOdd,
toFrac,
isPositive,
isNegative,
isNaN,
isInfinite,
isFinite,
rem,
remChecked,
div,
divChecked,
sqrt,
sqrtChecked,
log,
logChecked,
round,
ceiling,
floor,
compare,
pow,
powInt,
countLeadingZeroBits,
countTrailingZeroBits,
countOneBits,
addWrap,
addChecked,
addSaturated,
bitwiseAnd,
bitwiseXor,
bitwiseOr,
bitwiseNot,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
subWrap,
subChecked,
subSaturated,
mulWrap,
mulSaturated,
mulChecked,
intCast,
divCeil,
divCeilChecked,
divTrunc,
divTruncChecked,
toStr,
isMultipleOf,
minI8,
maxI8,
minU8,
maxU8,
minI16,
maxI16,
minU16,
maxU16,
minI32,
maxI32,
minU32,
maxU32,
minI64,
maxI64,
minU64,
maxU64,
minI128,
maxI128,
minU128,
maxU128,
minF32,
maxF32,
minF64,
maxF64,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toF32,
toF32Checked,
toF64,
toF64Checked,
withoutDecimalPoint,
withDecimalPoint,
f32ToParts,
f64ToParts,
f32FromParts,
f64FromParts,
]
import Bool exposing [Bool]
import Result exposing [Result]
## Represents a number that could be either an [Int] or a [Frac].
##

View file

@ -1,6 +1,6 @@
interface Result
exposes [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
imports [Bool.{ Bool }]
module [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
import Bool exposing [Bool]
## The result of an operation that could fail: either the operation went
## okay, or else there was an error of some sort.

View file

@ -1,37 +1,35 @@
interface Set
exposes [
Set,
empty,
withCapacity,
reserve,
releaseExcessCapacity,
single,
walk,
walkUntil,
keepIf,
dropIf,
insert,
len,
isEmpty,
capacity,
remove,
contains,
toList,
fromList,
union,
intersection,
difference,
map,
joinMap,
]
imports [
List,
Bool.{ Bool, Eq },
Dict.{ Dict },
Num.{ U64 },
Hash.{ Hash, Hasher },
Inspect.{ Inspect, Inspector, InspectFormatter },
]
module [
Set,
empty,
withCapacity,
reserve,
releaseExcessCapacity,
single,
walk,
walkUntil,
keepIf,
dropIf,
insert,
len,
isEmpty,
capacity,
remove,
contains,
toList,
fromList,
union,
intersection,
difference,
map,
joinMap,
]
import List
import Bool exposing [Bool, Eq]
import Dict
import Num exposing [U64]
import Hash exposing [Hash, Hasher]
import Inspect exposing [Inspect, Inspector, InspectFormatter]
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
## type which stores a collection of unique values, without any ordering

View file

@ -326,55 +326,53 @@
## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)).
##
## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice.
interface Str
exposes [
Utf8Problem,
Utf8ByteProblem,
concat,
isEmpty,
joinWith,
split,
repeat,
countUtf8Bytes,
toUtf8,
fromUtf8,
startsWith,
endsWith,
trim,
trimStart,
trimEnd,
toDec,
toF64,
toF32,
toU128,
toI128,
toU64,
toI64,
toU32,
toI32,
toU16,
toI16,
toU8,
toI8,
replaceEach,
replaceFirst,
replaceLast,
splitFirst,
splitLast,
walkUtf8,
walkUtf8WithIndex,
reserve,
releaseExcessCapacity,
withCapacity,
withPrefix,
contains,
]
imports [
Bool.{ Bool, Eq },
Result.{ Result },
List,
Num.{ Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
]
module [
Utf8Problem,
Utf8ByteProblem,
concat,
isEmpty,
joinWith,
split,
repeat,
countUtf8Bytes,
toUtf8,
fromUtf8,
startsWith,
endsWith,
trim,
trimStart,
trimEnd,
toDec,
toF64,
toF32,
toU128,
toI128,
toU64,
toI64,
toU32,
toI32,
toU16,
toI16,
toU8,
toI8,
replaceEach,
replaceFirst,
replaceLast,
splitFirst,
splitLast,
walkUtf8,
walkUtf8WithIndex,
reserve,
releaseExcessCapacity,
withCapacity,
withPrefix,
contains,
]
import Bool exposing [Bool]
import Result exposing [Result]
import List
import Num exposing [Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
Utf8ByteProblem : [
InvalidStartByte,

View file

@ -1,44 +1,18 @@
## THIS MODULE IS DEPRECATED AND CURRENTLY IN THE PROCESS OF BEING REMOVED
## FROM STD LIBRARY
interface TotallyNotJson
exposes [
Json,
json,
jsonWithOptions,
]
imports [
List,
Str,
Result.{ Result },
Encode,
Encode.{
Encoder,
EncoderFormatting,
appendWith,
},
Decode,
Decode.{
DecoderFormatting,
DecodeResult,
},
Num.{
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
F32,
F64,
Dec,
},
Bool.{ Bool, Eq },
Result,
]
module [
Json,
json,
jsonWithOptions,
]
import List
import Str
import Result
import Encode exposing [EncoderFormatting, appendWith]
import Decode exposing [DecoderFormatting, DecodeResult]
import Num exposing [U8, U16, U64, F32, F64, Dec]
import Bool exposing [Bool]
## An opaque type with the `EncoderFormatting` and
## `DecoderFormatting` abilities.

View file

@ -1,3 +1,15 @@
package "builtins"
exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Box, TotallyNotJson, Inspect]
packages {}
package [
Str,
Num,
Bool,
Result,
List,
Dict,
Set,
Decode,
Encode,
Hash,
Box,
TotallyNotJson,
Inspect,
] {}

View file

@ -1,5 +1,5 @@
use crate::env::Env;
use crate::procedure::References;
use crate::procedure::{QualifiedReference, References};
use crate::scope::{PendingAbilitiesInScope, Scope};
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
use roc_module::ident::{Ident, Lowercase, TagName};
@ -17,7 +17,7 @@ use roc_types::types::{
pub struct Annotation {
pub typ: Type,
pub introduced_variables: IntroducedVariables,
pub references: VecSet<Symbol>,
pub references: References,
pub aliases: VecMap<Symbol, Alias>,
}
@ -28,9 +28,7 @@ impl Annotation {
references: &mut References,
introduced_variables: &mut IntroducedVariables,
) {
for symbol in self.references.iter() {
references.insert_type_lookup(*symbol);
}
references.union_mut(&self.references);
introduced_variables.union(&self.introduced_variables);
@ -291,7 +289,7 @@ pub(crate) fn canonicalize_annotation(
annotation_for: AnnotationFor,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = VecSet::default();
let mut references = References::new();
let mut aliases = VecMap::default();
let (annotation, region) = match annotation {
@ -381,13 +379,17 @@ pub(crate) fn make_apply_symbol(
scope: &mut Scope,
module_name: &str,
ident: &str,
references: &mut References,
) -> Result<Symbol, Type> {
if module_name.is_empty() {
// Since module_name was empty, this is an unqualified type.
// Look it up in scope!
match scope.lookup_str(ident, region) {
Ok(symbol) => Ok(symbol),
Ok(symbol) => {
references.insert_type_lookup(symbol, QualifiedReference::Unqualified);
Ok(symbol)
}
Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
@ -396,7 +398,10 @@ pub(crate) fn make_apply_symbol(
}
} else {
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => Ok(symbol),
Ok(symbol) => {
references.insert_type_lookup(symbol, QualifiedReference::Qualified);
Ok(symbol)
}
Err(problem) => {
// Either the module wasn't imported, or
// it was imported but it doesn't expose this ident.
@ -537,7 +542,7 @@ fn can_annotation_help(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut References,
) -> Type {
use roc_parse::ast::TypeAnnotation::*;
@ -580,15 +585,14 @@ fn can_annotation_help(
Type::Function(args, Box::new(closure), Box::new(ret))
}
Apply(module_name, ident, type_arguments) => {
let symbol = match make_apply_symbol(env, region, scope, module_name, ident) {
let symbol = match make_apply_symbol(env, region, scope, module_name, ident, references)
{
Err(problem) => return problem,
Ok(symbol) => symbol,
};
let mut args = Vec::new();
references.insert(symbol);
if scope.abilities_store.is_ability(symbol) {
let fresh_ty_var = find_fresh_var_name(introduced_variables);
@ -744,7 +748,7 @@ fn can_annotation_help(
let mut vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(loc_vars.len());
references.insert(symbol);
references.insert_type_lookup(symbol, QualifiedReference::Unqualified);
for loc_var in *loc_vars {
let var = match loc_var.value {
@ -1057,7 +1061,7 @@ fn canonicalize_has_clause(
introduced_variables: &mut IntroducedVariables,
clause: &Loc<roc_parse::ast::ImplementsClause<'_>>,
pending_abilities_in_scope: &PendingAbilitiesInScope,
references: &mut VecSet<Symbol>,
references: &mut References,
) -> Result<(), Type> {
let Loc {
region,
@ -1080,7 +1084,7 @@ fn canonicalize_has_clause(
{
let ability = match ability {
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
let symbol = make_apply_symbol(env, region, scope, module_name, ident)?;
let symbol = make_apply_symbol(env, region, scope, module_name, ident, references)?;
// Ability defined locally, whose members we are constructing right now...
if !pending_abilities_in_scope.contains_key(&symbol)
@ -1098,7 +1102,6 @@ fn canonicalize_has_clause(
}
};
references.insert(ability);
let already_seen = can_abilities.insert(ability);
if already_seen {
@ -1132,7 +1135,7 @@ fn can_extension_type(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut References,
opt_ext: &Option<&Loc<TypeAnnotation>>,
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
) -> (Type, ExtImplicitOpenness) {
@ -1335,7 +1338,7 @@ fn can_assigned_fields<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut References,
) -> SendMap<Lowercase, RecordField<Type>> {
use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*;
@ -1450,7 +1453,7 @@ fn can_assigned_tuple_elems(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut References,
) -> VecMap<usize, Type> {
let mut elem_types = VecMap::with_capacity(elems.len());
@ -1484,7 +1487,7 @@ fn can_tags<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut References,
) -> Vec<(TagName, Vec<Type>)> {
let mut tag_types = Vec::with_capacity(tags.len());

View file

@ -28,6 +28,8 @@ use roc_collections::{ImSet, MutMap, SendMap};
use roc_error_macros::internal_error;
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::ident::ModuleName;
use roc_module::ident::QualifiedModuleName;
use roc_module::symbol::IdentId;
use roc_module::symbol::ModuleId;
use roc_module::symbol::Symbol;
@ -52,6 +54,10 @@ use roc_types::types::MemberImpl;
use roc_types::types::OptAbleType;
use roc_types::types::{Alias, Type};
use std::fmt::Debug;
use std::fs;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Def {
@ -158,6 +164,12 @@ enum PendingValueDef<'a> {
&'a Loc<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Expr<'a>>,
),
/// Ingested file
IngestedFile(
Loc<Pattern>,
Loc<ast::TypeAnnotation<'a>>,
Loc<ast::StrLiteral<'a>>,
),
}
impl PendingValueDef<'_> {
@ -166,6 +178,7 @@ impl PendingValueDef<'_> {
PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern,
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
}
}
}
@ -357,9 +370,7 @@ fn canonicalize_alias<'a>(
);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.insert_type_lookup(symbol);
}
output.references.union_mut(&can_ann.references);
let mut can_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
@ -495,7 +506,7 @@ fn canonicalize_claimed_ability_impl<'a>(
// OPTION-1: The implementation identifier is the only identifier of that name in the
// scope. For example,
//
// interface F imports [] exposes []
// module []
//
// Hello := {} implements [Encoding.{ toEncoder }]
//
@ -507,7 +518,9 @@ fn canonicalize_claimed_ability_impl<'a>(
// OPTION-2: The implementation identifier is a unique shadow of the ability member,
// which has also been explicitly imported. For example,
//
// interface F imports [Encoding.{ toEncoder }] exposes []
// module []
//
// import Encoding exposing [toEncoder]
//
// Hello := {} implements [Encoding.{ toEncoder }]
//
@ -703,6 +716,8 @@ fn canonicalize_opaque<'a>(
AliasKind::Opaque,
)?;
let mut references = References::new();
let mut derived_defs = Vec::new();
if let Some(has_abilities) = has_abilities {
let has_abilities = has_abilities.value.collection();
@ -721,7 +736,8 @@ fn canonicalize_opaque<'a>(
// Op := {} has [Eq]
let (ability, members) = match ability.value {
ast::TypeAnnotation::Apply(module_name, ident, []) => {
match make_apply_symbol(env, region, scope, module_name, ident) {
match make_apply_symbol(env, region, scope, module_name, ident, &mut references)
{
Ok(ability) => {
let opt_members = scope
.abilities_store
@ -914,6 +930,8 @@ fn canonicalize_opaque<'a>(
}
}
output.references.union_mut(&references);
Ok(CanonicalizedOpaque {
opaque_def: alias,
derived_defs,
@ -928,7 +946,12 @@ pub(crate) fn canonicalize_defs<'a>(
scope: &mut Scope,
loc_defs: &'a mut roc_parse::ast::Defs<'a>,
pattern_type: PatternType,
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
) -> (
CanDefs,
Output,
MutMap<Symbol, Region>,
Vec<IntroducedImport>,
) {
// Canonicalizing defs while detecting shadowing involves a multi-step process:
//
// 1. Go through each of the patterns.
@ -955,7 +978,7 @@ pub(crate) fn canonicalize_defs<'a>(
// there are opaques that implement an ability using a value symbol). But, value symbols might
// shadow symbols defined in a local ability def.
for (_, either_index) in loc_defs.tags.iter().enumerate() {
for either_index in loc_defs.tags.iter() {
if let Ok(type_index) = either_index.split() {
let type_def = &loc_defs.type_defs[type_index.index()];
let pending_type_def = to_pending_type_def(env, type_def, scope, pattern_type);
@ -978,6 +1001,7 @@ pub(crate) fn canonicalize_defs<'a>(
env,
var_store,
value_def,
region,
scope,
&pending_abilities_in_scope,
&mut output,
@ -1034,7 +1058,12 @@ fn canonicalize_value_defs<'a>(
pattern_type: PatternType,
mut aliases: VecMap<Symbol, Alias>,
mut symbols_introduced: MutMap<Symbol, Region>,
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
) -> (
CanDefs,
Output,
MutMap<Symbol, Region>,
Vec<IntroducedImport>,
) {
// Canonicalize all the patterns, record shadowing problems, and store
// the ast::Expr values in pending_exprs for further canonicalization
// once we've finished assembling the entire scope.
@ -1043,6 +1072,8 @@ fn canonicalize_value_defs<'a>(
let mut pending_expects = Vec::with_capacity(value_defs.len());
let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
let mut imports_introduced = Vec::with_capacity(value_defs.len());
for loc_pending_def in value_defs {
match loc_pending_def.value {
PendingValue::Def(pending_def) => {
@ -1062,6 +1093,11 @@ fn canonicalize_value_defs<'a>(
PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect);
}
PendingValue::ModuleImport(introduced_import) => {
imports_introduced.push(introduced_import);
}
PendingValue::InvalidIngestedFile => { /* skip */ }
PendingValue::ImportNameConflict => { /* skip */ }
}
}
@ -1171,7 +1207,7 @@ fn canonicalize_value_defs<'a>(
aliases,
};
(can_defs, output, symbols_introduced)
(can_defs, output, symbols_introduced, imports_introduced)
}
struct CanonicalizedTypeDefs<'a> {
@ -1385,9 +1421,7 @@ fn resolve_abilities(
);
// Record all the annotation's references in output.references.lookups
for symbol in member_annot.references {
output.references.insert_type_lookup(symbol);
}
output.references.union_mut(&member_annot.references);
// What variables in the annotation are bound to the parent ability, and what variables
// are bound to some other ability?
@ -2302,6 +2336,69 @@ fn canonicalize_pending_value_def<'a>(
None,
)
}
IngestedFile(loc_pattern, loc_ann, path_literal) => {
let relative_path =
if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value {
ingested_path
} else {
todo!(
"Only plain strings are supported. Other cases should be made impossible here"
);
};
let mut file_path: PathBuf = env.module_path.into();
// Remove the header file name and push the new path.
file_path.pop();
file_path.push(relative_path);
let mut bytes = vec![];
let expr = match fs::File::open(&file_path)
.and_then(|mut file| file.read_to_end(&mut bytes))
{
Ok(_) => Expr::IngestedFile(file_path.into(), Arc::new(bytes), var_store.fresh()),
Err(e) => {
env.problems.push(Problem::FileProblem {
filename: file_path.to_path_buf(),
error: e.kind(),
});
Expr::RuntimeError(RuntimeError::ReadIngestedFileError {
filename: file_path.to_path_buf(),
error: e.kind(),
region: path_literal.region,
})
}
};
let loc_expr = Loc::at(path_literal.region, expr);
let can_ann = canonicalize_annotation(
env,
scope,
&loc_ann.value,
loc_ann.region,
var_store,
pending_abilities_in_scope,
AnnotationFor::Value,
);
output.references.union_mut(&can_ann.references);
let def = single_can_def(
loc_pattern,
loc_expr,
var_store.fresh(),
Some(Loc::at(loc_ann.region, can_ann)),
SendMap::default(),
);
DefOutput {
output,
references: DefReferences::Value(References::new()),
def,
}
}
};
// Disallow ability specializations that aren't on the toplevel (note: we might loosen this
@ -2460,7 +2557,7 @@ pub fn can_defs_with_return<'a>(
loc_defs: &'a mut Defs<'a>,
loc_ret: &'a Loc<ast::Expr<'a>>,
) -> (Expr, Output) {
let (unsorted, defs_output, symbols_introduced) = canonicalize_defs(
let (unsorted, defs_output, symbols_introduced, imports_introduced) = canonicalize_defs(
env,
Output::default(),
var_store,
@ -2494,6 +2591,8 @@ pub fn can_defs_with_return<'a>(
}
}
report_unused_imports(imports_introduced, &output.references, env, scope);
let mut loc_expr: Loc<Expr> = ret_expr;
for declaration in declarations.into_iter().rev() {
@ -2503,6 +2602,28 @@ pub fn can_defs_with_return<'a>(
(loc_expr.value, output)
}
pub fn report_unused_imports(
imports_introduced: Vec<IntroducedImport>,
references: &References,
env: &mut Env<'_>,
scope: &mut Scope,
) {
for import in imports_introduced {
if references.has_module_lookup(import.module_id) {
for (symbol, region) in &import.exposed_symbols {
if !references.has_unqualified_type_or_value_lookup(*symbol)
&& !scope.abilities_store.is_specialization_name(*symbol)
&& !import.is_task(env)
{
env.problem(Problem::UnusedImport(*symbol, *region));
}
}
} else if !import.is_task(env) {
env.problem(Problem::UnusedModuleImport(import.module_id, import.region));
}
}
}
fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
match decl {
Declaration::Declare(def) => {
@ -2750,7 +2871,10 @@ enum PendingValue<'a> {
Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>),
ModuleImport(IntroducedImport),
SignatureDefMismatch,
InvalidIngestedFile,
ImportNameConflict,
}
struct PendingExpectOrDbg<'a> {
@ -2758,10 +2882,28 @@ struct PendingExpectOrDbg<'a> {
preceding_comment: Region,
}
pub struct IntroducedImport {
module_id: ModuleId,
region: Region,
exposed_symbols: Vec<(Symbol, Region)>,
}
impl IntroducedImport {
pub fn is_task(&self, env: &Env<'_>) -> bool {
// Temporarily needed for `!` convenience. Can be removed when Task becomes a builtin.
match env.qualified_module_ids.get_name(self.module_id) {
Some(name) => name.as_inner().as_str() == "Task",
None => false,
}
}
}
#[allow(clippy::too_many_arguments)]
fn to_pending_value_def<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>,
region: Region,
scope: &mut Scope,
pending_abilities_in_scope: &PendingAbilitiesInScope,
output: &mut Output,
@ -2875,6 +3017,116 @@ fn to_pending_value_def<'a>(
preceding_comment: *preceding_comment,
}),
ModuleImport(module_import) => {
let qualified_module_name: QualifiedModuleName = module_import.name.value.into();
let module_name = qualified_module_name.module.clone();
let pq_module_name = qualified_module_name.into_pq_module_name(env.opt_shorthand);
let module_id = env
.qualified_module_ids
.get_id(&pq_module_name)
.expect("Module id should have been added in load");
let name_with_alias = match module_import.alias {
Some(alias) => ModuleName::from(alias.item.value.as_str()),
None => module_name.clone(),
};
if let Err(existing_import) =
scope
.modules
.insert(name_with_alias.clone(), module_id, region)
{
env.problems.push(Problem::ImportNameConflict {
name: name_with_alias,
is_alias: module_import.alias.is_some(),
new_module_id: module_id,
new_import_region: region,
existing_import,
});
return PendingValue::ImportNameConflict;
}
let exposed_names = module_import
.exposed
.map(|kw| kw.item.items)
.unwrap_or_default();
if exposed_names.is_empty() && !env.home.is_builtin() && module_id.is_automatically_imported() {
env.problems.push(Problem::ExplicitBuiltinImport(module_id, region));
}
let exposed_ids = env
.dep_idents
.get(&module_id)
.expect("Module id should have been added in load");
let mut exposed_symbols = Vec::with_capacity(exposed_names.len());
for loc_name in exposed_names {
let exposed_name = loc_name.value.item();
let name = exposed_name.as_str();
let ident = Ident::from(name);
match exposed_ids.get_id(name) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);
exposed_symbols.push((symbol, loc_name.region));
if let Err((_shadowed_symbol, existing_symbol_region)) = scope.import_symbol(ident, symbol, loc_name.region) {
if symbol.is_automatically_imported() {
env.problem(Problem::ExplicitBuiltinTypeImport(
symbol,
loc_name.region,
));
} else {
env.problem(Problem::ImportShadowsSymbol {
region: loc_name.region,
new_symbol: symbol,
existing_symbol_region,
})
}
}
}
None => {
env.problem(Problem::RuntimeError(RuntimeError::ValueNotExposed {
module_name: module_name.clone(),
ident,
region: loc_name.region,
exposed_values: exposed_ids.exposed_values(),
}))
}
}
}
PendingValue::ModuleImport(IntroducedImport {
module_id,
region,
exposed_symbols,
})
}
IngestedFileImport(ingested_file) => {
let typed_ident = ingested_file.name.item.extract_spaces().item;
let symbol = match scope.introduce(typed_ident.ident.value.into(), typed_ident.ident.region) {
Ok(symbol ) => symbol,
Err((original, shadow, _)) => {
env.problem(Problem::Shadowing {
original_region: original.region,
shadow,
kind: ShadowKind::Variable
});
return PendingValue::InvalidIngestedFile;
}
};
let loc_pattern = Loc::at(typed_ident.ident.region, Pattern::Identifier(symbol));
PendingValue::Def(PendingValueDef::IngestedFile(loc_pattern, typed_ident.ann, ingested_file.path))
}
Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"),
}
}

View file

@ -130,6 +130,13 @@ fn desugar_value_def<'a>(
preceding_comment: *preceding_comment,
}
}
ModuleImport(roc_parse::ast::ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
}) => *def,
IngestedFileImport(_) => *def,
Stmt(stmt_expr) => {
// desugar into a Body({}, stmt_expr)
@ -247,6 +254,7 @@ pub fn desugar_value_def_suffixed<'a>(arena: &'a Bump, value_def: ValueDef<'a>)
// TODO support desugaring of Dbg, Expect, and ExpectFx
Dbg { .. } | Expect { .. } | ExpectFx { .. } => value_def,
ModuleImport { .. } | IngestedFileImport(_) => value_def,
Stmt(..) => {
internal_error!(
@ -281,7 +289,6 @@ pub fn desugar_expr<'a>(
| UnappliedRecordBuilder { .. }
| Tag(_)
| OpaqueRef(_)
| IngestedFile(_, _)
| Crash => loc_expr,
Str(str_literal) => match str_literal {

View file

@ -1,9 +1,11 @@
use std::path::Path;
use crate::procedure::References;
use crate::scope::Scope;
use bumpalo::Bump;
use roc_collections::{MutMap, VecSet};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIdsByModule, ModuleId, ModuleIds, Symbol};
use roc_module::ident::{Ident, ModuleName};
use roc_module::symbol::{IdentIdsByModule, ModuleId, PQModuleName, PackageModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
@ -13,9 +15,11 @@ pub struct Env<'a> {
/// are assumed to be relative to this path.
pub home: ModuleId,
pub module_path: &'a Path,
pub dep_idents: &'a IdentIdsByModule,
pub module_ids: &'a ModuleIds,
pub qualified_module_ids: &'a PackageModuleIds<'a>,
/// Problems we've encountered along the way, which will be reported to the user at the end.
pub problems: Vec<Problem>,
@ -35,26 +39,32 @@ pub struct Env<'a> {
pub top_level_symbols: VecSet<Symbol>,
pub arena: &'a Bump,
pub opt_shorthand: Option<&'a str>,
}
impl<'a> Env<'a> {
pub fn new(
arena: &'a Bump,
home: ModuleId,
module_path: &'a Path,
dep_idents: &'a IdentIdsByModule,
module_ids: &'a ModuleIds,
qualified_module_ids: &'a PackageModuleIds<'a>,
opt_shorthand: Option<&'a str>,
) -> Env<'a> {
Env {
arena,
home,
module_path,
dep_idents,
module_ids,
qualified_module_ids,
problems: Vec::new(),
closures: MutMap::default(),
qualified_value_lookups: VecSet::default(),
qualified_type_lookups: VecSet::default(),
tailcallable_symbol: None,
top_level_symbols: VecSet::default(),
opt_shorthand,
}
}
@ -72,17 +82,20 @@ impl<'a> Env<'a> {
let module_name = ModuleName::from(module_name_str);
match self.module_ids.get_id(&module_name) {
match scope.modules.get_id(&module_name) {
Some(module_id) => self.qualified_lookup_help(scope, module_id, ident, region),
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.module_ids
.available_modules()
module_name: module_name.clone(),
imported_modules: scope
.modules
.available_names()
.map(|string| string.as_ref().into())
.collect(),
region,
module_exists: false,
module_exists: self
.qualified_module_ids
.get_id(&PQModuleName::Unqualified(module_name))
.is_some(),
}),
}
}
@ -94,7 +107,11 @@ impl<'a> Env<'a> {
ident: &str,
region: Region,
) -> Result<Symbol, RuntimeError> {
self.qualified_lookup_help(scope, module_id, ident, region)
if !scope.modules.has_id(module_id) {
Err(self.module_exists_but_not_imported(scope, module_id, region))
} else {
self.qualified_lookup_help(scope, module_id, ident, region)
}
}
/// Returns Err if the symbol resolved, but it was not exposed by the given module
@ -153,43 +170,46 @@ impl<'a> Env<'a> {
Ok(symbol)
}
None => {
let exposed_values = exposed_ids
.ident_strs()
.filter(|(_, ident)| ident.starts_with(|c: char| c.is_lowercase()))
.map(|(_, ident)| Lowercase::from(ident))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name: self
.module_ids
.get_name(module_id)
.expect("Module ID known, but not in the module IDs somehow")
.clone(),
ident: Ident::from(ident),
region,
exposed_values,
})
}
None => Err(RuntimeError::ValueNotExposed {
module_name: self
.qualified_module_ids
.get_name(module_id)
.expect("Module ID known, but not in the module IDs somehow")
.as_inner()
.clone(),
ident: Ident::from(ident),
region,
exposed_values: exposed_ids.exposed_values(),
}),
},
None => Err(RuntimeError::ModuleNotImported {
module_name: self
.module_ids
.get_name(module_id)
.expect("Module ID known, but not in the module IDs somehow")
.clone(),
imported_modules: self
.dep_idents
.keys()
.filter_map(|module_id| self.module_ids.get_name(*module_id))
.map(|module_name| module_name.as_ref().into())
.collect(),
region,
module_exists: true,
}),
_ => Err(self.module_exists_but_not_imported(scope, module_id, region)),
}
}
}
fn module_exists_but_not_imported(
&self,
scope: &Scope,
module_id: ModuleId,
region: Region,
) -> RuntimeError {
RuntimeError::ModuleNotImported {
module_name: self
.qualified_module_ids
.get_name(module_id)
.expect("Module ID known, but not in the module IDs somehow")
.as_inner()
.clone(),
imported_modules: scope
.modules
.available_names()
.map(|string| string.as_ref().into())
.collect(),
region,
module_exists: true,
}
}
pub fn problem(&mut self, problem: Problem) {
self.problems.push(problem)
}

View file

@ -8,7 +8,7 @@ use crate::num::{
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumBound,
};
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern, PermitShadows};
use crate::procedure::References;
use crate::procedure::{QualifiedReference, References};
use crate::scope::Scope;
use crate::traverse::{walk_expr, Visitor};
use roc_collections::soa::Index;
@ -27,8 +27,6 @@ use roc_types::num::SingleQuoteBound;
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display};
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use std::{char, u32};
@ -742,48 +740,6 @@ pub fn canonicalize_expr<'a>(
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::IngestedFile(file_path, _) => match File::open(file_path) {
Ok(mut file) => {
let mut bytes = vec![];
match file.read_to_end(&mut bytes) {
Ok(_) => (
Expr::IngestedFile(
file_path.to_path_buf().into(),
Arc::new(bytes),
var_store.fresh(),
),
Output::default(),
),
Err(e) => {
env.problems.push(Problem::FileProblem {
filename: file_path.to_path_buf(),
error: e.kind(),
});
// This will not manifest as a real runtime error and is just returned to have a value here.
// The pushed FileProblem will be fatal to compilation.
(
Expr::RuntimeError(roc_problem::can::RuntimeError::NoImplementation),
Output::default(),
)
}
}
}
Err(e) => {
env.problems.push(Problem::FileProblem {
filename: file_path.to_path_buf(),
error: e.kind(),
});
// This will not manifest as a real runtime error and is just returned to have a value here.
// The pushed FileProblem will be fatal to compilation.
(
Expr::RuntimeError(roc_problem::can::RuntimeError::NoImplementation),
Output::default(),
)
}
},
ast::Expr::SingleQuote(string) => {
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
@ -885,7 +841,9 @@ pub fn canonicalize_expr<'a>(
}
Ok((name, opaque_def)) => {
let argument = Box::new(args.pop().unwrap());
output.references.insert_type_lookup(name);
output
.references
.insert_type_lookup(name, QualifiedReference::Unqualified);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
@ -1197,7 +1155,9 @@ pub fn canonicalize_expr<'a>(
}
Ok((name, opaque_def)) => {
let mut output = Output::default();
output.references.insert_type_lookup(name);
output
.references
.insert_type_lookup(name, QualifiedReference::Unqualified);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
@ -1892,7 +1852,9 @@ fn canonicalize_var_lookup(
// Look it up in scope!
match scope.lookup_str(ident, region) {
Ok(symbol) => {
output.references.insert_value_lookup(symbol);
output
.references
.insert_value_lookup(symbol, QualifiedReference::Unqualified);
if scope.abilities_store.is_ability_member_name(symbol) {
AbilityMember(
@ -1915,7 +1877,9 @@ fn canonicalize_var_lookup(
// Look it up in the env!
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => {
output.references.insert_value_lookup(symbol);
output
.references
.insert_value_lookup(symbol, QualifiedReference::Qualified);
if scope.abilities_store.is_ability_member_name(symbol) {
AbilityMember(
@ -2426,7 +2390,6 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::Expr::Expect(_, _)
| ast::Expr::When(_, _)
| ast::Expr::Backpassing(_, _, _)
| ast::Expr::IngestedFile(_, _)
| ast::Expr::SpaceBefore(_, _)
| ast::Expr::Str(StrLiteral::Block(_))
| ast::Expr::SpaceAfter(_, _)

View file

@ -1,19 +1,22 @@
use std::path::Path;
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
use crate::annotation::{canonicalize_annotation, AnnotationFor};
use crate::def::{canonicalize_defs, Def};
use crate::def::{canonicalize_defs, report_unused_imports, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
use crate::expr::{
ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
};
use crate::pattern::{BindingsFromPattern, Pattern};
use crate::procedure::References;
use crate::scope::Scope;
use bumpalo::Bump;
use roc_collections::{MutMap, SendMap, VecMap, VecSet};
use roc_error_macros::internal_error;
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol};
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, PackageModuleIds, Symbol};
use roc_parse::ast::{Defs, TypeAnnotation};
use roc_parse::header::HeaderType;
use roc_parse::pattern::PatternType;
@ -127,7 +130,6 @@ pub struct Module {
pub exposed_imports: MutMap<Symbol, Region>,
pub exposed_symbols: VecSet<Symbol>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
/// all aliases. `bool` indicates whether it is exposed
pub aliases: MutMap<Symbol, (bool, Alias)>,
pub rigid_variables: RigidVariables,
@ -152,7 +154,6 @@ pub struct ModuleOutput {
pub exposed_symbols: VecSet<Symbol>,
pub problems: Vec<Problem>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
pub pending_derives: PendingDerives,
pub scope: Scope,
@ -275,21 +276,38 @@ pub fn canonicalize_module_defs<'a>(
loc_defs: &'a mut Defs<'a>,
header_type: &roc_parse::header::HeaderType,
home: ModuleId,
module_path: &str,
module_path: &'a str,
src: &'a str,
module_ids: &'a ModuleIds,
qualified_module_ids: &'a PackageModuleIds<'a>,
exposed_ident_ids: IdentIds,
dep_idents: &'a IdentIdsByModule,
aliases: MutMap<Symbol, Alias>,
imported_abilities_state: PendingAbilitiesStore,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
initial_scope: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: VecSet<Symbol>,
symbols_from_requires: &[(Loc<Symbol>, Loc<TypeAnnotation<'a>>)],
var_store: &mut VarStore,
opt_shorthand: Option<&'a str>,
) -> ModuleOutput {
let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, exposed_ident_ids, imported_abilities_state);
let mut env = Env::new(arena, home, dep_idents, module_ids);
let mut scope = Scope::new(
home,
qualified_module_ids
.get_name(home)
.expect("home module not found")
.as_inner()
.to_owned(),
exposed_ident_ids,
imported_abilities_state,
);
let mut env = Env::new(
arena,
home,
arena.alloc(Path::new(module_path)),
dep_idents,
qualified_module_ids,
opt_shorthand,
);
for (name, alias) in aliases.into_iter() {
scope.add_alias(
@ -317,26 +335,21 @@ pub fn canonicalize_module_defs<'a>(
let mut rigid_variables = RigidVariables::default();
// Exposed values are treated like defs that appear before any others, e.g.
//
// imports [Foo.{ bar, baz }]
//
// ...is basically the same as if we'd added these extra defs at the start of the module:
//
// bar = Foo.bar
// baz = Foo.baz
// Iniital scope values are treated like defs that appear before any others.
// They include builtin types that are automatically imported, and for a platform
// package, the required values from the app.
//
// Here we essentially add those "defs" to "the beginning of the module"
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
for (ident, (symbol, region)) in exposed_imports {
for (ident, (symbol, region)) in initial_scope {
let first_char = ident.as_inline_str().as_str().chars().next().unwrap();
if first_char.is_lowercase() {
match scope.import(ident, symbol, region) {
match scope.import_symbol(ident, symbol, region) {
Ok(()) => {
// Add an entry to exposed_imports using the current module's name
// as the key; e.g. if this is the Foo module and we have
// exposes [Bar.{ baz }] then insert Foo.baz as the key, so when
// Bar exposes [baz] then insert Foo.baz as the key, so when
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
can_exposed_imports.insert(symbol, region);
}
@ -355,7 +368,7 @@ pub fn canonicalize_module_defs<'a>(
// but now we know this symbol by a different identifier, so we still need to add it to
// the scope
match scope.import(ident, symbol, region) {
match scope.import_symbol(ident, symbol, region) {
Ok(()) => {
// here we do nothing special
}
@ -369,7 +382,7 @@ pub fn canonicalize_module_defs<'a>(
}
}
let (defs, output, symbols_introduced) = canonicalize_defs(
let (defs, output, symbols_introduced, imports_introduced) = canonicalize_defs(
&mut env,
Output::default(),
var_store,
@ -410,18 +423,15 @@ pub fn canonicalize_module_defs<'a>(
}
let mut referenced_values = VecSet::default();
let mut referenced_types = VecSet::default();
// Gather up all the symbols that were referenced across all the defs' lookups.
referenced_values.extend(output.references.value_lookups().copied());
referenced_types.extend(output.references.type_lookups().copied());
// Gather up all the symbols that were referenced across all the defs' calls.
referenced_values.extend(output.references.calls().copied());
// Gather up all the symbols that were referenced from other modules.
referenced_values.extend(env.qualified_value_lookups.iter().copied());
referenced_types.extend(env.qualified_type_lookups.iter().copied());
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
@ -433,6 +443,7 @@ pub fn canonicalize_module_defs<'a>(
let new_output = Output {
aliases: output.aliases,
references: output.references,
..Default::default()
};
@ -482,6 +493,8 @@ pub fn canonicalize_module_defs<'a>(
})
.collect();
report_unused_imports(imports_introduced, &output.references, &mut env, &mut scope);
if let GeneratedInfo::Hosted {
effect_symbol,
generated_functions,
@ -545,7 +558,7 @@ pub fn canonicalize_module_defs<'a>(
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
introduced_variables: def_annotation.introduced_variables,
references: Default::default(),
references: References::new(),
aliases: Default::default(),
};
@ -603,7 +616,7 @@ pub fn canonicalize_module_defs<'a>(
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
introduced_variables: def_annotation.introduced_variables,
references: Default::default(),
references: References::new(),
aliases: Default::default(),
};
@ -700,14 +713,12 @@ pub fn canonicalize_module_defs<'a>(
// Incorporate any remaining output.lookups entries into references.
referenced_values.extend(output.references.value_lookups().copied());
referenced_types.extend(output.references.type_lookups().copied());
// Incorporate any remaining output.calls entries into references.
referenced_values.extend(output.references.calls().copied());
// Gather up all the symbols that were referenced from other modules.
referenced_values.extend(env.qualified_value_lookups.iter().copied());
referenced_types.extend(env.qualified_type_lookups.iter().copied());
let mut fix_closures_no_capture_symbols = VecSet::default();
let mut fix_closures_closure_captures = VecMap::default();
@ -803,7 +814,6 @@ pub fn canonicalize_module_defs<'a>(
rigid_variables,
declarations,
referenced_values,
referenced_types,
exposed_imports: can_exposed_imports,
problems: env.problems,
symbols_from_requires,

View file

@ -446,7 +446,10 @@ pub fn canonicalize_pattern<'a>(
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
output.references.insert_type_lookup(opaque);
output.references.insert_type_lookup(
opaque,
crate::procedure::QualifiedReference::Unqualified,
);
Pattern::UnwrappedOpaque {
whole_var: var_store.fresh(),

View file

@ -1,6 +1,6 @@
use crate::expr::Expr;
use crate::pattern::Pattern;
use roc_module::symbol::Symbol;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
@ -46,6 +46,23 @@ impl ReferencesBitflags {
const TYPE_LOOKUP: Self = ReferencesBitflags(2);
const CALL: Self = ReferencesBitflags(4);
const BOUND: Self = ReferencesBitflags(8);
const QUALIFIED: Self = ReferencesBitflags(16);
const UNQUALIFIED: Self = ReferencesBitflags(32);
}
#[derive(Copy, Clone, Debug)]
pub enum QualifiedReference {
Unqualified,
Qualified,
}
impl QualifiedReference {
fn flags(&self, flags: ReferencesBitflags) -> ReferencesBitflags {
match self {
Self::Unqualified => ReferencesBitflags(flags.0 | ReferencesBitflags::UNQUALIFIED.0),
Self::Qualified => ReferencesBitflags(flags.0 | ReferencesBitflags::QUALIFIED.0),
}
}
}
#[derive(Clone, Debug, Default)]
@ -108,12 +125,12 @@ impl References {
}
}
pub fn insert_value_lookup(&mut self, symbol: Symbol) {
self.insert(symbol, ReferencesBitflags::VALUE_LOOKUP);
pub fn insert_value_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) {
self.insert(symbol, qualified.flags(ReferencesBitflags::VALUE_LOOKUP));
}
pub fn insert_type_lookup(&mut self, symbol: Symbol) {
self.insert(symbol, ReferencesBitflags::TYPE_LOOKUP);
pub fn insert_type_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) {
self.insert(symbol, qualified.flags(ReferencesBitflags::TYPE_LOOKUP));
}
pub fn insert_bound(&mut self, symbol: Symbol) {
@ -178,7 +195,24 @@ impl References {
false
}
pub fn has_unqualified_type_or_value_lookup(&self, symbol: Symbol) -> bool {
let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0;
let it = self.symbols.iter().zip(self.bitflags.iter());
for (a, b) in it {
if *a == symbol && b.0 & mask > 0 && b.0 & ReferencesBitflags::UNQUALIFIED.0 > 0 {
return true;
}
}
false
}
pub fn references_type_def(&self, symbol: Symbol) -> bool {
self.has_type_lookup(symbol)
}
pub fn has_module_lookup(&self, module_id: ModuleId) -> bool {
self.symbols.iter().any(|sym| sym.module_id() == module_id)
}
}

View file

@ -1,7 +1,7 @@
use roc_collections::{VecMap, VecSet};
use roc_error_macros::internal_error;
use roc_module::ident::Ident;
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_module::ident::{Ident, ModuleName};
use roc_module::symbol::{IdentId, IdentIds, ModuleId, ScopeModules, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
@ -29,8 +29,11 @@ pub struct Scope {
/// The first `exposed_ident_count` identifiers are exposed
exposed_ident_count: usize,
/// Identifiers that are imported (and introduced in the header)
imports: Vec<(Ident, Symbol, Region)>,
/// Modules that are imported
pub modules: ScopeModules,
/// Identifiers that are imported
imported_symbols: Vec<(Ident, Symbol, Region)>,
/// Shadows of an ability member, for example a local specialization of `eq` for the ability
/// member `Eq implements eq : a, a -> Bool where a implements Eq` gets a shadow symbol it can use for its
@ -50,6 +53,7 @@ pub struct Scope {
impl Scope {
pub fn new(
home: ModuleId,
module_name: ModuleName,
initial_ident_ids: IdentIds,
starting_abilities_store: PendingAbilitiesStore,
) -> Scope {
@ -66,7 +70,8 @@ impl Scope {
aliases: VecMap::default(),
abilities_store: starting_abilities_store,
shadows: VecMap::default(),
imports: default_imports,
modules: ScopeModules::new(home, module_name),
imported_symbols: default_imports,
ignored_locals: VecMap::default(),
}
}
@ -80,9 +85,9 @@ impl Scope {
}
pub fn add_docs_imports(&mut self) {
self.imports
self.imported_symbols
.push(("Dict".into(), Symbol::DICT_DICT, Region::zero()));
self.imports
self.imported_symbols
.push(("Set".into(), Symbol::SET_SET, Region::zero()));
}
@ -111,7 +116,7 @@ impl Scope {
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
let it1 = self.locals.idents_in_scope();
let it2 = self.imports.iter().map(|t| t.0.clone());
let it2 = self.imported_symbols.iter().map(|t| t.0.clone());
it2.chain(it1)
}
@ -137,7 +142,7 @@ impl Scope {
},
None => {
// opaque types can only be wrapped/unwrapped in the scope they are defined in (and below)
let error = if let Some((_, decl_region)) = self.has_imported(opaque_str) {
let error = if let Some((_, decl_region)) = self.has_imported_symbol(opaque_str) {
// specific error for when the opaque is imported, which definitely does not work
RuntimeError::OpaqueOutsideScope {
opaque,
@ -200,8 +205,8 @@ impl Scope {
}
}
fn has_imported(&self, ident: &str) -> Option<(Symbol, Region)> {
for (import, shadow, original_region) in self.imports.iter() {
fn has_imported_symbol(&self, ident: &str) -> Option<(Symbol, Region)> {
for (import, shadow, original_region) in self.imported_symbols.iter() {
if ident == import.as_str() {
return Some((*shadow, *original_region));
}
@ -213,7 +218,7 @@ impl Scope {
/// Is an identifier in scope, either in the locals or imports
fn scope_contains_ident(&self, ident: &str) -> ContainsIdent {
// exposed imports are likely to be small
match self.has_imported(ident) {
match self.has_imported_symbol(ident) {
Some((symbol, region)) => ContainsIdent::InScope(symbol, region),
None => self.locals.contains_ident(ident),
}
@ -377,19 +382,19 @@ impl Scope {
///
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
pub fn import(
pub fn import_symbol(
&mut self,
ident: Ident,
symbol: Symbol,
region: Region,
) -> Result<(), (Symbol, Region)> {
if let Some((s, r)) = self.has_imported(ident.as_str()) {
return Err((s, r));
match self.scope_contains_ident(ident.as_str()) {
ContainsIdent::InScope(symbol, region) => Err((symbol, region)),
ContainsIdent::NotPresent | ContainsIdent::NotInScope(_) => {
self.imported_symbols.push((ident, symbol, region));
Ok(())
}
}
self.imports.push((ident, symbol, region));
Ok(())
}
pub fn add_alias(
@ -421,17 +426,22 @@ impl Scope {
//
// - abilities_store: ability definitions not allowed in inner scopes
// - locals: everything introduced in the inner scope is marked as not in scope in the rollback
// - imports: everything that was imported in the inner scope is removed in the rollback
// - aliases: stored in a VecMap, we just discard anything added in an inner scope
// - exposed_ident_count: unchanged
// - home: unchanged
let aliases_count = self.aliases.len();
let ignored_locals_count = self.ignored_locals.len();
let locals_snapshot = self.locals.in_scope.len();
let imported_symbols_snapshot = self.imported_symbols.len();
let imported_modules_snapshot = self.modules.len();
let result = f(self);
self.aliases.truncate(aliases_count);
self.ignored_locals.truncate(ignored_locals_count);
self.imported_symbols.truncate(imported_symbols_snapshot);
self.modules.truncate(imported_modules_snapshot);
// anything added in the inner scope is no longer in scope now
for i in locals_snapshot..self.locals.in_scope.len() {
@ -649,6 +659,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -668,6 +679,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -697,6 +709,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -718,6 +731,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -735,6 +749,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -796,6 +811,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -806,7 +822,7 @@ mod test {
assert!(scope.lookup(&ident, region).is_err());
assert!(scope.import(ident.clone(), symbol, region).is_ok());
assert!(scope.import_symbol(ident.clone(), symbol, region).is_ok());
assert!(scope.lookup(&ident, region).is_ok());
@ -818,6 +834,7 @@ mod test {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
"#Attr".into(),
IdentIds::default(),
PendingAbilitiesStore::default(),
);
@ -828,7 +845,7 @@ mod test {
let region1 = Region::from_pos(Position { offset: 10 });
let region2 = Region::from_pos(Position { offset: 20 });
scope.import(ident.clone(), symbol, region1).unwrap();
scope.import_symbol(ident.clone(), symbol, region1).unwrap();
let (original, _ident, shadow_symbol) =
scope.introduce(ident.clone(), region2).unwrap_err();

View file

@ -225,7 +225,7 @@ pub fn unwrap_suffixed_expression_apply_help<'a>(
// Any suffixed arguments will be innermost, therefore we unwrap those first
let local_args = arena.alloc_slice_copy(apply_args);
for (_, arg) in local_args.iter_mut().enumerate() {
for arg in local_args.iter_mut() {
match unwrap_suffixed_expression(arena, arg, maybe_def_pat) {
Ok(new_arg) => {
*arg = new_arg;
@ -562,7 +562,7 @@ pub fn unwrap_suffixed_expression_defs_help<'a>(
};
let maybe_suffixed_value_def = match current_value_def {
Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Stmt(..) => None,
Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Stmt(..) | ModuleImport{..} | IngestedFileImport(_) => None,
AnnotatedBody { body_pattern, body_expr, .. } => Some((body_pattern, body_expr)),
Body (def_pattern, def_expr, .. ) => Some((def_pattern, def_expr)),
};

View file

@ -7,12 +7,13 @@ use roc_can::expr::Output;
use roc_can::expr::{canonicalize_expr, Expr};
use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, PackageModuleIds, Symbol};
use roc_problem::can::Problem;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{AliasVar, Type};
use std::hash::Hash;
use std::path::Path;
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
@ -43,7 +44,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
let mut var_store = VarStore::default();
let var = var_store.fresh();
let module_ids = ModuleIds::default();
let qualified_module_ids = PackageModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
@ -60,7 +61,12 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
arena.alloc("TestPath"),
);
let mut scope = Scope::new(home, IdentIds::default(), Default::default());
let mut scope = Scope::new(
home,
"TestPath".into(),
IdentIds::default(),
Default::default(),
);
scope.add_alias(
Symbol::NUM_INT,
Region::zero(),
@ -74,7 +80,14 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(arena, home, &dep_idents, &module_ids);
let mut env = Env::new(
arena,
home,
Path::new("Test.roc"),
&dep_idents,
&qualified_module_ids,
None,
);
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
@ -87,7 +100,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
all_ident_ids.insert(home, scope.locals.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
module_ids: env.qualified_module_ids.clone().into_module_ids(),
all_ident_ids,
};

View file

@ -3682,7 +3682,7 @@ fn constraint_recursive_function(
signature_closure_type,
ret_type,
),
_ => todo!("TODO {:?}", (loc_symbol, &signature)),
_ => todo!("TODO {:?}", (loc_symbol, types[signature])),
};
let region = loc_function_def.region;

View file

@ -1,11 +1,15 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_default_newline, fmt_spaces, INDENT};
use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef,
TypeHeader, ValueDef,
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileImport, ModuleImport, Pattern, Spaces, StrLiteral,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use roc_parse::header::Keyword;
use roc_region::all::Loc;
/// A Located formattable value is also formattable
@ -183,6 +187,180 @@ impl<'a> Formattable for TypeHeader<'a> {
}
}
impl<'a> Formattable for ModuleImport<'a> {
fn is_multiline(&self) -> bool {
let Self {
before_name,
name,
alias,
exposed,
} = self;
!before_name.is_empty()
|| name.is_multiline()
|| alias.is_multiline()
|| match exposed {
Some(a) => a.keyword.is_multiline() || is_collection_multiline(&a.item),
None => false,
}
}
fn format_with_options(
&self,
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
let Self {
before_name,
name,
alias,
exposed,
} = self;
buf.indent(indent);
buf.push_str("import");
let indent = indent + INDENT;
fmt_default_spaces(buf, before_name, indent);
buf.indent(indent);
name.format(buf, indent);
if let Some(alias) = alias {
alias.format(buf, indent);
}
if let Some(exposed) = exposed {
exposed.keyword.format(buf, indent);
let list_indent = if !before_name.is_empty()
|| alias.is_multiline()
|| exposed.keyword.is_multiline()
{
indent
} else {
// Align list with import keyword
indent - INDENT
};
fmt_collection(buf, list_indent, Braces::Square, exposed.item, Newlines::No);
}
}
}
impl<'a> Formattable for IngestedFileImport<'a> {
fn is_multiline(&self) -> bool {
let Self {
before_path,
path: _,
name,
} = self;
!before_path.is_empty() || name.is_multiline()
}
fn format_with_options(
&self,
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
let Self {
before_path,
path,
name,
} = self;
buf.indent(indent);
buf.push_str("import");
let indent = indent + INDENT;
fmt_default_spaces(buf, before_path, indent);
fmt_str_literal(buf, path.value, indent);
name.format(buf, indent);
}
}
impl<'a> Formattable for ImportedModuleName<'a> {
fn is_multiline(&self) -> bool {
// No newlines in module name itself.
false
}
fn format_with_options(
&self,
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
if let Some(package_shorthand) = self.package {
buf.push_str(package_shorthand);
buf.push_str(".");
}
self.name.format(buf, indent);
}
}
impl<'a> Formattable for ImportAlias<'a> {
fn is_multiline(&self) -> bool {
// No newlines in alias itself.
false
}
fn format_with_options(
&self,
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(self.as_str());
}
}
impl Formattable for ImportAsKeyword {
fn is_multiline(&self) -> bool {
false
}
fn format_with_options(
&self,
buf: &mut Buf<'_>,
_parens: crate::annotation::Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(ImportAsKeyword::KEYWORD);
}
}
impl Formattable for ImportExposingKeyword {
fn is_multiline(&self) -> bool {
false
}
fn format_with_options(
&self,
buf: &mut Buf<'_>,
_parens: crate::annotation::Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(ImportExposingKeyword::KEYWORD);
}
}
impl<'a> Formattable for ValueDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::ValueDef::*;
@ -196,6 +374,8 @@ impl<'a> Formattable for ValueDef<'a> {
Expect { condition, .. } => condition.is_multiline(),
ExpectFx { condition, .. } => condition.is_multiline(),
Dbg { condition, .. } => condition.is_multiline(),
ModuleImport(module_import) => module_import.is_multiline(),
IngestedFileImport(ingested_file_import) => ingested_file_import.is_multiline(),
Stmt(loc_expr) => loc_expr.is_multiline(),
}
}
@ -239,6 +419,8 @@ impl<'a> Formattable for ValueDef<'a> {
buf.newline();
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
}
ModuleImport(module_import) => module_import.format(buf, indent),
IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent),
Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent),
}
}

View file

@ -45,7 +45,6 @@ impl<'a> Formattable for Expr<'a> {
| MalformedClosure
| Tag(_)
| OpaqueRef(_)
| IngestedFile(_, _)
| EmptyDefsFinal
| Crash => false,
@ -530,7 +529,6 @@ impl<'a> Formattable for Expr<'a> {
PrecedenceConflict { .. } => {}
MultipleRecordBuilders { .. } => {}
UnappliedRecordBuilder { .. } => {}
IngestedFile(_, _) => {}
}
}
}

View file

@ -5,12 +5,13 @@ use crate::spaces::RemoveSpaces;
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use bumpalo::Bump;
use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces};
use roc_parse::ast::{Collection, CommentOrNewline, Header, Module, Spaced, Spaces};
use roc_parse::header::{
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry, PackageHeader,
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
ImportsKeyword, Keyword, KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader,
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformKeyword,
PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent,
WithKeyword,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -18,8 +19,8 @@ use roc_region::all::Loc;
pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
fmt_comments_only(buf, module.comments.iter(), NewlineAt::Bottom, 0);
match &module.header {
Header::Interface(header) => {
fmt_interface_header(buf, header);
Header::Module(header) => {
fmt_module_header(buf, header);
}
Header::App(header) => {
fmt_app_header(buf, header);
@ -75,6 +76,7 @@ keywords! {
RequiresKeyword,
ProvidesKeyword,
ToKeyword,
PlatformKeyword,
}
impl<V: Formattable> Formattable for Option<V> {
@ -171,20 +173,12 @@ impl<'a, K: Formattable, V: Formattable> Formattable for KeywordItem<'a, K, V> {
}
}
pub fn fmt_interface_header<'a>(buf: &mut Buf, header: &'a InterfaceHeader<'a>) {
pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
buf.indent(0);
buf.push_str("interface");
let indent = INDENT;
fmt_default_spaces(buf, header.before_name, indent);
buf.push_str("module");
// module name
buf.indent(indent);
buf.push_str(header.name.value.as_str());
header.exposes.keyword.format(buf, indent);
fmt_exposes(buf, header.exposes.item, indent);
header.imports.keyword.format(buf, indent);
fmt_imports(buf, header.imports.item, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
fmt_exposes(buf, header.exposes, indent);
}
pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
@ -207,34 +201,33 @@ pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) {
buf.indent(0);
buf.push_str("app");
let indent = INDENT;
fmt_default_spaces(buf, header.before_name, indent);
fmt_str_literal(buf, header.name.value, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_provides, INDENT);
fmt_exposes(buf, header.provides, indent);
if let Some(packages) = &header.packages {
packages.keyword.format(buf, indent);
fmt_packages(buf, packages.item, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
fmt_packages(buf, header.packages.value, indent);
}
pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 {
if spaces.iter().all(|c| c.is_newline()) {
buf.spaces(1);
0
} else {
fmt_default_spaces(buf, spaces, indent);
indent
}
if let Some(imports) = &header.imports {
imports.keyword.format(buf, indent);
fmt_imports(buf, imports.item, indent);
}
header.provides.format(buf, indent);
}
pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) {
buf.indent(0);
buf.push_str("package");
let indent = INDENT;
fmt_default_spaces(buf, header.before_name, indent);
fmt_package_name(buf, header.name.value, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
fmt_exposes(buf, header.exposes, indent);
header.exposes.keyword.format(buf, indent);
fmt_exposes(buf, header.exposes.item, indent);
header.packages.keyword.format(buf, indent);
fmt_packages(buf, header.packages.item, indent);
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
fmt_packages(buf, header.packages.value, indent);
}
pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) {
@ -465,6 +458,15 @@ fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, indent: u16) {
buf.push_str(entry.shorthand);
buf.push(':');
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
let indent = indent + INDENT;
if let Some(spaces_after) = entry.platform_marker {
buf.indent(indent);
buf.push_str(roc_parse::keyword::PLATFORM);
fmt_default_spaces(buf, spaces_after, indent);
}
fmt_package_name(buf, entry.package_name.value, indent);
}

View file

@ -4,14 +4,15 @@ use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{
ast::{
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, Module,
Pattern, PatternAs, RecordBuilderField, Spaced, Spaces, StrLiteral, StrSegment, Tag,
TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileImport, Module,
ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced, Spaces, StrLiteral,
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem,
ModuleName, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
ProvidesTo, To, TypedIdent,
AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
TypedIdent,
},
ident::{BadIdent, UppercaseIdent},
};
@ -282,23 +283,25 @@ impl<'a> RemoveSpaces<'a> for ProvidesTo<'a> {
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let header = match &self.header {
Header::Interface(header) => Header::Interface(InterfaceHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
Header::Module(header) => Header::Module(ModuleHeader {
before_exposes: &[],
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
interface_imports: header.interface_imports.remove_spaces(arena),
}),
Header::App(header) => Header::App(AppHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_provides: &[],
provides: header.provides.remove_spaces(arena),
before_packages: &[],
packages: header.packages.remove_spaces(arena),
old_imports: header.old_imports.remove_spaces(arena),
old_provides_to_new_package: header
.old_provides_to_new_package
.remove_spaces(arena),
}),
Header::Package(header) => Header::Package(PackageHeader {
before_name: &[],
name: header.name.remove_spaces(arena),
before_exposes: &[],
exposes: header.exposes.remove_spaces(arena),
before_packages: &[],
packages: header.packages.remove_spaces(arena),
}),
Header::Platform(header) => Header::Platform(PlatformHeader {
@ -405,6 +408,10 @@ impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
platform_marker: match self.platform_marker {
Some(_) => Some(&[]),
None => None,
},
package_name: self.package_name.remove_spaces(arena),
}
}
@ -567,11 +574,63 @@ impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment: Region::zero(),
},
ModuleImport(module_import) => ModuleImport(module_import.remove_spaces(arena)),
IngestedFileImport(ingested_file_import) => {
IngestedFileImport(ingested_file_import.remove_spaces(arena))
}
Stmt(loc_expr) => Stmt(arena.alloc(loc_expr.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for ModuleImport<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ModuleImport {
before_name: &[],
name: self.name.remove_spaces(arena),
alias: self.alias.remove_spaces(arena),
exposed: self.exposed.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
IngestedFileImport {
before_path: &[],
path: self.path.remove_spaces(arena),
name: self.name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportedModuleName<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
ImportedModuleName {
package: self.package.remove_spaces(arena),
name: self.name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportAlias<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ImportAsKeyword {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ImportExposingKeyword {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for Implements<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Implements::Implements
@ -682,7 +741,6 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::IngestedFile(a, b) => Expr::IngestedFile(a, b),
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),

View file

@ -1124,7 +1124,11 @@ pub fn construct_optimization_passes<'a>(
}
OptLevel::Size => {
pmb.set_optimization_level(OptimizationLevel::Default);
// 2 is equivalent to `-Oz`.
pmb.set_size_level(2);
// TODO: For some usecase, like embedded, it is useful to expose this and tune it.
// This really depends on if inlining causes enough simplifications to reduce code size.
pmb.set_inliner_with_threshold(50);
}
OptLevel::Optimize => {
@ -1134,9 +1138,10 @@ pub fn construct_optimization_passes<'a>(
}
}
// Add optimization passes for Size and Optimize.
if matches!(opt_level, OptLevel::Size | OptLevel::Optimize) {
// TODO figure out which of these actually help
// Add extra optimization passes for Optimize.
if matches!(opt_level, OptLevel::Optimize) {
// TODO: figure out which of these actually help.
// Note, llvm probably already runs all of these as part of Aggressive.
// function passes

View file

@ -2161,13 +2161,13 @@ impl<'a> LowLevelCall<'a> {
// Empty record is always equal to empty record.
// There are no runtime arguments to check, so just emit true or false.
LayoutRepr::Struct(field_layouts) if field_layouts.is_empty() => {
LayoutRepr::Struct([]) => {
backend.code_builder.i32_const(!invert_result as i32);
}
// Void is always equal to void. This is the type for the contents of the empty list in `[] == []`
// This instruction will never execute, but we need an i32 for module validation
LayoutRepr::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => {
LayoutRepr::Union(UnionLayout::NonRecursive([])) => {
backend.code_builder.i32_const(!invert_result as i32);
}

View file

@ -11,7 +11,7 @@ use roc_can::scope::Scope;
use roc_collections::all::{ImMap, MutMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_derive::SharedDerivedModule;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds};
use roc_parse::parser::{SourceError, SyntaxError};
use roc_problem::can::Problem;
use roc_region::all::Loc;
@ -154,10 +154,10 @@ pub fn can_expr_with<'a>(
let var = var_store.fresh();
let var_index = constraints.push_variable(var);
let expected = constraints.push_expected_type(Expected::NoExpectation(var_index));
let mut module_ids = ModuleIds::default();
let mut module_ids = PackageModuleIds::default();
// ensure the Test module is accessible in our tests
module_ids.get_or_insert(&"Test".into());
module_ids.get_or_insert(&PQModuleName::Unqualified("Test".into()));
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
@ -174,10 +174,22 @@ pub fn can_expr_with<'a>(
arena.alloc("TestPath"),
);
let mut scope = Scope::new(home, IdentIds::default(), Default::default());
let mut scope = Scope::new(
home,
"TestPath".into(),
IdentIds::default(),
Default::default(),
);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(arena, home, &dep_idents, &module_ids);
let mut env = Env::new(
arena,
home,
Path::new("Test.roc"),
&dep_idents,
&module_ids,
None,
);
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
@ -203,7 +215,7 @@ pub fn can_expr_with<'a>(
all_ident_ids.insert(home, scope.locals.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
module_ids: env.qualified_module_ids.clone().into_module_ids(),
all_ident_ids,
};

View file

@ -0,0 +1,8 @@
platform "test-platform"
requires {} { main : * }
exposes []
packages {}
provides [mainForHost]
mainForHost : {} -> {}
mainForHost = \{} -> {}

View file

@ -4561,7 +4561,7 @@ mod test_reporting {
test_report!(
comment_with_control_character,
"# comment with a \x07\n",
@r"
@r###"
ASCII CONTROL CHARACTER in tmp/comment_with_control_character/Test.roc
I encountered an ASCII control character:
@ -4570,7 +4570,7 @@ mod test_reporting {
^
ASCII control characters are not allowed.
"
"###
);
test_report!(
@ -4919,20 +4919,20 @@ mod test_reporting {
dict_type_formatting,
indoc!(
r#"
app "dict" imports [ Dict ] provides [main] to "./platform"
app "dict" imports [] provides [main] to "./platform"
myDict : Dict.Dict Num.I64 Str
myDict : Dict Num.I64 Str
myDict = Dict.insert (Dict.empty {}) "foo" 42
main = myDict
"#
),
@r#"
@r###"
TYPE MISMATCH in /code/proj/Main.roc
Something is off with the body of the `myDict` definition:
3 myDict : Dict.Dict Num.I64 Str
3 myDict : Dict Num.I64 Str
4 myDict = Dict.insert (Dict.empty {}) "foo" 42
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -4943,14 +4943,14 @@ mod test_reporting {
But the type annotation on `myDict` says it should be:
Dict I64 Str
"#
"###
);
test_report!(
alias_type_diff,
indoc!(
r#"
app "test" imports [Set.{ Set }] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
HSet a : Set a
@ -6109,9 +6109,7 @@ In roc, functions are always written as a lambda, like{}
report_header_problem_as(
indoc!(
r"
interface Foobar
exposes [main, @Foo]
imports [pf.Task, Base64]
module [main, @Foo]
"
),
indoc!(
@ -6120,39 +6118,12 @@ In roc, functions are always written as a lambda, like{}
I am partway through parsing an `exposes` list, but I got stuck here:
1 interface Foobar
2 exposes [main, @Foo]
^
1 module [main, @Foo]
^
I was expecting a type name, value name or function name next, like
exposes [Animal, default, tame]
"
),
)
}
#[test]
fn invalid_module_name() {
report_header_problem_as(
indoc!(
r"
interface foobar
exposes [main, @Foo]
imports [pf.Task, Base64]
"
),
indoc!(
r"
WEIRD MODULE NAME in /code/proj/Main.roc
I am partway through parsing a header, but got stuck here:
1 interface foobar
^
I am expecting a module name next, like BigNum or Main. Module names
must start with an uppercase letter.
[Animal, default, tame]
"
),
)
@ -9310,7 +9281,7 @@ In roc, functions are always written as a lambda, like{}
type_error_in_apply_is_circular,
indoc!(
r#"
app "test" imports [Set] provides [go] to "./platform"
app "test" imports [] provides [go] to "./platform"
S a : { set : Set.Set a }
@ -10898,7 +10869,9 @@ In roc, functions are always written as a lambda, like{}
function_cannot_derive_encoding,
indoc!(
r#"
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import Decode exposing [decoder]
main =
myDecoder : Decoder (a -> a) fmt where fmt implements DecoderFormatting
@ -10907,12 +10880,12 @@ In roc, functions are always written as a lambda, like{}
myDecoder
"#
),
@r"
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
5 myDecoder = decoder
7 myDecoder = decoder
^^^^^^^
I can't generate an implementation of the `Decoding` ability for
@ -10920,14 +10893,16 @@ In roc, functions are always written as a lambda, like{}
a -> a
Note: `Decoding` cannot be generated for functions.
"
"###
);
test_report!(
nested_opaque_cannot_derive_encoding,
indoc!(
r#"
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import Decode exposing [decoder]
A := {}
@ -10938,12 +10913,12 @@ In roc, functions are always written as a lambda, like{}
myDecoder
"#
),
@r"
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
7 myDecoder = decoder
9 myDecoder = decoder
^^^^^^^
I can't generate an implementation of the `Decoding` ability for
@ -10958,7 +10933,7 @@ In roc, functions are always written as a lambda, like{}
Tip: `A` does not implement `Decoding`. Consider adding a custom
implementation or `implements Decode.Decoding` to the definition of `A`.
"
"###
);
test_report!(
@ -11119,7 +11094,9 @@ In roc, functions are always written as a lambda, like{}
infer_decoded_record_error_with_function_field,
indoc!(
r#"
app "test" imports [TotallyNotJson] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import TotallyNotJson
main =
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes TotallyNotJson.json
@ -11128,12 +11105,12 @@ In roc, functions are always written as a lambda, like{}
_ -> "something went wrong"
"#
),
@r"
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
6 Ok rcd -> rcd.first rcd.second
8 Ok rcd -> rcd.first rcd.second
^^^^^^^^^
I can't generate an implementation of the `Decoding` ability for
@ -11141,14 +11118,16 @@ In roc, functions are always written as a lambda, like{}
* -> *
Note: `Decoding` cannot be generated for functions.
"
"###
);
test_report!(
record_with_optional_field_types_cannot_derive_decoding,
indoc!(
r#"
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import Decode exposing [decoder]
main =
myDecoder : Decoder {x : Str, y ? Str} fmt where fmt implements DecoderFormatting
@ -11157,12 +11136,12 @@ In roc, functions are always written as a lambda, like{}
myDecoder
"#
),
@r"
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
5 myDecoder = decoder
7 myDecoder = decoder
^^^^^^^
I can't generate an implementation of the `Decoding` ability for
@ -11177,7 +11156,7 @@ In roc, functions are always written as a lambda, like{}
over records that may or may not contain them at compile time, but are
not a concept that extends to runtime!
Maybe you wanted to use a `Result`?
"
"###
);
test_report!(
@ -11359,21 +11338,23 @@ In roc, functions are always written as a lambda, like{}
unused_value_import,
indoc!(
r#"
app "test" imports [List.{ concat }] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import List exposing [concat]
main = ""
"#
),
@r#"
@r###"
UNUSED IMPORT in /code/proj/Main.roc
`List.concat` is not used in this module.
List is imported but not used.
1 app "test" imports [List.{ concat }] provides [main] to "./platform"
^^^^^^
3 import List exposing [concat]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since `List.concat` isn't used, you don't need to import it.
"#
Since List isn't used, you don't need to import it.
"###
);
test_report!(
@ -11395,7 +11376,9 @@ In roc, functions are always written as a lambda, like{}
unnecessary_builtin_type_import,
indoc!(
r#"
app "test" imports [Decode.{ DecodeError }] provides [main, E] to "./platform"
app "test" imports [] provides [main, E] to "./platform"
import Decode exposing [DecodeError]
E : DecodeError
@ -13664,7 +13647,9 @@ In roc, functions are always written as a lambda, like{}
derive_decoding_for_tuple,
indoc!(
r#"
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import Decode exposing [decoder]
main =
myDecoder : Decoder (U32, Str) fmt where fmt implements DecoderFormatting
@ -13679,7 +13664,9 @@ In roc, functions are always written as a lambda, like{}
cannot_decode_tuple_with_non_decode_element,
indoc!(
r#"
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
import Decode exposing [decoder]
main =
myDecoder : Decoder (U32, {} -> {}) fmt where fmt implements DecoderFormatting
@ -13688,12 +13675,12 @@ In roc, functions are always written as a lambda, like{}
myDecoder
"#
),
@r"
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
5 myDecoder = decoder
7 myDecoder = decoder
^^^^^^^
I can't generate an implementation of the `Decoding` ability for
@ -13701,7 +13688,7 @@ In roc, functions are always written as a lambda, like{}
U32, {} -> {}
Note: `Decoding` cannot be generated for functions.
"
"###
);
test_no_problem!(

View file

@ -275,6 +275,12 @@ fn generate_entry_docs(
ValueDef::ExpectFx { .. } => {
// Don't generate docs for `expect-fx`s
}
ValueDef::ModuleImport { .. } => {
// Don't generate docs for module imports
}
ValueDef::IngestedFileImport { .. } => {
// Don't generate docs for ingested file imports
}
ValueDef::Stmt(loc_expr) => {
if let roc_parse::ast::Expr::Var {

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ use roc_module::symbol::{
};
use roc_mono::ir::{GlueLayouts, HostExposedLambdaSets, LambdaSetId, Proc, ProcLayout, ProcsBase};
use roc_mono::layout::{LayoutCache, STLayoutInterner};
use roc_parse::ast::{CommentOrNewline, Defs, TypeAnnotation, ValueDef};
use roc_parse::ast::{CommentOrNewline, Defs, TypeAnnotation};
use roc_parse::header::{HeaderType, PackageName};
use roc_region::all::{Loc, Region};
use roc_solve::module::Solved;
@ -96,26 +96,20 @@ pub(crate) struct ModuleHeader<'a> {
pub(crate) module_id: ModuleId,
pub(crate) module_path: PathBuf,
pub(crate) is_root_module: bool,
pub(crate) exposed_ident_ids: IdentIds,
pub(crate) deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
pub(crate) packages: MutMap<&'a str, PackageName<'a>>,
pub(crate) imported_modules: MutMap<ModuleId, Region>,
pub(crate) package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>,
pub(crate) exposes: Vec<Symbol>,
pub(crate) exposed_imports: MutMap<Ident, (Symbol, Region)>,
pub(crate) parse_state: roc_parse::state::State<'a>,
pub(crate) header_type: HeaderType<'a>,
pub(crate) header_comments: &'a [CommentOrNewline<'a>],
pub(crate) symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
pub(crate) header_imports: Option<roc_parse::header::ImportsKeywordItem<'a>>,
pub(crate) module_timing: ModuleTiming,
pub(crate) defined_values: Vec<ValueDef<'a>>,
pub(crate) opt_shorthand: Option<&'a str>,
}
#[derive(Debug)]
pub(crate) struct ConstrainedModule {
pub(crate) module: Module,
pub(crate) declarations: Declarations,
pub(crate) imported_modules: MutMap<ModuleId, Region>,
pub(crate) available_modules: MutMap<ModuleId, Region>,
pub(crate) constraints: Constraints,
pub(crate) constraint: ConstraintSoa,
pub(crate) ident_ids: IdentIds,
@ -203,13 +197,17 @@ pub struct ParsedModule<'a> {
pub src: &'a str,
pub module_timing: ModuleTiming,
pub deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
pub imported_modules: MutMap<ModuleId, Region>,
pub exposed_ident_ids: IdentIds,
pub exposed_imports: MutMap<Ident, (Symbol, Region)>,
pub parsed_defs: Defs<'a>,
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
pub header_type: HeaderType<'a>,
pub header_comments: &'a [CommentOrNewline<'a>],
pub available_modules: MutMap<ModuleId, Region>,
pub package_qualified_available_modules: MutSet<PackageQualified<'a, ModuleId>>,
pub packages: MutMap<&'a str, PackageName<'a>>,
pub initial_scope: MutMap<Ident, (Symbol, Region)>,
pub exposes: Vec<Symbol>,
pub opt_shorthand: Option<&'a str>,
}
#[derive(Debug)]

View file

@ -162,10 +162,6 @@ impl<'a> Dependencies<'a> {
output.insert((dep, Phase::LoadHeader));
}
// to parse and generate constraints, the headers of all dependencies must be loaded!
// otherwise, we don't know whether an imported symbol is actually exposed
self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader);
// to canonicalize a module, all its dependencies must be canonicalized
self.add_dependency(module_id, dep, Phase::CanonicalizeAndConstrain);
@ -427,10 +423,10 @@ impl<'a> Dependencies<'a> {
PrepareStartPhase::Recurse(new)
}
None => match phase {
Phase::LoadHeader => {
// this is fine, mark header loading as pending
Phase::LoadHeader | Phase::Parse => {
// this is fine, mark as pending
self.status
.insert(Job::Step(module_id, Phase::LoadHeader), Status::Pending);
.insert(Job::Step(module_id, phase), Status::Pending);
PrepareStartPhase::Continue
}

View file

@ -1,6 +1,8 @@
interface Dep1
exposes [three, str, Unit, Identity, one, two]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3Blah exposing [foo]
one = 1

View file

@ -1,10 +1,11 @@
interface Dep2
exposes [one, two, blah]
imports [Dep3.Blah.{ foo, bar }]
imports []
import Dep3Blah exposing [foo, bar]
one = 1
blah = foo
two = 2.0

View file

@ -1,6 +0,0 @@
interface Dep3.Other
exposes [foo, bar]
imports []
foo = "foo from Dep3.Other"
bar = "bar from Dep3.Other"

View file

@ -1,10 +1,12 @@
interface Dep3.Blah
interface Dep3Blah
exposes [one, two, foo, bar]
imports [Dep3.Other]
imports []
import Dep3Other
one = 1
two = 2
foo = "foo from Dep3"
bar = Dep3.Other.bar
bar = Dep3Other.bar

View file

@ -0,0 +1,6 @@
interface Dep3Other
exposes [foo, bar]
imports []
foo = "foo from Dep3Other"
bar = "bar from Dep3Other"

View file

@ -1,7 +1,8 @@
interface ImportAlias
exposes [unit]
imports [Dep1]
imports []
import Dep1
unit : Dep1.Unit
unit = Unit

View file

@ -1,5 +1,7 @@
interface OneDep
exposes [str]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3Blah exposing [foo]
str = foo

View file

@ -1,6 +1,11 @@
interface Primary
exposes [blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay]
imports [Dep1, Dep2.{ two }, Dep3.Blah.{ bar }, Res]
imports []
import Dep1
import Dep2
import Dep3Blah exposing [bar]
import Res
blah2 = Dep2.two
blah3 = bar

View file

@ -1,6 +1,9 @@
interface WithBuiltins
exposes [floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2]
imports [Dep1, Dep2.{ two }]
imports []
import Dep1
import Dep2 exposing [two]
floatTest = Num.maxF64

View file

@ -1,5 +0,0 @@
interface IngestedFile
exposes [str]
imports ["IngestedFile.roc" as foo : Str]
str = foo

View file

@ -1,6 +1,8 @@
interface Dep1
exposes [three, str, Unit, Identity, one, two]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3 exposing [foo]
one = 1

View file

@ -1,10 +1,11 @@
interface Dep2
exposes [one, two, blah]
imports [Dep3.Blah.{ foo, bar }]
imports []
import Dep3 exposing [foo, bar]
one = 1
blah = foo
two = 2.0

View file

@ -1,4 +1,4 @@
interface Dep3.Blah
interface Dep3
exposes [one, two, foo, bar]
imports []

View file

@ -0,0 +1,8 @@
interface ExposedUsedOutsideScope exposes [good, bad] imports []
good =
import Dep2 exposing [two]
two
bad =
two

View file

@ -1,7 +1,8 @@
interface ImportAlias
exposes [unit]
imports [Dep1]
imports []
import Dep1
unit : Dep1.Unit
unit = Unit

View file

@ -0,0 +1,12 @@
interface ImportInsideDef exposes [dep1Str, dep2TwoDobuled] imports []
dep1Str =
import Dep1
Dep1.str
dep2TwoDobuled =
2
* (
import Dep2 exposing [two]
two
)

View file

@ -0,0 +1,8 @@
interface ImportUsedOutsideScope exposes [good, bad] imports []
good =
import Dep2
Dep2.two
bad =
Dep2.two

View file

@ -0,0 +1,11 @@
interface IngestedFile
exposes [str, nested]
imports []
import "IngestedFile.roc" as foo : Str
str = foo
nested =
import "Dep1.roc" as dep1 : Str
dep1

View file

@ -1,5 +1,7 @@
interface IngestedFileBytes
exposes [str]
imports ["IngestedFileBytes.roc" as foo : List U8]
imports []
import "IngestedFileBytes.roc" as foo : List U8
str = Str.fromUtf8 foo |> Result.withDefault ""

View file

@ -1,5 +1,7 @@
interface OneDep
exposes [str]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3 exposing [foo]
str = foo

View file

@ -1,6 +1,11 @@
interface Primary
exposes [blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay]
imports [Dep1, Dep2.{ two }, Dep3.Blah.{ bar }, Res]
imports []
import Dep1
import Dep2
import Dep3 exposing [bar]
import Res
blah2 = Dep2.two
blah3 = bar

View file

@ -1,6 +1,9 @@
interface WithBuiltins
exposes [floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2]
imports [Dep1, Dep2.{ two }]
imports []
import Dep1
import Dep2
floatTest = Num.maxF64

View file

@ -1,6 +1,8 @@
interface MissingDep
exposes [unit]
imports [ThisFileIsMissing]
imports []
import ThisFileIsMissing
Unit : [Unit]

View file

@ -1,6 +1,8 @@
interface MissingIngestedFile
exposes [unit]
imports ["ThisFileIsMissing" as data: List U8]
imports []
import "ThisFileIsMissing" as data : List U8
Unit : [Unit]

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
use crate::ident::{Ident, ModuleName};
use crate::ident::{Ident, Lowercase, ModuleName};
use crate::module_err::{IdentIdNotFoundSnafu, ModuleIdNotFoundSnafu, ModuleResult};
use roc_collections::{SmallStringInterner, VecMap};
use roc_error_macros::internal_error;
@ -115,6 +115,15 @@ impl Symbol {
)
}
pub fn is_automatically_imported(self) -> bool {
let module_id = self.module_id();
module_id.is_automatically_imported()
&& Self::builtin_types_in_scope(module_id)
.iter()
.any(|(_, (s, _))| *s == self)
}
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName {
interns
.module_ids
@ -384,6 +393,11 @@ impl ModuleId {
.get_name(self)
.unwrap_or_else(|| internal_error!("Could not find ModuleIds for {:?}", self))
}
pub fn is_automatically_imported(self) -> bool {
// The deprecated TotallyNotJson module is not automatically imported.
self.is_builtin() && self != ModuleId::JSON
}
}
impl fmt::Debug for ModuleId {
@ -448,6 +462,15 @@ impl<'a, T> PackageQualified<'a, T> {
PackageQualified::Qualified(_, name) => name,
}
}
pub fn map_module<B>(&self, f: impl FnOnce(&T) -> B) -> PackageQualified<'a, B> {
match self {
PackageQualified::Unqualified(name) => PackageQualified::Unqualified(f(name)),
PackageQualified::Qualified(package, name) => {
PackageQualified::Qualified(package, f(name))
}
}
}
}
#[derive(Debug, Clone)]
@ -593,6 +616,68 @@ impl ModuleIds {
}
}
#[derive(Debug, Clone)]
pub struct ScopeModules {
modules: VecMap<ModuleName, ModuleId>,
sources: VecMap<ModuleId, ScopeModuleSource>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeModuleSource {
Builtin,
Current,
Import(Region),
}
impl ScopeModules {
pub fn get_id(&self, module_name: &ModuleName) -> Option<ModuleId> {
self.modules.get(module_name).copied()
}
pub fn has_id(&self, module_id: ModuleId) -> bool {
self.sources.contains_key(&module_id)
}
pub fn available_names(&self) -> impl Iterator<Item = &ModuleName> {
self.modules.keys()
}
pub fn insert(
&mut self,
module_name: ModuleName,
module_id: ModuleId,
region: Region,
) -> Result<(), ScopeModuleSource> {
if let Some(existing_module_id) = self.modules.get(&module_name) {
if *existing_module_id == module_id {
return Ok(());
}
return Err(*self.sources.get(existing_module_id).unwrap());
}
self.modules.insert(module_name, module_id);
self.sources
.insert(module_id, ScopeModuleSource::Import(region));
Ok(())
}
pub fn len(&self) -> usize {
debug_assert_eq!(self.modules.len(), self.sources.len());
self.modules.len()
}
pub fn is_empty(&self) -> bool {
debug_assert_eq!(self.modules.is_empty(), self.sources.is_empty());
self.modules.is_empty()
}
pub fn truncate(&mut self, len: usize) {
self.modules.truncate(len);
self.sources.truncate(len);
}
}
/// An ID that is assigned to interned string identifiers within a module.
/// By turning these strings into numbers, post-canonicalization processes
/// like unification and optimization can run a lot faster.
@ -697,6 +782,13 @@ impl IdentIds {
pub fn is_empty(&self) -> bool {
self.interner.is_empty()
}
pub fn exposed_values(&self) -> Vec<Lowercase> {
self.ident_strs()
.filter(|(_, ident)| ident.starts_with(|c: char| c.is_lowercase()))
.map(|(_, ident)| Lowercase::from(ident))
.collect()
}
}
#[derive(Debug, Default, Clone)]
@ -926,6 +1018,32 @@ macro_rules! define_builtins {
}
}
impl ScopeModules {
pub fn new(home_id: ModuleId, home_name: ModuleName) -> Self {
// +1 because the user will be compiling at least 1 non-builtin module!
let capacity = $total + 1;
let mut modules = VecMap::with_capacity(capacity);
let mut sources = VecMap::with_capacity(capacity);
modules.insert(home_name, home_id);
sources.insert(home_id, ScopeModuleSource::Current);
let mut insert_both = |id: ModuleId, name_str: &'static str| {
let name: ModuleName = name_str.into();
modules.insert(name, id);
sources.insert(id, ScopeModuleSource::Builtin);
};
$(
insert_both(ModuleId::$module_const, $module_name);
)+
ScopeModules { modules, sources }
}
}
impl<'a> Default for PackageModuleIds<'a> {
fn default() -> Self {
// +1 because the user will be compiling at least 1 non-builtin module!

View file

@ -452,7 +452,7 @@ enum Match {
}
fn check_for_match(branches: &[Branch]) -> Match {
match branches.get(0) {
match branches.first() {
Some(Branch {
goal,
patterns,

View file

@ -1,9 +1,4 @@
use std::{
cell::RefCell,
hash::{BuildHasher, Hasher},
marker::PhantomData,
sync::Arc,
};
use std::{cell::RefCell, hash::BuildHasher, marker::PhantomData, sync::Arc};
use bumpalo::Bump;
use parking_lot::{Mutex, RwLock};
@ -591,9 +586,8 @@ struct LockedGlobalInterner<'a, 'r> {
///
/// This uses the [default_hasher], so interner maps should also rely on [default_hasher].
fn hash<V: std::hash::Hash>(val: V) -> u64 {
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
val.hash(&mut state);
state.finish()
let hasher = roc_collections::all::BuildHasher::default();
hasher.hash_one(&val)
}
#[inline(always)]

View file

@ -1,6 +1,10 @@
use bumpalo::Bump;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use roc_parse::{module, module::module_defs, parser::Parser, state::State};
use roc_parse::{
ast::Defs,
module::{self, parse_module_defs},
state::State,
};
use std::path::PathBuf;
pub fn parse_benchmark(c: &mut Criterion) {
@ -18,11 +22,7 @@ pub fn parse_benchmark(c: &mut Criterion) {
let (_actual, state) =
module::parse_header(&arena, State::new(src.as_bytes())).unwrap();
let min_indent = 0;
let res = module_defs()
.parse(&arena, state, min_indent)
.map(|tuple| tuple.1)
.unwrap();
let res = parse_module_defs(&arena, state, Defs::default()).unwrap();
black_box(res.len());
})
@ -43,11 +43,7 @@ pub fn parse_benchmark(c: &mut Criterion) {
let (_actual, state) =
module::parse_header(&arena, State::new(src.as_bytes())).unwrap();
let min_indent = 0;
let res = module_defs()
.parse(&arena, state, min_indent)
.map(|tuple| tuple.1)
.unwrap();
let res = parse_module_defs(&arena, state, Defs::default()).unwrap();
black_box(res.len());
})

View file

@ -1,7 +1,8 @@
use std::fmt::Debug;
use std::path::Path;
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader};
use crate::header::{
self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader,
};
use crate::ident::Accessor;
use crate::parser::ESingleQuote;
use bumpalo::collections::{String, Vec};
@ -9,9 +10,10 @@ use bumpalo::Bump;
use roc_collections::soa::{EitherIndex, Index, Slice};
use roc_error_macros::internal_error;
use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_module::ident::QualifiedModuleName;
use roc_region::all::{Loc, Position, Region};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Spaces<'a, T> {
pub before: &'a [CommentOrNewline<'a>],
pub item: T,
@ -46,6 +48,18 @@ impl<'a, T> Spaced<'a, T> {
Spaced::SpaceBefore(next, _spaces) | Spaced::SpaceAfter(next, _spaces) => next.item(),
}
}
pub fn map<U, F: Fn(&T) -> U>(&self, arena: &'a Bump, f: F) -> Spaced<'a, U> {
match self {
Spaced::Item(item) => Spaced::Item(f(item)),
Spaced::SpaceBefore(next, spaces) => {
Spaced::SpaceBefore(arena.alloc(next.map(arena, f)), spaces)
}
Spaced::SpaceAfter(next, spaces) => {
Spaced::SpaceAfter(arena.alloc(next.map(arena, f)), spaces)
}
}
}
}
impl<'a, T: Debug> Debug for Spaced<'a, T> {
@ -96,9 +110,143 @@ pub struct Module<'a> {
pub header: Header<'a>,
}
impl<'a> Module<'a> {
pub fn upgrade_header_imports(self, arena: &'a Bump) -> (Self, Defs<'a>) {
let (header, defs) = match self.header {
Header::Module(header) => (
Header::Module(ModuleHeader {
interface_imports: None,
..header
}),
Self::header_imports_to_defs(arena, header.interface_imports),
),
Header::App(header) => (
Header::App(AppHeader {
old_imports: None,
..header
}),
Self::header_imports_to_defs(arena, header.old_imports),
),
Header::Package(_) | Header::Platform(_) | Header::Hosted(_) => {
(self.header, Defs::default())
}
};
(Module { header, ..self }, defs)
}
pub fn header_imports_to_defs(
arena: &'a Bump,
imports: Option<
header::KeywordItem<'a, header::ImportsKeyword, header::ImportsCollection<'a>>,
>,
) -> Defs<'a> {
let mut defs = Defs::default();
if let Some(imports) = imports {
let len = imports.item.len();
for (index, import) in imports.item.iter().enumerate() {
let spaced = import.extract_spaces();
let value_def = match spaced.item {
header::ImportsEntry::Package(pkg_name, name, exposed) => {
Self::header_import_to_value_def(
Some(pkg_name),
name,
exposed,
import.region,
)
}
header::ImportsEntry::Module(name, exposed) => {
Self::header_import_to_value_def(None, name, exposed, import.region)
}
header::ImportsEntry::IngestedFile(path, typed_ident) => {
ValueDef::IngestedFileImport(IngestedFileImport {
before_path: &[],
path: Loc {
value: path,
region: import.region,
},
name: header::KeywordItem {
keyword: Spaces {
before: &[],
item: ImportAsKeyword,
after: &[],
},
item: Loc {
value: typed_ident,
region: import.region,
},
},
})
}
};
defs.push_value_def(
value_def,
import.region,
if index == 0 {
let mut before = vec![CommentOrNewline::Newline, CommentOrNewline::Newline];
before.extend(spaced.before);
arena.alloc(before)
} else {
spaced.before
},
if index == len - 1 {
let mut after = spaced.after.to_vec();
after.extend_from_slice(imports.item.final_comments());
after.push(CommentOrNewline::Newline);
after.push(CommentOrNewline::Newline);
arena.alloc(after)
} else {
spaced.after
},
);
}
}
defs
}
fn header_import_to_value_def(
pkg_name: Option<&'a str>,
name: header::ModuleName<'a>,
exposed: Collection<'a, Loc<Spaced<'a, header::ExposedName<'a>>>>,
region: Region,
) -> ValueDef<'a> {
use crate::header::KeywordItem;
let new_exposed = if exposed.is_empty() {
None
} else {
Some(KeywordItem {
keyword: Spaces {
before: &[],
item: ImportExposingKeyword,
after: &[],
},
item: exposed,
})
};
ValueDef::ModuleImport(ModuleImport {
before_name: &[],
name: Loc {
region,
value: ImportedModuleName {
package: pkg_name,
name,
},
},
alias: None,
exposed: new_exposed,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Header<'a> {
Interface(InterfaceHeader<'a>),
Module(ModuleHeader<'a>),
App(AppHeader<'a>),
Package(PackageHeader<'a>),
Platform(PlatformHeader<'a>),
@ -283,9 +431,6 @@ pub enum Expr<'a> {
// Record Builders
RecordBuilder(Collection<'a, Loc<RecordBuilderField<'a>>>),
// The name of a file to be ingested directly into a variable.
IngestedFile(&'a Path, &'a Loc<TypeAnnotation<'a>>),
// Lookups
Var {
module_name: &'a str, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar`
@ -456,7 +601,6 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::RecordBuilder(items) => items
.iter()
.any(|rbf| is_record_builder_field_suffixed(&rbf.value)),
Expr::IngestedFile(_, _) => false,
Expr::Underscore(_) => false,
Expr::Crash => false,
Expr::Tag(_) => false,
@ -622,10 +766,53 @@ pub enum ValueDef<'a> {
preceding_comment: Region,
},
/// e.g. `import InternalHttp as Http exposing [Req]`.
ModuleImport(ModuleImport<'a>),
/// e.g. `import "path/to/my/file.txt" as myFile : Str`
IngestedFileImport(IngestedFileImport<'a>),
Stmt(&'a Loc<Expr<'a>>),
}
impl<'a> ValueDef<'a> {
pub fn expr(&self) -> Option<&'a Expr<'a>> {
match self {
ValueDef::Body(_, body) => Some(&body.value),
ValueDef::AnnotatedBody {
ann_pattern: _,
ann_type: _,
comment: _,
body_pattern: _,
body_expr,
} => Some(&body_expr.value),
ValueDef::Dbg {
condition,
preceding_comment: _,
}
| ValueDef::Expect {
condition,
preceding_comment: _,
}
| ValueDef::ExpectFx {
condition,
preceding_comment: _,
} => Some(&condition.value),
ValueDef::Annotation(_, _)
| ValueDef::ModuleImport(ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
})
| ValueDef::IngestedFileImport(_) => None,
ValueDef::Stmt(loc_expr) => Some(&loc_expr.value),
}
}
pub fn replace_expr(&mut self, new_expr: &'a Loc<Expr<'a>>) {
match self {
ValueDef::Body(_, expr) => *expr = new_expr,
@ -635,6 +822,281 @@ impl<'a> ValueDef<'a> {
}
}
pub struct RecursiveValueDefIter<'a, 'b> {
current: &'b Defs<'a>,
index: usize,
pending: std::vec::Vec<&'b Defs<'a>>,
}
impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
pub fn new(defs: &'b Defs<'a>) -> Self {
Self {
current: defs,
index: 0,
pending: vec![],
}
}
fn push_pending_from_expr(&mut self, expr: &'b Expr<'a>) {
let mut expr_stack = vec![expr];
use Expr::*;
macro_rules! push_stack_from_record_fields {
($fields:expr) => {
for field in $fields.items {
let mut current = field.value;
loop {
use AssignedField::*;
match current {
RequiredValue(_, _, loc_val) => break expr_stack.push(&loc_val.value),
OptionalValue(_, _, loc_val) => break expr_stack.push(&loc_val.value),
SpaceBefore(next, _) => current = *next,
SpaceAfter(next, _) => current = *next,
LabelOnly(_) | Malformed(_) => break,
}
}
}
};
}
while let Some(next) = expr_stack.pop() {
match next {
Defs(defs, cont) => {
self.pending.push(defs);
// We purposefully don't push the exprs inside defs here
// because they will be traversed when the iterator
// gets to their parent def.
expr_stack.push(&cont.value);
}
List(list) => {
expr_stack.reserve(list.len());
for loc_expr in list.items {
expr_stack.push(&loc_expr.value);
}
}
RecordUpdate { update, fields } => {
expr_stack.reserve(fields.len() + 1);
expr_stack.push(&update.value);
push_stack_from_record_fields!(fields);
}
Record(fields) => {
expr_stack.reserve(fields.len());
push_stack_from_record_fields!(fields);
}
Tuple(fields) => {
expr_stack.reserve(fields.len());
for loc_expr in fields.items {
expr_stack.push(&loc_expr.value);
}
}
RecordBuilder(fields) => {
expr_stack.reserve(fields.len());
for loc_record_builder_field in fields.items {
let mut current_field = loc_record_builder_field.value;
loop {
use RecordBuilderField::*;
match current_field {
Value(_, _, loc_val) => break expr_stack.push(&loc_val.value),
ApplyValue(_, _, _, loc_val) => {
break expr_stack.push(&loc_val.value)
}
SpaceBefore(next_field, _) => current_field = *next_field,
SpaceAfter(next_field, _) => current_field = *next_field,
LabelOnly(_) | Malformed(_) => break,
}
}
}
}
Closure(_, body) => expr_stack.push(&body.value),
Backpassing(_, a, b) => {
expr_stack.reserve(2);
expr_stack.push(&a.value);
expr_stack.push(&b.value);
}
Expect(condition, cont)
| Dbg(condition, cont)
| LowLevelDbg(_, condition, cont) => {
expr_stack.reserve(2);
expr_stack.push(&condition.value);
expr_stack.push(&cont.value);
}
Apply(fun, args, _) => {
expr_stack.reserve(args.len() + 1);
expr_stack.push(&fun.value);
for loc_expr in args.iter() {
expr_stack.push(&loc_expr.value);
}
}
BinOps(ops, expr) => {
expr_stack.reserve(ops.len() + 1);
for (a, _) in ops.iter() {
expr_stack.push(&a.value);
}
expr_stack.push(&expr.value);
}
UnaryOp(expr, _) => expr_stack.push(&expr.value),
If(ifs, alternate) => {
expr_stack.reserve(ifs.len() * 2 + 1);
for (condition, consequent) in ifs.iter() {
expr_stack.push(&condition.value);
expr_stack.push(&consequent.value);
}
expr_stack.push(&alternate.value);
}
When(condition, branches) => {
expr_stack.reserve(branches.len() + 1);
expr_stack.push(&condition.value);
for WhenBranch {
patterns: _,
value,
guard,
} in branches.iter()
{
expr_stack.push(&value.value);
match guard {
None => {}
Some(guard) => expr_stack.push(&guard.value),
}
}
}
RecordAccess(expr, _)
| TupleAccess(expr, _)
| TaskAwaitBang(expr)
| SpaceBefore(expr, _)
| SpaceAfter(expr, _)
| ParensAround(expr) => expr_stack.push(expr),
MultipleRecordBuilders(loc_expr) | UnappliedRecordBuilder(loc_expr) => {
expr_stack.push(&loc_expr.value)
}
Float(_)
| Num(_)
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| AccessorFunction(_)
| Var { .. }
| Underscore(_)
| Crash
| Tag(_)
| OpaqueRef(_)
| MalformedIdent(_, _)
| MalformedClosure
| PrecedenceConflict(_)
| MalformedSuffixed(_)
| EmptyDefsFinal => { /* terminal */ }
}
}
}
}
impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
type Item = (&'b ValueDef<'a>, &'b Region);
fn next(&mut self) -> Option<Self::Item> {
match self.current.tags.get(self.index) {
Some(tag) => {
if let Err(def_index) = tag.split() {
let def = &self.current.value_defs[def_index.index()];
let region = &self.current.regions[self.index];
if let Some(expr) = def.expr() {
self.push_pending_from_expr(expr);
}
self.index += 1;
Some((def, region))
} else {
// Not a value def, try next
self.index += 1;
self.next()
}
}
None => {
self.current = self.pending.pop()?;
self.index = 0;
self.next()
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModuleImport<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<ImportedModuleName<'a>>,
pub alias: Option<header::KeywordItem<'a, ImportAsKeyword, Loc<ImportAlias<'a>>>>,
pub exposed: Option<
header::KeywordItem<
'a,
ImportExposingKeyword,
Collection<'a, Loc<Spaced<'a, header::ExposedName<'a>>>>,
>,
>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IngestedFileImport<'a> {
pub before_path: &'a [CommentOrNewline<'a>],
pub path: Loc<StrLiteral<'a>>,
pub name: header::KeywordItem<'a, ImportAsKeyword, Loc<Spaced<'a, header::TypedIdent<'a>>>>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ImportAsKeyword;
impl header::Keyword for ImportAsKeyword {
const KEYWORD: &'static str = "as";
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ImportExposingKeyword;
impl header::Keyword for ImportExposingKeyword {
const KEYWORD: &'static str = "exposing";
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImportedModuleName<'a> {
pub package: Option<&'a str>,
pub name: ModuleName<'a>,
}
impl<'a> From<ImportedModuleName<'a>> for QualifiedModuleName<'a> {
fn from(imported: ImportedModuleName<'a>) -> Self {
Self {
opt_package: imported.package,
module: imported.name.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImportAlias<'a>(&'a str);
impl<'a> ImportAlias<'a> {
pub const fn new(name: &'a str) -> Self {
ImportAlias(name)
}
pub const fn as_str(&'a self) -> &'a str {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Defs<'a> {
pub tags: std::vec::Vec<EitherIndex<TypeDef<'a>, ValueDef<'a>>>,
@ -1847,7 +2309,7 @@ impl<'a> Malformed for Module<'a> {
impl<'a> Malformed for Header<'a> {
fn is_malformed(&self) -> bool {
match self {
Header::Interface(header) => header.is_malformed(),
Header::Module(header) => header.is_malformed(),
Header::App(header) => header.is_malformed(),
Header::Package(header) => header.is_malformed(),
Header::Platform(header) => header.is_malformed(),
@ -1876,7 +2338,6 @@ impl<'a> Malformed for Expr<'a> {
Tag(_) |
OpaqueRef(_) |
SingleQuote(_) | // This is just a &str - not a bunch of segments
IngestedFile(_, _) |
EmptyDefsFinal |
Crash => false,
@ -2150,6 +2611,17 @@ impl<'a> Malformed for ValueDef<'a> {
condition,
preceding_comment: _,
} => condition.is_malformed(),
ValueDef::ModuleImport(ModuleImport {
before_name: _,
name: _,
alias: _,
exposed: _,
}) => false,
ValueDef::IngestedFileImport(IngestedFileImport {
before_path: _,
path,
name: _,
}) => path.is_malformed(),
ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(),
}
}

View file

@ -322,43 +322,6 @@ pub fn fast_eat_until_control_character(bytes: &[u8]) -> usize {
simple_eat_until_control_character(&bytes[i..]) + i
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_eat_whitespace_simple() {
let bytes = &[0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(simple_eat_whitespace(bytes), fast_eat_whitespace(bytes));
}
proptest! {
#[test]
fn test_eat_whitespace(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(simple_eat_whitespace(&bytes), fast_eat_whitespace(&bytes));
}
}
#[test]
fn test_eat_until_control_character_simple() {
let bytes = &[32, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
simple_eat_until_control_character(bytes),
fast_eat_until_control_character(bytes)
);
}
proptest! {
#[test]
fn test_eat_until_control_character(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(
simple_eat_until_control_character(&bytes),
fast_eat_until_control_character(&bytes));
}
}
}
pub fn space0_e<'a, E>(
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
@ -514,3 +477,40 @@ where
Ok((progress, state))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_eat_whitespace_simple() {
let bytes = &[0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(simple_eat_whitespace(bytes), fast_eat_whitespace(bytes));
}
proptest! {
#[test]
fn test_eat_whitespace(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(simple_eat_whitespace(&bytes), fast_eat_whitespace(&bytes));
}
}
#[test]
fn test_eat_until_control_character_simple() {
let bytes = &[32, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
simple_eat_until_control_character(bytes),
fast_eat_until_control_character(bytes)
);
}
proptest! {
#[test]
fn test_eat_until_control_character(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(
simple_eat_until_control_character(&bytes),
fast_eat_until_control_character(&bytes));
}
}
}

View file

@ -1,24 +1,29 @@
use crate::ast::{
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, Pattern, RecordBuilderField, Spaceable, Spaces,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileImport, ModuleImport, Pattern, RecordBuilderField, Spaceable,
Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
space0_before_optional_after, space0_e, spaces, spaces_around, spaces_before,
};
use crate::ident::{integer_ident, lowercase_ident, parse_ident, Accessor, Ident, Suffix};
use crate::keyword;
use crate::ident::{
integer_ident, lowercase_ident, parse_ident, unqualified_ident, uppercase_ident, Accessor,
Ident, Suffix,
};
use crate::module::module_name_help;
use crate::parser::{
self, backtrackable, byte, byte_indent, increment_min_indent, line_min_indent, optional,
reset_min_indent, sep_by1, sep_by1_e, set_min_indent, specialize_err, specialize_err_ref, then,
two_bytes, EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord,
EString, EType, EWhen, Either, ParseResult, Parser,
two_bytes, EClosure, EExpect, EExpr, EIf, EImport, EInParens, EList, ENumber, EPattern,
ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
};
use crate::pattern::{closure_param, loc_implements_parser};
use crate::state::State;
use crate::string_literal::StrLikeLiteral;
use crate::type_annotation;
use crate::string_literal::{self, StrLikeLiteral};
use crate::{header, keyword};
use crate::{module, type_annotation};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::soa::Slice;
@ -286,17 +291,19 @@ fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
fn loc_possibly_negative_or_negated_term<'a>(
options: ExprParseOptions,
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
let parse_unary_negate = move |arena, state: State<'a>, min_indent: u32| {
let initial = state.clone();
let (_, (loc_op, loc_expr), state) =
and!(loc!(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
Ok((MadeProgress, loc_expr, state))
};
one_of![
|arena, state: State<'a>, min_indent: u32| {
let initial = state.clone();
let (_, (loc_op, loc_expr), state) =
and!(loc!(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
Ok((MadeProgress, loc_expr, state))
},
parse_unary_negate,
// this will parse negative numbers, which the unary negate thing up top doesn't (for now)
loc!(specialize_err(EExpr::Number, number_literal_help())),
loc!(map_with_arena!(
@ -345,6 +352,7 @@ fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, E
loc!(specialize_err(EExpr::When, when::expr_help(options))),
loc!(specialize_err(EExpr::Expect, expect_help(options))),
loc!(specialize_err(EExpr::Dbg, dbg_help(options))),
loc!(import_help(options)),
loc!(specialize_err(EExpr::Closure, closure_help(options))),
loc!(expr_operator_chain(options)),
fail_expr_start_e()
@ -648,30 +656,43 @@ pub fn parse_single_def<'a>(
min_indent,
) {
Err((NoProgress, _)) => {
match parse_expect.parse(arena, state.clone(), min_indent) {
match loc!(import()).parse(arena, state.clone(), min_indent) {
Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, None, initial))
match parse_expect.parse(arena, state.clone(), min_indent) {
Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, None, initial))
}
Ok((_, expect_flavor, state)) => parse_statement_inside_def(
arena,
state,
min_indent,
options,
start,
spaces_before_current_start,
spaces_before_current,
|preceding_comment, loc_def_expr| match expect_flavor {
Either::Second(_) => ValueDef::Expect {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
Either::First(_) => ValueDef::ExpectFx {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
},
),
}
}
Ok((_, expect_flavor, state)) => parse_statement_inside_def(
arena,
Ok((_, loc_import, state)) => Ok((
MadeProgress,
Some(SingleDef {
type_or_value: Either::Second(loc_import.value),
region: loc_import.region,
spaces_before: spaces_before_current,
}),
state,
min_indent,
options,
start,
spaces_before_current_start,
spaces_before_current,
|preceding_comment, loc_def_expr| match expect_flavor {
Either::Second(_) => ValueDef::Expect {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
Either::First(_) => ValueDef::ExpectFx {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
},
),
)),
}
}
Err((MadeProgress, _)) => {
@ -936,6 +957,121 @@ pub fn parse_single_def<'a>(
}
}
fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
skip_first!(
parser::keyword(keyword::IMPORT, EImport::Import),
increment_min_indent(one_of!(import_body(), import_ingested_file_body()))
)
}
fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
map!(
record!(ModuleImport {
before_name: space0_e(EImport::IndentStart),
name: loc!(imported_module_name()),
alias: optional(backtrackable(import_as())),
exposed: optional(backtrackable(import_exposing()))
}),
ValueDef::ModuleImport
)
}
#[inline(always)]
fn imported_module_name<'a>() -> impl Parser<'a, ImportedModuleName<'a>, EImport<'a>> {
record!(ImportedModuleName {
package: optional(skip_second!(
specialize_err(|_, pos| EImport::PackageShorthand(pos), lowercase_ident()),
byte(b'.', EImport::PackageShorthandDot)
)),
name: module_name_help(EImport::ModuleName)
})
}
#[inline(always)]
fn import_as<'a>(
) -> impl Parser<'a, header::KeywordItem<'a, ImportAsKeyword, Loc<ImportAlias<'a>>>, EImport<'a>> {
record!(header::KeywordItem {
keyword: module::spaces_around_keyword(
ImportAsKeyword,
EImport::As,
EImport::IndentAs,
EImport::IndentAlias
),
item: loc!(map!(
specialize_err(|_, pos| EImport::Alias(pos), uppercase_ident()),
ImportAlias::new
))
})
}
#[inline(always)]
fn import_exposing<'a>() -> impl Parser<
'a,
header::KeywordItem<
'a,
ImportExposingKeyword,
Collection<'a, Loc<Spaced<'a, header::ExposedName<'a>>>>,
>,
EImport<'a>,
> {
record!(header::KeywordItem {
keyword: module::spaces_around_keyword(
ImportExposingKeyword,
EImport::Exposing,
EImport::IndentExposing,
EImport::ExposingListStart,
),
item: collection_trailing_sep_e!(
byte(b'[', EImport::ExposingListStart),
loc!(import_exposed_name()),
byte(b',', EImport::ExposingListEnd),
byte(b']', EImport::ExposingListEnd),
Spaced::SpaceBefore
)
})
}
#[inline(always)]
fn import_exposed_name<'a>(
) -> impl Parser<'a, crate::ast::Spaced<'a, crate::header::ExposedName<'a>>, EImport<'a>> {
map!(
specialize_err(|_, pos| EImport::ExposedName(pos), unqualified_ident()),
|n| Spaced::Item(crate::header::ExposedName::new(n))
)
}
#[inline(always)]
fn import_ingested_file_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
map!(
record!(IngestedFileImport {
before_path: space0_e(EImport::IndentStart),
path: loc!(specialize_err(
|_, pos| EImport::IngestedPath(pos),
string_literal::parse_str_literal()
)),
name: import_ingested_file_as(),
}),
ValueDef::IngestedFileImport
)
}
#[inline(always)]
fn import_ingested_file_as<'a>() -> impl Parser<
'a,
header::KeywordItem<'a, ImportAsKeyword, Loc<Spaced<'a, header::TypedIdent<'a>>>>,
EImport<'a>,
> {
record!(header::KeywordItem {
keyword: module::spaces_around_keyword(
ImportAsKeyword,
EImport::As,
EImport::IndentAs,
EImport::IndentIngestedName
),
item: specialize_err(EImport::IngestedName, loc!(module::typed_ident()))
})
}
pub fn parse_single_def_assignment<'a>(
options: ExprParseOptions,
min_indent: u32,
@ -2243,8 +2379,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
is_negative,
},
// These would not have parsed as patterns
Expr::IngestedFile(_, _)
| Expr::AccessorFunction(_)
Expr::AccessorFunction(_)
| Expr::RecordAccess(_, _)
| Expr::TupleAccess(_, _)
| Expr::List { .. }
@ -2332,41 +2467,42 @@ fn assigned_expr_field_to_pattern_help<'a>(
})
}
pub fn toplevel_defs<'a>() -> impl Parser<'a, Defs<'a>, EExpr<'a>> {
move |arena, state: State<'a>, min_indent: u32| {
let (_, initial_space, state) =
space0_e(EExpr::IndentEnd).parse(arena, state, min_indent)?;
pub fn parse_top_level_defs<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
mut output: Defs<'a>,
) -> ParseResult<'a, Defs<'a>, EExpr<'a>> {
let (_, initial_space, state) = space0_e(EExpr::IndentEnd).parse(arena, state, 0)?;
let start_column = state.column();
let start_column = state.column();
let options = ExprParseOptions {
accept_multi_backpassing: true,
check_for_arrow: true,
suffixed_found: false,
};
let options = ExprParseOptions {
accept_multi_backpassing: true,
check_for_arrow: true,
suffixed_found: false,
};
let mut output = Defs::default();
let before = Slice::extend_new(&mut output.spaces, initial_space.iter().copied());
let existing_len = output.tags.len();
let (_, mut output, state) = parse_defs_end(options, start_column, output, arena, state)?;
let before = Slice::extend_new(&mut output.spaces, initial_space.iter().copied());
let (_, final_space, state) =
space0_e(EExpr::IndentEnd).parse(arena, state, start_column)?;
let (_, mut output, state) = parse_defs_end(options, start_column, output, arena, state)?;
if !output.tags.is_empty() {
// add surrounding whitespace
let after = Slice::extend_new(&mut output.spaces, final_space.iter().copied());
let (_, final_space, state) = space0_e(EExpr::IndentEnd).parse(arena, state, start_column)?;
debug_assert!(output.space_before[0].is_empty());
output.space_before[0] = before;
if output.tags.len() > existing_len {
// add surrounding whitespace
let after = Slice::extend_new(&mut output.spaces, final_space.iter().copied());
let last = output.tags.len() - 1;
debug_assert!(output.space_after[last].is_empty() || after.is_empty());
output.space_after[last] = after;
}
debug_assert!(output.space_before[existing_len].is_empty());
output.space_before[existing_len] = before;
Ok((MadeProgress, output, state))
let last = output.tags.len() - 1;
debug_assert!(output.space_after[last].is_empty() || after.is_empty());
output.space_after[last] = after;
}
Ok((MadeProgress, output, state))
}
// PARSER HELPERS
@ -2375,7 +2511,7 @@ fn closure_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EClo
// closure_help_help(options)
map_with_arena!(
// After the first token, all other tokens must be indented past the start of the line
indented_seq!(
indented_seq_skip_first!(
// All closures start with a '\' - e.g. (\x -> x + 1)
byte_indent(b'\\', EClosure::Start),
// Once we see the '\', we're committed to parsing this as a closure.
@ -2419,7 +2555,7 @@ mod when {
pub fn expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
map_with_arena!(
and!(
indented_seq!(
indented_seq_skip_first!(
parser::keyword(keyword::WHEN, EWhen::When),
space0_around_e_no_after_indent_check(
specialize_err_ref(EWhen::Condition, expr_start(options)),
@ -2430,7 +2566,7 @@ mod when {
// ambiguity. The formatter will fix it up.
//
// We require that branches are indented relative to the line containing the `is`.
indented_seq!(
indented_seq_skip_first!(
parser::keyword(keyword::IS, EWhen::Is),
branches(options)
)
@ -2722,6 +2858,18 @@ fn dbg_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpect<
}
}
fn import_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, import_def, state) =
loc!(specialize_err(EExpr::Import, import())).parse(arena, state, min_indent)?;
let mut defs = Defs::default();
defs.push_value_def(import_def.value, import_def.region, &[], &[]);
parse_defs_expr(options, min_indent, defs, arena, state)
}
}
fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
move |arena: &'a Bump, state, min_indent| {
let (_, _, state) =

View file

@ -19,7 +19,7 @@ impl<'a> HeaderType<'a> {
}
| HeaderType::Hosted { exposes, .. }
| HeaderType::Builtin { exposes, .. }
| HeaderType::Interface { exposes, .. } => exposes,
| HeaderType::Module { exposes, .. } => exposes,
HeaderType::Platform { .. } | HeaderType::Package { .. } => &[],
}
}
@ -30,7 +30,7 @@ impl<'a> HeaderType<'a> {
HeaderType::Builtin { .. } => "builtin",
HeaderType::Package { .. } => "package",
HeaderType::Platform { .. } => "platform",
HeaderType::Interface { .. } => "interface",
HeaderType::Module { .. } => "module",
}
}
}
@ -38,7 +38,6 @@ impl<'a> HeaderType<'a> {
#[derive(Debug)]
pub enum HeaderType<'a> {
App {
output_name: StrLiteral<'a>,
provides: &'a [Loc<ExposedName<'a>>],
to_platform: To<'a>,
},
@ -73,7 +72,7 @@ pub enum HeaderType<'a> {
/// usually `pf`
config_shorthand: &'a str,
},
Interface {
Module {
name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>],
},
@ -82,14 +81,10 @@ pub enum HeaderType<'a> {
impl<'a> HeaderType<'a> {
pub fn get_name(self) -> Option<&'a str> {
match self {
Self::Interface { name, .. }
| Self::Builtin { name, .. }
| Self::Hosted { name, .. } => Some(name.into()),
Self::App {
output_name: StrLiteral::PlainLine(name),
..
Self::Module { name, .. } | Self::Builtin { name, .. } | Self::Hosted { name, .. } => {
Some(name.into())
}
| Self::Platform {
Self::Platform {
config_shorthand: name,
..
}
@ -103,6 +98,17 @@ impl<'a> HeaderType<'a> {
}
}
}
pub fn to_maybe_builtin(self, module_id: ModuleId) -> Self {
match self {
HeaderType::Module { name, exposes } if module_id.is_builtin() => HeaderType::Builtin {
name,
exposes,
generates_with: &[],
},
_ => self,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
@ -156,7 +162,15 @@ impl<'a> From<ModuleName<'a>> for &'a str {
}
}
impl<'a> From<ModuleName<'a>> for roc_module::ident::ModuleName {
fn from(name: ModuleName<'a>) -> Self {
name.0.into()
}
}
impl<'a> ModuleName<'a> {
const MODULE_SEPARATOR: char = '.';
pub const fn new(name: &'a str) -> Self {
ModuleName(name)
}
@ -164,6 +178,10 @@ impl<'a> ModuleName<'a> {
pub const fn as_str(&'a self) -> &'a str {
self.0
}
pub fn parts(&'a self) -> impl DoubleEndedIterator<Item = &'a str> {
self.0.split(Self::MODULE_SEPARATOR)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
@ -204,7 +222,6 @@ macro_rules! keywords {
keywords! {
ExposesKeyword => "exposes",
ImportsKeyword => "imports",
WithKeyword => "with",
GeneratesKeyword => "generates",
PackageKeyword => "package",
@ -212,23 +229,29 @@ keywords! {
RequiresKeyword => "requires",
ProvidesKeyword => "provides",
ToKeyword => "to",
PlatformKeyword => "platform",
// Deprecated
ImportsKeyword => "imports",
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct KeywordItem<'a, K, V> {
pub keyword: Spaces<'a, K>,
pub item: V,
}
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<ModuleName<'a>>,
pub struct ModuleHeader<'a> {
pub before_exposes: &'a [CommentOrNewline<'a>],
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>,
// Keeping this so we can format old interface header into module headers
pub interface_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
}
pub type ImportsKeywordItem<'a> = KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>;
pub type ImportsCollection<'a> = Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>;
#[derive(Clone, Debug, PartialEq)]
pub struct HostedHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
@ -250,14 +273,13 @@ pub enum To<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<StrLiteral<'a>>,
pub packages:
Option<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
pub imports:
Option<KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>>,
pub provides: ProvidesTo<'a>,
pub before_provides: &'a [CommentOrNewline<'a>],
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub before_packages: &'a [CommentOrNewline<'a>],
pub packages: Loc<Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
// Old header pieces
pub old_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
pub old_provides_to_new_package: Option<PackageName<'a>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -272,12 +294,10 @@ pub struct ProvidesTo<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<PackageName<'a>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
pub packages:
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
pub before_exposes: &'a [CommentOrNewline<'a>],
pub exposes: Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>,
pub before_packages: &'a [CommentOrNewline<'a>],
pub packages: Loc<Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -333,6 +353,7 @@ pub struct TypedIdent<'a> {
pub struct PackageEntry<'a> {
pub shorthand: &'a str,
pub spaces_after_shorthand: &'a [CommentOrNewline<'a>],
pub platform_marker: Option<&'a [CommentOrNewline<'a>]>,
pub package_name: Loc<PackageName<'a>>,
}
@ -353,9 +374,15 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac
),
space0_e(EPackageEntry::IndentPackage)
)),
loc!(specialize_err(EPackageEntry::BadPackage, package_name()))
and!(
optional(skip_first!(
crate::parser::keyword(crate::keyword::PLATFORM, EPackageEntry::Platform),
space0_e(EPackageEntry::IndentPackage)
)),
loc!(specialize_err(EPackageEntry::BadPackage, package_name()))
)
),
move |arena, (opt_shorthand, package_or_path)| {
move |arena, (opt_shorthand, (platform_marker, package_or_path))| {
let entry = match opt_shorthand {
Some(((shorthand, spaces_before_colon), spaces_after_colon)) => PackageEntry {
shorthand,
@ -364,11 +391,13 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac
spaces_before_colon,
spaces_after_colon,
),
platform_marker,
package_name: package_or_path,
},
None => PackageEntry {
shorthand: "",
spaces_after_shorthand: &[],
platform_marker,
package_name: package_or_path,
},
};
@ -402,7 +431,7 @@ where
}
}
impl<'a> Malformed for InterfaceHeader<'a> {
impl<'a> Malformed for ModuleHeader<'a> {
fn is_malformed(&self) -> bool {
false
}
@ -416,7 +445,7 @@ impl<'a> Malformed for HostedHeader<'a> {
impl<'a> Malformed for AppHeader<'a> {
fn is_malformed(&self) -> bool {
self.name.is_malformed()
false
}
}

View file

@ -6,12 +6,21 @@ pub const WHEN: &str = "when";
pub const AS: &str = "as";
pub const IS: &str = "is";
pub const DBG: &str = "dbg";
pub const IMPORT: &str = "import";
pub const EXPECT: &str = "expect";
pub const EXPECT_FX: &str = "expect-fx";
pub const CRASH: &str = "crash";
// These keywords are valid in imports
pub const EXPOSING: &str = "exposing";
// These keywords are valid in types
pub const IMPLEMENTS: &str = "implements";
pub const WHERE: &str = "where";
pub const KEYWORDS: [&str; 10] = [IF, THEN, ELSE, WHEN, AS, IS, DBG, EXPECT, EXPECT_FX, CRASH];
// These keywords are valid in headers
pub const PLATFORM: &str = "platform";
pub const KEYWORDS: [&str; 11] = [
IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, EXPECT_FX, CRASH,
];

View file

@ -1,10 +1,12 @@
use crate::ast::{Collection, Defs, Header, Module, Spaced, Spaces};
use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::expr::merge_spaces;
use crate::header::{
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
HostedHeader, ImportsEntry, ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName,
PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
HostedHeader, ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword,
KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader, PackagesKeyword,
PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword,
TypedIdent, WithKeyword,
};
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
use crate::parser::Progress::{self, *};
@ -16,7 +18,7 @@ use crate::parser::{
use crate::state::State;
use crate::string_literal::{self, parse_str_literal};
use crate::type_annotation;
use roc_region::all::{Loc, Position};
use roc_region::all::{Loc, Position, Region};
fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
|_arena, state: State<'a>, _min_indent: u32| {
@ -28,12 +30,19 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
}
}
#[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Defs<'a>, SyntaxError<'a>> {
skip_second!(
specialize_err(SyntaxError::Expr, crate::expr::toplevel_defs(),),
end_of_file()
)
pub fn parse_module_defs<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
defs: Defs<'a>,
) -> Result<Defs<'a>, SyntaxError<'a>> {
let min_indent = 0;
match crate::expr::parse_top_level_defs(arena, state.clone(), defs) {
Ok((_, defs, state)) => match end_of_file().parse(arena, state, min_indent) {
Ok(_) => Ok(defs),
Err((_, fail)) => Err(fail),
},
Err((_, fail)) => Err(SyntaxError::Expr(fail, state.pos())),
}
}
pub fn parse_header<'a>(
@ -53,24 +62,31 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
record!(Module {
comments: space0_e(EHeader::IndentStart),
header: one_of![
map!(
skip_first!(
keyword("module", EHeader::Start),
increment_min_indent(module_header())
),
Header::Module
),
map!(
skip_first!(
keyword("interface", EHeader::Start),
increment_min_indent(interface_header())
),
Header::Interface
Header::Module
),
map!(
skip_first!(
keyword("app", EHeader::Start),
increment_min_indent(app_header())
increment_min_indent(one_of![app_header(), old_app_header()])
),
Header::App
),
map!(
skip_first!(
keyword("package", EHeader::Start),
increment_min_indent(package_header())
increment_min_indent(one_of![package_header(), old_package_header()])
),
Header::Package
),
@ -93,22 +109,68 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
}
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> {
record!(InterfaceHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()),
imports: specialize_err(EHeader::Imports, imports()),
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
record!(ModuleHeader {
before_exposes: space0_e(EHeader::IndentStart),
exposes: specialize_err(EHeader::Exposes, exposes_list()),
interface_imports: succeed!(None)
})
.trace("module_header")
}
macro_rules! merge_n_spaces {
($arena:expr, $($slice:expr),*) => {
{
let mut merged = bumpalo::collections::Vec::with_capacity_in(0 $(+ $slice.len())*, $arena);
$(merged.extend_from_slice($slice);)*
merged.into_bump_slice()
}
};
}
/// Parse old interface headers so we can format them into module headers
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
let before_exposes = map_with_arena!(
and!(
skip_second!(
space0_e(EHeader::IndentStart),
loc!(module_name_help(EHeader::ModuleName))
),
specialize_err(EHeader::Exposes, exposes_kw())
),
|arena: &'a bumpalo::Bump,
(before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| {
merge_n_spaces!(arena, before_name, kw.before, kw.after)
}
);
record!(ModuleHeader {
before_exposes: before_exposes,
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
interface_imports: map!(
specialize_err(EHeader::Imports, imports()),
imports_none_if_empty
)
.trace("imports"),
})
.trace("interface_header")
}
fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option<ImportsKeywordItem<'_>> {
if value.item.is_empty() {
None
} else {
Some(value)
}
}
#[inline(always)]
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
record!(HostedHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()),
exposes: specialize_err(EHeader::Exposes, exposes_values_kw()),
imports: specialize_err(EHeader::Imports, imports()),
generates: specialize_err(EHeader::Generates, generates()),
generates_with: specialize_err(EHeader::GeneratesWith, generates_with()),
@ -178,29 +240,194 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
#[inline(always)]
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
record!(AppHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(crate::parser::specialize_err(
EHeader::AppName,
string_literal::parse_str_literal()
)),
packages: optional(specialize_err(EHeader::Packages, packages())),
imports: optional(specialize_err(EHeader::Imports, imports())),
provides: specialize_err(EHeader::Provides, provides_to()),
before_provides: space0_e(EHeader::IndentStart),
provides: specialize_err(EHeader::Exposes, exposes_list()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc!(packages_collection())),
old_imports: succeed!(None),
old_provides_to_new_package: succeed!(None),
})
.trace("app_header")
}
struct OldAppHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub packages: Option<Loc<OldAppPackages<'a>>>,
pub imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
pub provides: ProvidesTo<'a>,
}
type OldAppPackages<'a> =
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>;
#[inline(always)]
fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
let old = record!(OldAppHeader {
before_name: skip_second!(
space0_e(EHeader::IndentStart),
loc!(crate::parser::specialize_err(
EHeader::AppName,
string_literal::parse_str_literal()
))
),
packages: optional(specialize_err(EHeader::Packages, loc!(packages()))),
imports: optional(specialize_err(EHeader::Imports, imports())),
provides: specialize_err(EHeader::Provides, provides_to()),
});
map_with_arena!(old, |arena: &'a bumpalo::Bump, old: OldAppHeader<'a>| {
let mut before_packages: &'a [CommentOrNewline] = &[];
let packages = match old.packages {
Some(packages) => {
before_packages = merge_spaces(
arena,
packages.value.keyword.before,
packages.value.keyword.after,
);
if let To::ExistingPackage(platform_shorthand) = old.provides.to.value {
packages.map(|coll| {
coll.item.map_items(arena, |loc_spaced_pkg| {
if loc_spaced_pkg.value.item().shorthand == platform_shorthand {
loc_spaced_pkg.map(|spaced_pkg| {
spaced_pkg.map(arena, |pkg| {
let mut new_pkg = *pkg;
new_pkg.platform_marker = Some(merge_spaces(
arena,
old.provides.to_keyword.before,
old.provides.to_keyword.after,
));
new_pkg
})
})
} else {
*loc_spaced_pkg
}
})
})
} else {
packages.map(|kw| kw.item)
}
}
None => Loc {
region: Region::zero(),
value: Collection::empty(),
},
};
let provides = match old.provides.types {
Some(types) => {
let mut combined_items = bumpalo::collections::Vec::with_capacity_in(
old.provides.entries.items.len() + types.items.len(),
arena,
);
combined_items.extend_from_slice(old.provides.entries.items);
for loc_spaced_type_ident in types.items {
combined_items.push(loc_spaced_type_ident.map(|spaced_type_ident| {
spaced_type_ident.map(arena, |type_ident| {
ExposedName::new(From::from(*type_ident))
})
}));
}
let value_comments = old.provides.entries.final_comments();
let type_comments = types.final_comments();
let mut combined_comments = bumpalo::collections::Vec::with_capacity_in(
value_comments.len() + type_comments.len(),
arena,
);
combined_comments.extend_from_slice(value_comments);
combined_comments.extend_from_slice(type_comments);
Collection::with_items_and_comments(
arena,
combined_items.into_bump_slice(),
combined_comments.into_bump_slice(),
)
}
None => old.provides.entries,
};
AppHeader {
before_provides: merge_spaces(
arena,
old.before_name,
old.provides.provides_keyword.before,
),
provides,
before_packages: merge_spaces(
arena,
before_packages,
old.provides.provides_keyword.after,
),
packages,
old_imports: old.imports.and_then(imports_none_if_empty),
old_provides_to_new_package: match old.provides.to.value {
To::NewPackage(new_pkg) => Some(new_pkg),
To::ExistingPackage(_) => None,
},
}
})
}
#[inline(always)]
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
record!(PackageHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc!(specialize_err(EHeader::PackageName, package_name())),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, packages()),
before_exposes: space0_e(EHeader::IndentStart),
exposes: specialize_err(EHeader::Exposes, exposes_module_collection()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc!(packages_collection())),
})
.trace("package_header")
}
#[derive(Debug, Clone, PartialEq)]
struct OldPackageHeader<'a> {
before_name: &'a [CommentOrNewline<'a>],
exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
packages:
Loc<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
}
#[inline(always)]
fn old_package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
map_with_arena!(
record!(OldPackageHeader {
before_name: skip_second!(
space0_e(EHeader::IndentStart),
specialize_err(EHeader::PackageName, package_name())
),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, loc!(packages())),
}),
|arena: &'a bumpalo::Bump, old: OldPackageHeader<'a>| {
let before_exposes = merge_n_spaces!(
arena,
old.before_name,
old.exposes.keyword.before,
old.exposes.keyword.after
);
let before_packages = merge_spaces(
arena,
old.packages.value.keyword.before,
old.packages.value.keyword.after,
);
PackageHeader {
before_exposes,
exposes: old.exposes.item,
before_packages,
packages: old.packages.map(|kw| kw.item),
}
}
)
.trace("old_package_header")
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
record!(PlatformHeader {
@ -381,29 +608,40 @@ fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>
}
#[inline(always)]
fn exposes_values<'a>() -> impl Parser<
fn exposes_values_kw<'a>() -> impl Parser<
'a,
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
EExposes,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
keyword: exposes_kw(),
item: exposes_list()
})
}
fn spaces_around_keyword<'a, K: Keyword, E>(
#[inline(always)]
fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> {
spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart,
)
}
#[inline(always)]
fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, EExposes>
{
collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
}
pub fn spaces_around_keyword<'a, K: Keyword, E>(
keyword_item: K,
expectation: fn(Position) -> E,
indent_problem1: fn(Position) -> E,
@ -443,16 +681,21 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
),
item: exposes_module_collection(),
})
}
fn exposes_module_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>, EExposes> {
collection_trailing_sep_e!(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Loc<Spaced<'a, ModuleName<'a>>>, E>
@ -474,22 +717,33 @@ fn packages<'a>() -> impl Parser<
EPackages<'a>,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
PackagesKeyword,
EPackages::Packages,
EPackages::IndentPackages,
EPackages::IndentListStart
),
item: collection_trailing_sep_e!(
byte(b'{', EPackages::ListStart),
specialize_err(EPackages::PackageEntry, loc!(package_entry())),
byte(b',', EPackages::ListEnd),
byte(b'}', EPackages::ListEnd),
Spaced::SpaceBefore
)
keyword: packages_kw(),
item: packages_collection()
})
}
#[inline(always)]
fn packages_kw<'a>() -> impl Parser<'a, Spaces<'a, PackagesKeyword>, EPackages<'a>> {
spaces_around_keyword(
PackagesKeyword,
EPackages::Packages,
EPackages::IndentPackages,
EPackages::IndentListStart,
)
}
#[inline(always)]
fn packages_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>, EPackages<'a>> {
collection_trailing_sep_e!(
byte(b'{', EPackages::ListStart),
specialize_err(EPackages::PackageEntry, loc!(package_entry())),
byte(b',', EPackages::ListEnd),
byte(b'}', EPackages::ListEnd),
Spaced::SpaceBefore
)
}
#[inline(always)]
fn generates<'a>(
) -> impl Parser<'a, KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, EGenerates> {
@ -552,7 +806,7 @@ fn imports<'a>() -> impl Parser<
}
#[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
pub fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g.
//
// printLine : Str -> Effect {}
@ -590,7 +844,7 @@ fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> {
specialize_err(|_, pos| EImports::Shorthand(pos), lowercase_ident())
}
fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
pub fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
where
F: Fn(Position) -> E,
E: 'a,

View file

@ -87,6 +87,7 @@ impl_space_problem! {
EGeneratesWith,
EHeader<'a>,
EIf<'a>,
EImport<'a>,
EImports,
EInParens<'a>,
EClosure<'a>,
@ -210,6 +211,8 @@ pub enum EPackageEntry<'a> {
Shorthand(Position),
Colon(Position),
IndentPackage(Position),
IndentPlatform(Position),
Platform(Position),
Space(BadInputError, Position),
}
@ -346,6 +349,7 @@ pub enum EExpr<'a> {
Expect(EExpect<'a>, Position),
Dbg(EExpect<'a>, Position),
Import(EImport<'a>, Position),
Closure(EClosure<'a>, Position),
Underscore(Position),
@ -519,6 +523,29 @@ pub enum EExpect<'a> {
IndentCondition(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EImport<'a> {
Import(Position),
IndentStart(Position),
PackageShorthand(Position),
PackageShorthandDot(Position),
ModuleName(Position),
IndentAs(Position),
As(Position),
IndentAlias(Position),
Alias(Position),
IndentExposing(Position),
Exposing(Position),
ExposingListStart(Position),
ExposedName(Position),
ExposingListEnd(Position),
IndentIngestedPath(Position),
IngestedPath(Position),
IndentIngestedName(Position),
IngestedName(ETypedIdent<'a>, Position),
Space(BadInputError, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EPattern<'a> {
Record(PRecord<'a>, Position),
@ -1685,7 +1712,7 @@ macro_rules! record {
/// Similar to [`and!`], but we modify the `min_indent` of the second parser to be
/// 1 greater than the `line_indent()` at the start of the first parser.
#[macro_export]
macro_rules! indented_seq {
macro_rules! indented_seq_skip_first {
($p1:expr, $p2:expr) => {
move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, _min_indent: u32| {
let start_indent = state.line_indent();
@ -1710,9 +1737,36 @@ macro_rules! indented_seq {
};
}
/// Similar to [`and!`], but we modify the min_indent of the second parser to be
/// 1 greater than the line_indent() at the start of the first parser.
#[macro_export]
macro_rules! indented_seq {
($p1:expr, $p2:expr) => {
move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, _min_indent: u32| {
let start_indent = state.line_indent();
// TODO: we should account for min_indent here, but this doesn't currently work
// because min_indent is sometimes larger than it really should be, which is in turn
// due to uses of `increment_indent`.
//
// let p1_indent = std::cmp::max(start_indent, min_indent);
let p1_indent = start_indent;
let p2_indent = p1_indent + 1;
match $p1.parse(arena, state, p1_indent) {
Ok((p1, out1, state)) => match $p2.parse(arena, state, p2_indent) {
Ok((p2, out2, state)) => Ok((p1.or(p2), (out1, out2), state)),
Err((p2, fail)) => Err((p1.or(p2), fail)),
},
Err((progress, fail)) => Err((progress, fail)),
}
}
};
}
/// Similar to [`and!`], but we modify the `min_indent` of the second parser to be
/// 1 greater than the `column()` at the start of the first parser.
#[macro_export]
macro_rules! absolute_indented_seq {
($p1:expr, $p2:expr) => {
move |arena: &'a bumpalo::Bump, state: $crate::state::State<'a>, _min_indent: u32| {

View file

@ -1,7 +1,6 @@
use crate::ast;
use crate::ast::Defs;
use crate::module::module_defs;
use crate::parser::Parser;
use crate::module::parse_module_defs;
use crate::parser::SourceError;
use crate::parser::SyntaxError;
use crate::state::State;
@ -34,12 +33,7 @@ pub fn parse_loc_with<'a>(
pub fn parse_defs_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Defs<'a>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
let min_indent = 0;
match module_defs().parse(arena, state, min_indent) {
Ok(tuple) => Ok(tuple.1),
Err(tuple) => Err(tuple.1),
}
parse_module_defs(arena, state, Defs::default())
}
pub fn parse_header_with<'a>(

View file

@ -392,7 +392,7 @@ fn record_type<'a>(
fn applied_type<'a>(stop_at_surface_has: bool) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> {
map!(
and!(
indented_seq!(
specialize_err(EType::TApply, concrete_type()),
// Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float`

View file

@ -22,8 +22,8 @@ mod test_parse {
use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{self, EscapedChar};
use roc_parse::ast::{CommentOrNewline, StrLiteral::*};
use roc_parse::module::module_defs;
use roc_parse::parser::{Parser, SyntaxError};
use roc_parse::module::parse_module_defs;
use roc_parse::parser::SyntaxError;
use roc_parse::state::State;
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Loc, Region};
@ -345,9 +345,7 @@ mod test_parse {
List.map list isTest
"
);
let actual = module_defs()
.parse(&arena, State::new(src.as_bytes()), 0)
.map(|tuple| tuple.1);
let actual = parse_module_defs(&arena, State::new(src.as_bytes()), ast::Defs::default());
// It should occur twice in the debug output - once for the pattern,
// and then again for the lookup.
@ -371,13 +369,12 @@ mod test_parse {
);
let state = State::new(src.as_bytes());
let parser = module_defs();
let parsed = parser.parse(arena, state, 0);
let parsed = parse_module_defs(arena, state, ast::Defs::default());
match parsed {
Ok((_, _, _state)) => {
Ok(_) => {
// dbg!(_state);
}
Err((_, _fail)) => {
Err(_) => {
// dbg!(_fail, _state);
panic!("Failed to parse!");
}

View file

@ -4,7 +4,7 @@ use std::path::PathBuf;
use roc_collections::all::MutSet;
use roc_module::called_via::BinOp;
use roc_module::ident::{Ident, Lowercase, ModuleName, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_module::symbol::{ModuleId, ScopeModuleSource, Symbol};
use roc_parse::ast::Base;
use roc_parse::pattern::PatternType;
use roc_region::all::{Loc, Region};
@ -40,6 +40,20 @@ pub enum Problem {
UnusedModuleImport(ModuleId, Region),
ExposedButNotDefined(Symbol),
UnknownGeneratesWith(Loc<Ident>),
ImportNameConflict {
name: ModuleName,
is_alias: bool,
new_module_id: ModuleId,
new_import_region: Region,
existing_import: ScopeModuleSource,
},
ExplicitBuiltinImport(ModuleId, Region),
ExplicitBuiltinTypeImport(Symbol, Region),
ImportShadowsSymbol {
region: Region,
new_symbol: Symbol,
existing_symbol_region: Region,
},
/// First symbol is the name of the closure with that argument
/// Bool is whether the closure is anonymous
/// Second symbol is the name of the argument that is unused
@ -221,6 +235,10 @@ impl Problem {
Problem::UnusedDef(_, _) => Warning,
Problem::UnusedImport(_, _) => Warning,
Problem::UnusedModuleImport(_, _) => Warning,
Problem::ImportNameConflict { .. } => RuntimeError,
Problem::ExplicitBuiltinImport(_, _) => Warning,
Problem::ExplicitBuiltinTypeImport(_, _) => Warning,
Problem::ImportShadowsSymbol { .. } => RuntimeError,
Problem::ExposedButNotDefined(_) => RuntimeError,
Problem::UnknownGeneratesWith(_) => RuntimeError,
Problem::UnusedArgument(_, _, _, _) => Warning,
@ -295,6 +313,13 @@ impl Problem {
}
| Problem::UnusedImport(_, region)
| Problem::UnusedModuleImport(_, region)
| Problem::ImportNameConflict {
new_import_region: region,
..
}
| Problem::ExplicitBuiltinImport(_, region)
| Problem::ExplicitBuiltinTypeImport(_, region)
| Problem::ImportShadowsSymbol { region, .. }
| Problem::UnknownGeneratesWith(Loc { region, .. })
| Problem::UnusedArgument(_, _, _, region)
| Problem::UnusedBranchDef(_, region)
@ -368,6 +393,7 @@ impl Problem {
| Problem::RuntimeError(RuntimeError::DegenerateBranch(region))
| Problem::RuntimeError(RuntimeError::MultipleRecordBuilders(region))
| Problem::RuntimeError(RuntimeError::UnappliedRecordBuilder(region))
| Problem::RuntimeError(RuntimeError::ReadIngestedFileError { region, .. })
| Problem::InvalidAliasRigid { region, .. }
| Problem::InvalidInterpolation(region)
| Problem::InvalidHexadecimal(region)
@ -576,6 +602,11 @@ pub enum RuntimeError {
/// If unsure, this should be set to `false`
module_exists: bool,
},
ReadIngestedFileError {
filename: PathBuf,
error: io::ErrorKind,
region: Region,
},
InvalidPrecedence(PrecedenceProblem, Region),
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
MalformedTypeName(Box<str>, Region),
@ -659,7 +690,8 @@ impl RuntimeError {
| RuntimeError::InvalidHexadecimal(region)
| RuntimeError::MultipleRecordBuilders(region)
| RuntimeError::UnappliedRecordBuilder(region)
| RuntimeError::InvalidUnicodeCodePt(region) => *region,
| RuntimeError::ReadIngestedFileError { region, .. } => *region,
RuntimeError::InvalidUnicodeCodePt(region) => *region,
RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => Region::zero(),
RuntimeError::LookupNotInScope { loc_name, .. } => loc_name.region,
RuntimeError::OpaqueNotDefined { usage, .. } => usage.region,

View file

@ -958,7 +958,7 @@ fn sort_and_deduplicate<T>(tag_vars: &mut bumpalo::collections::Vec<(TagName, T)
fn find_tag_name_run(slice: &[TagName], subs: &mut Subs) -> Option<SubsSlice<TagName>> {
use std::cmp::Ordering;
let tag_name = slice.get(0)?;
let tag_name = slice.first()?;
let mut result = None;

View file

@ -3448,7 +3448,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
app "test" imports [Result.{ Result }] provides [main] to "./platform"
app "test" imports [] provides [main] to "./platform"
boom = \_ -> boom {}

View file

@ -355,7 +355,7 @@ fn encode_use_stdlib() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
HelloWorld := {} implements [Encoding {toEncoder}]
@ -383,7 +383,7 @@ fn encode_use_stdlib_without_wrapping_custom() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
HelloWorld := {} implements [Encoding {toEncoder}]
@ -433,7 +433,7 @@ fn to_encoder_encode_custom_has_capture() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
HelloWorld := Str implements [Encoding {toEncoder}]
@ -473,7 +473,7 @@ mod encode_immediate {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, TotallyNotJson] provides [main] to "./platform"
app "test" imports [TotallyNotJson] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes "foo" TotallyNotJson.json) is
@ -492,7 +492,7 @@ mod encode_immediate {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, TotallyNotJson] provides [main] to "./platform"
app "test" imports [TotallyNotJson] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes [1, 2, 3] TotallyNotJson.json) is
@ -511,7 +511,7 @@ mod encode_immediate {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, TotallyNotJson] provides [main] to "./platform"
app "test" imports [TotallyNotJson] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes Bool.false TotallyNotJson.json) is
@ -532,7 +532,7 @@ mod encode_immediate {
assert_evals_to!(
&format!(indoc!(
r#"
app "test" imports [Encode, TotallyNotJson] provides [main] to "./platform"
app "test" imports [TotallyNotJson] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes {}{} TotallyNotJson.json) is
@ -572,7 +572,7 @@ fn encode_derived_record_one_field_string() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -595,7 +595,7 @@ fn encode_derived_record_two_fields_strings() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -619,7 +619,7 @@ fn encode_derived_nested_record_string() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -643,7 +643,7 @@ fn encode_derived_tag_one_payload_string() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -666,7 +666,7 @@ fn encode_derived_tag_two_payloads_string() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -689,7 +689,7 @@ fn encode_derived_nested_tag_string() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -714,7 +714,7 @@ fn encode_derived_nested_record_tag_record() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -738,7 +738,7 @@ fn encode_derived_list_string() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -763,7 +763,7 @@ fn encode_derived_list_of_records() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -787,7 +787,7 @@ fn encode_derived_list_of_lists_of_strings() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -812,7 +812,7 @@ fn encode_derived_record_with_many_types() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -839,7 +839,7 @@ fn encode_derived_tuple_two_fields() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -862,7 +862,7 @@ fn encode_derived_tuple_of_tuples() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -886,7 +886,7 @@ fn encode_derived_generic_record_with_different_field_types() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
Q a b := {a: a, b: b} implements [Encoding]
@ -912,7 +912,7 @@ fn encode_derived_generic_tag_with_different_field_types() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
Q a b := [A a, B b] implements [Encoding]
@ -940,7 +940,7 @@ fn specialize_unique_newtype_records() {
indoc!(
r#"
app "test"
imports [Encode, TotallyNotJson]
imports [TotallyNotJson]
provides [main] to "./platform"
main =
@ -1056,7 +1056,7 @@ mod decode_immediate {
use indoc::indoc;
#[cfg(all(test, feature = "gen-llvm"))]
use roc_std::RocStr;
use roc_std::{RocStr, I128, U128};
#[test]
#[cfg(feature = "gen-llvm")]
@ -1121,7 +1121,7 @@ mod decode_immediate {
}
macro_rules! num_immediate {
($($num:expr, $typ:ident)*) => {$(
($($num:expr, $typ:ident, $expected_type:ident)*) => {$(
#[test]
#[cfg(feature = "gen-llvm")]
fn $typ() {
@ -1137,25 +1137,25 @@ mod decode_immediate {
"#
), $num, stringify!($typ), stringify!($typ)),
$num,
$typ
$expected_type
)
}
)*}
}
num_immediate! {
17, i8
17, i16
17, i32
17, i64
17, i128
17, u8
17, u16
17, u32
17, u64
17, u128
17.23, f32
17.23, f64
17, i8, i8
17, i16, i16
17, i32, i32
17, i64, i64
I128::from(17), i128, I128
17, u8, u8
17, u16, u16
17, u32, u32
17, u64, u64
U128::from(17), u128, U128
17.23, f32, f32
17.23, f64, f64
}
#[test]

View file

@ -11,7 +11,7 @@ use crate::helpers::wasm::assert_evals_to;
#[allow(unused_imports)]
use indoc::indoc;
#[allow(unused_imports)]
use roc_std::{RocDec, RocOrder, RocResult};
use roc_std::{RocDec, RocOrder, RocResult, I128, U128};
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
@ -42,8 +42,8 @@ fn i128_signed_int_alias() {
i
"
),
128,
i128
I128::from(128),
I128
);
}
#[test]
@ -126,8 +126,8 @@ fn i128_hex_int_alias() {
f
"
),
0x123,
i128
I128::from(0x123),
I128
);
}
#[test]
@ -207,8 +207,8 @@ fn u128_signed_int_alias() {
i
"
),
128,
u128
U128::from(128),
U128
);
}
#[test]
@ -288,8 +288,8 @@ fn u128_hex_int_alias() {
f
"
),
0x123,
i128
I128::from(0x123),
I128
);
}
#[test]
@ -429,8 +429,8 @@ fn dec_float_alias() {
x
"
),
RocDec::from_str_to_i128_unsafe("2.1"),
i128
RocDec::from_str("2.1").unwrap(),
RocDec
);
}
@ -568,14 +568,14 @@ fn various_sized_abs() {
assert_evals_to!("Num.abs -6i32", 6, i32);
assert_evals_to!("Num.abs -6i64", 6, i64);
if !cfg!(feature = "gen-wasm") {
assert_evals_to!("Num.abs -6i128", 6, i128);
assert_evals_to!("Num.abs -6i128", I128::from(6), I128);
}
assert_evals_to!("Num.abs 6u8", 6, u8);
assert_evals_to!("Num.abs 6u16", 6, u16);
assert_evals_to!("Num.abs 6u32", 6, u32);
assert_evals_to!("Num.abs 6u64", 6, u64);
if !cfg!(feature = "gen-wasm") {
assert_evals_to!("Num.abs 6u128", 6, u128);
assert_evals_to!("Num.abs 6u128", U128::from(6), U128);
}
}
@ -652,8 +652,8 @@ fn gen_add_dec() {
z
"
),
RocDec::from_str_to_i128_unsafe("5.2"),
i128
RocDec::from_str("5.2").unwrap(),
RocDec
);
}
#[test]
@ -742,8 +742,8 @@ fn gen_div_dec() {
x / y
"
),
RocDec::from_str_to_i128_unsafe("3.333333333333333333"),
i128
RocDec::from_str("3.333333333333333333").unwrap(),
RocDec
);
}
@ -764,8 +764,8 @@ fn gen_div_checked_dec() {
Err _ -> -1
"
),
RocDec::from_str_to_i128_unsafe("3.333333333333333333"),
i128
RocDec::from_str("3.333333333333333333").unwrap(),
RocDec
);
}
#[test]
@ -785,8 +785,8 @@ fn gen_div_checked_by_zero_dec() {
Err _ -> -1
"
),
RocDec::from_str_to_i128_unsafe("-1"),
i128
RocDec::from_str("-1").unwrap(),
RocDec
);
}
@ -794,7 +794,7 @@ fn gen_div_checked_by_zero_dec() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
#[should_panic(expected = r#"Roc failed with message: "Decimal division by 0!"#)]
fn gen_div_dec_by_zero() {
assert_evals_to!("1dec / 0", RocDec::from_str_to_i128_unsafe("-1"), i128);
assert_evals_to!("1dec / 0", RocDec::from_str("-1").unwrap(), RocDec);
}
#[test]
@ -1030,8 +1030,8 @@ fn gen_sub_dec() {
(x - y) - z
"
),
RocDec::from_str_to_i128_unsafe("-3.9"),
i128
RocDec::from_str("-3.9").unwrap(),
RocDec
);
}
@ -1053,8 +1053,8 @@ fn gen_mul_dec() {
x * y * z
"
),
RocDec::from_str_to_i128_unsafe("48.0"),
i128
RocDec::from_str("48.0").unwrap(),
RocDec
);
}
@ -2064,7 +2064,7 @@ fn int_mul_wrap_i64() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn int_mul_wrap_i128() {
assert_evals_to!("Num.mulWrap Num.maxI128 2", -2, i128);
assert_evals_to!("Num.mulWrap Num.maxI128 2", I128::from(-2), I128);
}
#[test]
@ -2195,13 +2195,13 @@ fn shift_right_cast_i8() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn min_i128() {
assert_evals_to!("Num.minI128", i128::MIN, i128);
assert_evals_to!("Num.minI128", I128::from(i128::MIN), I128);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn max_i128() {
assert_evals_to!("Num.maxI128", i128::MAX, i128);
assert_evals_to!("Num.maxI128", I128::from(i128::MAX), I128);
}
#[test]
@ -2498,13 +2498,6 @@ to_int_checked_tests! {
to_i64_checked_larger_width_unsigned_fits_pos, "15u128", 15
to_i64_checked_larger_width_unsigned_oob_pos, "9223372036854775808u128", None
)
"Num.toI128Checked", i128, (
to_i128_checked_smaller_width_pos, "15i8", 15
to_i128_checked_smaller_width_neg, "-15i8", -15
to_i128_checked_same, "15i128", 15
to_i128_checked_same_width_unsigned_fits, "15u128", 15
to_i128_checked_same_width_unsigned_oob, "170141183460469231731687303715884105728u128", None
)
"Num.toU8Checked", u8, (
to_u8_checked_same, "15u8", 15
to_u8_checked_same_width_signed_fits, "15i8", 15
@ -2551,13 +2544,81 @@ to_int_checked_tests! {
to_u64_checked_larger_width_unsigned_fits_pos, "15u128", 15
to_u64_checked_larger_width_unsigned_oob_pos, "18446744073709551616u128", None
)
"Num.toU128Checked", u128, (
to_u128_checked_smaller_width_pos, "15i8", 15
to_u128_checked_smaller_width_neg_oob, "-15i8", None
to_u128_checked_same, "15u128", 15
to_u128_checked_same_width_signed_fits, "15i128", 15
to_u128_checked_same_width_signed_oob, "-1i128", None
)
}
fn wrap_with_default(test_roc_code: &str) -> String {
format!("Result.withDefault ({}) 123454321", test_roc_code)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_i128_checked_smaller_width_pos() {
let test_roc_code = wrap_with_default("Num.toI128Checked 15i8");
assert_evals_to!(&test_roc_code, I128::from(15), I128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_i128_checked_smaller_width_neg() {
let test_roc_code = wrap_with_default("Num.toI128Checked -15i8");
assert_evals_to!(&test_roc_code, I128::from(-15), I128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_i128_checked_same() {
let test_roc_code = wrap_with_default("Num.toI128Checked 15i128");
assert_evals_to!(&test_roc_code, I128::from(15), I128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_i128_checked_same_width_unsigned_fits() {
let test_roc_code = wrap_with_default("Num.toI128Checked 15u128");
assert_evals_to!(&test_roc_code, I128::from(15), I128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_i128_checked_same_width_unsigned_oob() {
let test_roc_code =
"Result.isErr (Num.toI128Checked 170141183460469231731687303715884105728u128)";
assert_evals_to!(&test_roc_code, true, bool)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_u128_checked_smaller_width_pos() {
let test_roc_code = wrap_with_default("Num.toU128Checked 15i8");
assert_evals_to!(&test_roc_code, U128::from(15), U128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_u128_checked_smaller_width_neg_oob() {
let test_roc_code = "Result.isErr (Num.toU128Checked -15i8)";
assert_evals_to!(&test_roc_code, true, bool)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_u128_checked_same() {
let test_roc_code = wrap_with_default("Num.toU128Checked 15u128");
assert_evals_to!(&test_roc_code, U128::from(15), U128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_u128_checked_same_width_signed_fits() {
let test_roc_code = wrap_with_default("Num.toU128Checked 15i128");
assert_evals_to!(&test_roc_code, U128::from(15), U128)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_u128_checked_same_width_signed_oob() {
let test_roc_code = "Result.isErr (Num.toU128Checked -1i128)";
assert_evals_to!(&test_roc_code, true, bool)
}
#[test]
@ -3263,8 +3324,8 @@ fn dec_float_suffix() {
123.0dec
"
),
RocDec::from_str_to_i128_unsafe("123.0"),
i128
RocDec::from_str("123.0").unwrap(),
RocDec
);
}
@ -3277,8 +3338,8 @@ fn dec_no_decimal() {
3dec
"
),
RocDec::from_str_to_i128_unsafe("3.0"),
i128
RocDec::from_str("3.0").unwrap(),
RocDec
);
}
@ -3356,8 +3417,11 @@ fn promote_i128_number_layout() {
}
"
),
(18446744073709551617, -9223372036854775808),
(i128, i128)
(
I128::from(18446744073709551617),
I128::from(-9223372036854775808)
),
(I128, I128)
);
}
@ -3370,8 +3434,8 @@ fn promote_u128_number_layout() {
170141183460469231731687303715884105728 + 1
"
),
170141183460469231731687303715884105729,
u128
U128::from(170141183460469231731687303715884105729),
U128
);
}
@ -3498,10 +3562,14 @@ fn num_abs_diff_int() {
#[test]
#[cfg(feature = "gen-llvm")]
fn num_abs_diff_large_bits() {
assert_evals_to!(r"Num.absDiff 0u128 0u128", 0, u128);
assert_evals_to!(r"Num.absDiff 1u128 2u128", 1, u128);
assert_evals_to!(r"Num.absDiff -1i128 1i128", 2, i128);
assert_evals_to!(r"Num.absDiff Num.minI128 -1i128", i128::MAX, i128);
assert_evals_to!(r"Num.absDiff 0u128 0u128", U128::from(0), U128);
assert_evals_to!(r"Num.absDiff 1u128 2u128", U128::from(1), U128);
assert_evals_to!(r"Num.absDiff -1i128 1i128", I128::from(2), I128);
assert_evals_to!(
r"Num.absDiff Num.minI128 -1i128",
I128::from(i128::MAX),
I128
);
}
#[test]
@ -3532,7 +3600,7 @@ fn num_abs_int_min_overflow() {
#[cfg(feature = "gen-llvm")]
#[should_panic(expected = r#"Roc failed with message: "Integer subtraction overflowed!"#)]
fn num_abs_large_bits_min_overflow() {
assert_evals_to!(r"Num.absDiff Num.minI128 0", 0, i128);
assert_evals_to!(r"Num.absDiff Num.minI128 0", I128::from(0), I128);
}
#[test]
@ -3619,8 +3687,8 @@ fn mul_checked_u128() {
x
"
),
RocResult::ok(5u128 * 2u128),
RocResult<u128, ()>
RocResult::ok(U128::from(5u128 * 2u128)),
RocResult<U128, ()>
);
}
@ -3636,8 +3704,8 @@ fn sub_checked_u128() {
x
"
),
RocResult::ok(5u128 - 2u128),
RocResult<u128, ()>
RocResult::ok(U128::from(5u128 - 2u128)),
RocResult<U128, ()>
);
}
@ -3653,8 +3721,8 @@ fn add_checked_u128() {
x
"
),
RocResult::ok(5u128 + 2u128),
RocResult<u128, ()>
RocResult::ok(U128::from(5u128 + 2u128)),
RocResult<U128, ()>
);
}
@ -3717,18 +3785,18 @@ fn without_decimal_point() {
);
assert_evals_to!(
r"Num.withoutDecimalPoint 123.000000000000000000",
123000000000000000000,
i128
I128::from(123000000000000000000),
I128
);
assert_evals_to!(
r"Num.withoutDecimalPoint 170141183460469231731.687303715884105727",
i128::MAX,
i128
I128::from(i128::MAX),
I128
);
assert_evals_to!(
r"Num.withoutDecimalPoint -170141183460469231731.687303715884105728",
i128::MIN,
i128
I128::from(i128::MIN),
I128
);
}

View file

@ -13,7 +13,7 @@ use crate::helpers::dev::assert_evals_to as assert_llvm_evals_to;
#[allow(unused_imports)]
use indoc::indoc;
#[allow(unused_imports)]
use roc_std::{RocList, RocResult, RocStr};
use roc_std::{RocList, RocResult, RocStr, I128, U128};
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
@ -1345,8 +1345,8 @@ fn str_to_i128() {
Str.toI128 "1"
"#
),
RocResult::ok(1),
RocResult<i128, ()>
RocResult::ok(I128::from(1)),
RocResult<I128, ()>
);
}
@ -1359,11 +1359,12 @@ fn str_to_u128() {
Str.toU128 "1"
"#
),
RocResult::ok(1),
RocResult<u128, ()>
RocResult::ok(U128::from(1)),
RocResult<U128, ()>
);
}
// TODO add alignment check between i64 and I64 somewhere
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn str_to_i64() {

View file

@ -15,7 +15,7 @@ use indoc::indoc;
use roc_mono::layout::{LayoutRepr, STLayoutInterner};
#[cfg(test)]
use roc_std::{RocList, RocStr, U128};
use roc_std::{RocList, RocStr, I128, U128};
#[test]
fn width_and_alignment_u8_u8() {
@ -1779,7 +1779,24 @@ fn alignment_i128() {
x
#"
),
// NOTE: roc_std::U128 is always aligned to 16, unlike rust's u128
// NOTE: roc_std::I128 is always aligned to 16, unlike rust's i128
((I128::from(42), true), 1),
((I128, bool), u8)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn alignment_u128() {
assert_evals_to!(
indoc!(
r"#
x : [One U128 Bool, Empty]
x = One 42 (1 == 1)
x
#"
),
// NOTE: roc_std::U128 is always aligned to 16, unlike rust's i128
((U128::from(42), true), 1),
((U128, bool), u8)
);

View file

@ -335,6 +335,9 @@ pub fn helper<'a>(
let (main_fn_name, delayed_errors, module) =
create_llvm_module(arena, src, config, context, target, function_kind);
// for debugging:
//module.print_to_file(std::path::Path::new("/home/username/roc/llvm_ir.ll")).unwrap();
if !config.emit_debug_info {
module.strip_debug_info();
}

View file

@ -12,7 +12,7 @@ use crate::helpers::wasm::assert_evals_to;
#[allow(unused_imports)]
use indoc::indoc;
use roc_std::{RocList, RocStr};
use roc_std::{RocList, RocStr, I128, U128};
#[test]
fn str_split_empty_delimiter() {
@ -998,8 +998,8 @@ fn str_to_i128() {
Err _ -> 0
"#
),
1,
i128
I128::from(1),
I128
);
}
@ -1013,8 +1013,8 @@ fn str_to_u128() {
Err _ -> 0
"#
),
1,
u128
U128::from(1),
U128
);
}

View file

@ -37,4 +37,4 @@
":="
":"
"@"
"->"
"->"

View file

@ -2,7 +2,7 @@ use bumpalo::Bump;
use roc_fmt::{annotation::Formattable, module::fmt_module};
use roc_parse::{
ast::{Defs, Expr, Malformed, Module},
module::module_defs,
module::parse_module_defs,
parser::{Parser, SyntaxError},
state::State,
test_helpers::{parse_defs_with, parse_expr_with, parse_header_with},
@ -170,9 +170,9 @@ impl<'a> Input<'a> {
.parse(arena, state.clone(), min_indent)
.map_err(|(_, fail)| SyntaxError::Header(fail))?;
let (_, module_defs, _state) = module_defs()
.parse(arena, state, min_indent)
.map_err(|(_, fail)| fail)?;
let (header, defs) = header.upgrade_header_imports(arena);
let module_defs = parse_module_defs(arena, state, defs).unwrap();
Ok(Output::Full {
header,

View file

@ -1 +1 @@
Header(Imports(ListEnd(@87), @65))
Header(Exposes(ListEnd(@11), @4))

View file

@ -1,4 +1 @@
app "test-missing-comma"
packages { pf: "platform/main.roc" }
imports [pf.Task Base64]
provides [main, @Foo] to pf
app [main, @Foo] { pf: platform "platform/main.roc" }

View file

@ -1 +1 @@
app "test-app" packages {} imports [] provides [] to blah
app [] {}

Some files were not shown because too many files have changed in this diff Show more