Merge branch 'roc-lang:main' into int_overflow

This commit is contained in:
wizard7377 2024-11-28 08:35:17 -05:00 committed by GitHub
commit 62db49f7d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
231 changed files with 5522 additions and 834 deletions

View file

@ -30,3 +30,4 @@ soa.workspace = true
indoc.workspace = true
insta.workspace = true
pretty_assertions.workspace = true
test_compile.workspace = true

View file

@ -132,7 +132,7 @@ pub struct AbleVariable {
pub first_seen: Region,
}
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct IntroducedVariables {
pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>,

View file

@ -929,7 +929,7 @@ pub struct FxExpectation {
pub ann_region: Option<Region>,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FxCallKind {
Call(Option<Symbol>),
Stmt,
@ -943,7 +943,7 @@ pub struct FxSuffixConstraint {
pub region: Region,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FxSuffixKind {
Let(Symbol),
Pattern(Symbol),
@ -957,9 +957,16 @@ impl FxSuffixKind {
Self::UnsuffixedRecordField => IdentSuffix::None,
}
}
pub fn symbol(&self) -> Option<&Symbol> {
match self {
Self::Let(symbol) | Self::Pattern(symbol) => Some(symbol),
Self::UnsuffixedRecordField => None,
}
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExpectEffectfulReason {
Stmt,
Ignored,

View file

@ -63,7 +63,7 @@ use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Def {
pub loc_pattern: Loc<Pattern>,
pub loc_expr: Loc<Expr>,
@ -91,7 +91,7 @@ impl Def {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DefKind {
/// A def that introduces identifiers
Let,
@ -123,7 +123,7 @@ impl DefKind {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
pub signature: Type,
pub introduced_variables: IntroducedVariables,

View file

@ -2,7 +2,7 @@ use crate::pattern::Pattern;
use roc_region::all::{Loc, Region};
use roc_types::types::{AnnotationSource, PReason, Reason};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum Expected<T> {
NoExpectation(T),
FromAnnotation(Loc<Pattern>, usize, AnnotationSource, T),
@ -10,7 +10,7 @@ pub enum Expected<T> {
}
/// Like Expected, but for Patterns.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum PExpected<T> {
NoExpectation(T),
ForReason(PReason, T, Region),

View file

@ -85,7 +85,55 @@ impl Display for IntValue {
}
}
#[derive(Clone, Debug)]
impl IntValue {
pub fn as_u8(self) -> u8 {
self.as_u128() as u8
}
pub fn as_i8(self) -> i8 {
self.as_i128() as i8
}
pub fn as_u16(self) -> u16 {
self.as_u128() as u16
}
pub fn as_i16(self) -> i16 {
self.as_i128() as i16
}
pub fn as_u32(self) -> u32 {
self.as_u128() as u32
}
pub fn as_i32(self) -> i32 {
self.as_i128() as i32
}
pub fn as_u64(self) -> u64 {
self.as_u128() as u64
}
pub fn as_i64(self) -> i64 {
self.as_i128() as i64
}
pub fn as_u128(self) -> u128 {
match self {
IntValue::I128(i128) => i128::from_ne_bytes(i128) as u128,
IntValue::U128(u128) => u128::from_ne_bytes(u128),
}
}
pub fn as_i128(self) -> i128 {
match self {
IntValue::I128(i128) => i128::from_ne_bytes(i128),
IntValue::U128(u128) => u128::from_ne_bytes(u128) as i128,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
// Literals
@ -104,7 +152,7 @@ pub enum Expr {
loc_elems: Vec<Loc<Expr>>,
},
// An ingested files, it's bytes, and the type variable.
// An ingested files, its bytes, and the type variable.
IngestedFile(Box<PathBuf>, Arc<Vec<u8>>, Variable),
// Lookups
@ -131,7 +179,7 @@ pub enum Expr {
/// The actual condition of the when expression.
loc_cond: Box<Loc<Expr>>,
cond_var: Variable,
/// Result type produced by the branches.
/// Type of each branch (and therefore the type of the entire `when` expression)
expr_var: Variable,
region: Region,
/// The branches of the when, and the type of the condition that they expect to be matched
@ -292,7 +340,7 @@ pub enum Expr {
RuntimeError(RuntimeError),
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ExpectLookup {
pub symbol: Symbol,
pub var: Variable,
@ -370,7 +418,7 @@ impl Expr {
/// Stores exhaustiveness-checking metadata for a closure argument that may
/// have an annotated type.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AnnotatedMark {
pub annotation_var: Variable,
pub exhaustive: ExhaustiveMark,
@ -394,7 +442,7 @@ impl AnnotatedMark {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct ClosureData {
pub function_type: Variable,
pub closure_type: Variable,
@ -491,7 +539,7 @@ impl StructAccessorData {
/// An opaque wrapper like `@Foo`, which is equivalent to `\p -> @Foo p`
/// These are desugared to closures, but we distinguish them so we can have
/// better error messages during constraint generation.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct OpaqueWrapFunctionData {
pub opaque_name: Symbol,
pub opaque_var: Variable,
@ -563,7 +611,7 @@ impl OpaqueWrapFunctionData {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Field {
pub var: Variable,
// The region of the full `foo: f bar`, rather than just `f bar`
@ -587,7 +635,7 @@ impl Recursive {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct WhenBranchPattern {
pub pattern: Loc<Pattern>,
/// Degenerate branch patterns are those that don't fully bind symbols that the branch body
@ -596,7 +644,7 @@ pub struct WhenBranchPattern {
pub degenerate: bool,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct WhenBranch {
pub patterns: Vec<WhenBranchPattern>,
pub value: Loc<Expr>,

View file

@ -19,7 +19,7 @@ use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Identifier(Symbol),
As(Box<Loc<Pattern>>, Symbol),
@ -198,7 +198,7 @@ impl Pattern {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct ListPatterns {
pub patterns: Vec<Loc<Pattern>>,
/// Where a rest pattern splits patterns before and after it, if it does at all.
@ -207,6 +207,7 @@ pub struct ListPatterns {
/// [ .., A, B ] -> patterns = [A, B], rest = 0
/// [ A, .., B ] -> patterns = [A, B], rest = 1
/// [ A, B, .. ] -> patterns = [A, B], rest = 2
/// Optionally, the rest pattern can be named - e.g. `[ A, B, ..others ]`
pub opt_rest: Option<(usize, Option<Loc<Symbol>>)>,
}
@ -228,7 +229,7 @@ impl ListPatterns {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct RecordDestruct {
pub var: Variable,
pub label: Lowercase,
@ -236,14 +237,14 @@ pub struct RecordDestruct {
pub typ: DestructType,
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct TupleDestruct {
pub var: Variable,
pub destruct_index: usize,
pub typ: (Variable, Loc<Pattern>),
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum DestructType {
Required,
Optional(Variable, Loc<Expr>),

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-28,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -65,10 +66,10 @@ Defs {
@15-22,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-25,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -49,10 +50,10 @@ Defs {
@15-19,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-43,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -59,10 +60,10 @@ Defs {
@15-33,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-45,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -59,10 +60,10 @@ Defs {
@15-35,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-28,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -49,10 +50,10 @@ Defs {
@15-22,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-26,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -39,10 +40,10 @@ Defs {
@11-14,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-42,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -49,10 +50,10 @@ Defs {
@16-35,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -11,10 +11,10 @@ Defs {
@0-69,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -34,10 +34,10 @@ Defs {
@15-57,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -67,10 +67,10 @@ Defs {
@31-43,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-114,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@39-101,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -84,10 +85,10 @@ Defs {
@82-91,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-143,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@56-119,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -112,10 +113,10 @@ Defs {
@76-83,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -181,10 +182,10 @@ Defs {
@92-99,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,19 +1,20 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-26,
@0-28,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -24,88 +25,90 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@11-26 Defs(
@11-28 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@15-26,
@16-27,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@15-26 Identifier {
@16-27 Identifier {
ident: "1",
},
@15-26 ParensAround(
@16-27 ParensAround(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@20-25,
@21-26,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@20-25 Identifier {
@21-26 Identifier {
ident: "0",
},
@20-25 Apply(
@22-23 Var {
module_name: "Num",
ident: "add",
},
[
@20-21 Num(
"1",
@21-26 ParensAround(
Apply(
@23-24 Var {
module_name: "Num",
ident: "add",
},
[
@21-22 Num(
"1",
),
@25-26 Num(
"1",
),
],
BinOp(
Plus,
),
@24-25 Num(
"1",
),
],
BinOp(
Plus,
),
),
),
],
},
@15-26 LowLevelDbg(
@16-27 LowLevelDbg(
(
"test.roc:3",
" ",
),
@20-25 Apply(
@20-25 Var {
@21-26 Apply(
@21-26 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@20-25 Var {
@21-26 Var {
module_name: "",
ident: "0",
},
],
Space,
),
@20-25 Var {
@21-26 Var {
module_name: "",
ident: "0",
},
@ -115,25 +118,25 @@ Defs {
),
],
},
@11-26 LowLevelDbg(
@11-28 LowLevelDbg(
(
"test.roc:2",
"in =\n ",
"n =\n ",
),
@15-26 Apply(
@15-26 Var {
@16-27 Apply(
@16-27 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@15-26 Var {
@16-27 Var {
module_name: "",
ident: "1",
},
],
Space,
),
@15-26 Var {
@16-27 Var {
module_name: "",
ident: "1",
},

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-49,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-24,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-99,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -12,12 +13,12 @@ Defs {
@56-98,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 2 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 2 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 2, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 2, length: 1 },
],
spaces: [
Newline,
@ -39,10 +40,10 @@ Defs {
@15-20,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -74,10 +75,10 @@ Defs {
@25-39,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -151,10 +152,10 @@ Defs {
@75-80,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-158,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -45,10 +46,10 @@ Defs {
@50-52,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-31,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@11-24,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-307,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -37,14 +38,14 @@ Defs {
@109-298,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 1 },
Slice { start: 1, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 1, length: 1 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 1, length: 0 },
Slice { start: 2, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 1, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 2, length: 0 },
],
spaces: [
Newline,
@ -171,10 +172,10 @@ Defs {
@140-152,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -293,10 +294,10 @@ Defs {
@227-239,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-189,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -35,12 +36,12 @@ Defs {
@52-70,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 1, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 1, length: 0 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -14,14 +15,14 @@ Defs {
@229-266,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 2 },
Slice { start: 2, length: 2 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 2 },
Slice<roc_parse::ast::CommentOrNewline> { start: 2, length: 2 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 2, length: 0 },
Slice { start: 4, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 2, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 4, length: 1 },
],
spaces: [
Newline,
@ -120,12 +121,12 @@ Defs {
@203-208,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 1, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 1, length: 0 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -12,12 +13,12 @@ Defs {
@35-45,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 2 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 2 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 2, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 2, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-26,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@15-17,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-33,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -39,10 +40,10 @@ Defs {
@11-14,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -99,10 +100,10 @@ Defs {
@20-23,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-26,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -11,10 +11,10 @@ Defs {
@0-72,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -40,10 +40,10 @@ Defs {
@11-23,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-51,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@17-24,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-24,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -39,10 +40,10 @@ Defs {
@11-16,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-61,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-49,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@23-42,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-22,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-51,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -33,10 +34,10 @@ Defs {
@11-40,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
@ -60,10 +61,10 @@ Defs {
@11-12,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -11,10 +11,10 @@ Defs {
@0-73,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -40,10 +40,10 @@ Defs {
@11-57,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-67,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -48,10 +49,10 @@ Defs {
@19-30,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-154,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-44,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -45,10 +46,10 @@ Defs {
@28-29,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-45,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-120,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,
@ -67,10 +68,10 @@ Defs {
@54-65,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -10,10 +11,10 @@ Defs {
@0-74,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
Newline,

View file

@ -0,0 +1,13 @@
#[cfg(test)]
mod test_can_expr {
use roc_can::expr::Expr;
use test_compile::can_expr;
#[test]
fn test_can_unit() {
let output = can_expr("{}");
assert_eq!(output.problems, Vec::new());
assert!(matches!(output.expr, Expr::EmptyRecord));
}
}

View file

@ -464,7 +464,7 @@ mod suffixed_tests {
run_test!(
r#"
main =
dbg (dbg 1 + 1)
dbg (dbg (1 + 1))
"#
);
}

View file

@ -4,6 +4,7 @@
#![allow(clippy::large_enum_variant)]
pub mod all;
mod push;
mod reference_matrix;
mod small_string_interner;
mod small_vec;
@ -12,6 +13,7 @@ mod vec_map;
mod vec_set;
pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap};
pub use push::Push;
pub use reference_matrix::{ReferenceMatrix, Sccs, TopologicalSort};
pub use small_string_interner::SmallStringInterner;
pub use small_vec::SmallVec;

View file

@ -0,0 +1,15 @@
pub trait Push<T> {
fn push(&mut self, entry: T);
}
impl<T> Push<T> for Vec<T> {
fn push(&mut self, entry: T) {
self.push(entry);
}
}
impl<T> Push<T> for &mut Vec<T> {
fn push(&mut self, entry: T) {
(*self).push(entry);
}
}

View file

@ -64,6 +64,15 @@ pub struct Env {
}
impl Env {
pub fn new(home: ModuleId) -> Self {
Self {
rigids: MutMap::default(),
resolutions_to_make: Vec::new(),
home,
fx_expectation: None,
}
}
pub fn with_fx_expectation<F, T>(
&mut self,
fx_var: Variable,

View file

@ -447,7 +447,7 @@ impl<'a> Formattable for Expr<'a> {
buf.push_str("dbg");
}
DbgStmt(condition, continuation) => {
fmt_dbg_stmt(buf, condition, continuation, self.is_multiline(), indent);
fmt_dbg_stmt(buf, condition, continuation, parens, indent);
}
LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting"
@ -1022,42 +1022,15 @@ fn fmt_dbg_stmt<'a>(
buf: &mut Buf,
condition: &'a Loc<Expr<'a>>,
continuation: &'a Loc<Expr<'a>>,
_: bool,
parens: Parens,
indent: u16,
) {
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("dbg");
buf.spaces(1);
fn should_outdent(mut expr: &Expr) -> bool {
loop {
match expr {
Expr::ParensAround(_) | Expr::List(_) | Expr::Record(_) | Expr::Tuple(_) => {
return true
}
Expr::SpaceAfter(inner, _) => {
expr = inner;
}
_ => return false,
}
}
}
let inner_indent = if should_outdent(&condition.value) {
indent
} else {
indent + INDENT
};
let cond_value = condition.value.extract_spaces();
let is_defs = matches!(cond_value.item, Expr::Defs(_, _));
let newlines = if is_defs { Newlines::Yes } else { Newlines::No };
condition.format_with_options(buf, Parens::NotNeeded, newlines, inner_indent);
Expr::Apply(
&Loc::at_zero(Expr::Dbg),
&[condition],
called_via::CalledVia::Space,
)
.format_with_options(buf, parens, Newlines::Yes, indent);
// Always put a blank line after the `dbg` line(s)
buf.ensure_ends_with_blank_line();

View file

@ -1846,6 +1846,26 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
neg_reg64_reg64(buf, dst, src);
}
#[inline(always)]
fn neg_freg64_freg64(
buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
fneg_freg_freg(buf, FloatWidth::F64, dst, src);
}
#[inline(always)]
fn neg_freg32_freg32(
buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
fneg_freg_freg(buf, FloatWidth::F32, dst, src);
}
#[inline(always)]
fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -3953,6 +3973,24 @@ fn fsub_freg_freg_freg(
buf.extend(inst.bytes());
}
/// `FNEG Sd/Dd, Sn/Dn`
#[inline(always)]
fn fneg_freg_freg(
buf: &mut Vec<'_, u8>,
ftype: FloatWidth,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
let inst =
FloatingPointDataProcessingOneSource::new(FloatingPointDataProcessingOneSourceParams {
ptype: ftype,
opcode: 0b00010,
rn: src,
rd: dst,
});
buf.extend(inst.bytes());
}
/// `FCMP Sn/Dn, Sm/Dm` -> Compare Sn/Dn and Sm/Dm, setting condition flags.
#[inline(always)]
fn fcmp_freg_freg(

View file

@ -557,6 +557,18 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn neg_freg64_freg64(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: FloatReg,
src: FloatReg,
);
fn neg_freg32_freg32(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: FloatReg,
src: FloatReg,
);
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
@ -1791,7 +1803,24 @@ impl<
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
}
x => todo!("NumNeg: layout, {:?}", x),
LayoutRepr::F32 => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::neg_freg32_freg32(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
}
LayoutRepr::F64 => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::neg_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
}
LayoutRepr::DEC => self.build_fn_call(
dst,
bitcode::DEC_NEGATE.to_string(),
&[*src],
&[Layout::DEC],
&Layout::DEC,
),
other => internal_error!("unreachable: NumNeg for layout, {:?}", other),
}
}

View file

@ -2602,6 +2602,28 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
neg_reg64(buf, dst);
}
#[inline(always)]
fn neg_freg64_freg64(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: X86_64FloatReg,
src: X86_64FloatReg,
) {
Self::mov_freg64_imm64(buf, relocs, dst, f64::from_bits(0x8000_0000_0000_0000));
xorpd_freg64_freg64(buf, dst, src);
}
#[inline(always)]
fn neg_freg32_freg32(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: X86_64FloatReg,
src: X86_64FloatReg,
) {
Self::mov_freg32_imm32(buf, relocs, dst, f32::from_bits(0x8000_0000));
xorps_freg32_freg32(buf, dst, src);
}
#[inline(always)]
fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -3352,6 +3374,49 @@ fn sqrtss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64F
}
}
/// `XORPD xmm1, xmm2/m128` -> Bitwise exclusive-OR of xmm2/m128 and xmm1.
#[inline(always)]
fn xorpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0x66,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x57,
0xC0 | (dst_mod << 3) | src_mod,
])
} else {
buf.extend([0x66, 0x0F, 0x57, 0xC0 | (dst_mod << 3) | src_mod]);
}
}
/// `XORPS xmm1,xmm2/m128` -> Bitwise exclusive-OR of xmm2/m128 and xmm1.
#[inline(always)]
fn xorps_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x57,
0xC0 | (dst_mod << 3) | src_mod,
]);
} else {
buf.extend([0x0F, 0x57, 0xC0 | (dst_mod << 3) | src_mod]);
}
}
/// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result.
#[allow(dead_code)]
#[inline(always)]

View file

@ -2241,6 +2241,7 @@ fn build_dec_unary_op<'a, 'ctx>(
match op {
NumAbs => dec_unary_op(env, bitcode::DEC_ABS, arg),
NumNeg => dec_unary_op(env, bitcode::DEC_NEGATE, arg),
NumAcos => dec_unary_op(env, bitcode::DEC_ACOS, arg),
NumAsin => dec_unary_op(env, bitcode::DEC_ASIN, arg),
NumAtan => dec_unary_op(env, bitcode::DEC_ATAN, arg),

View file

@ -1624,6 +1624,7 @@ impl<'a> LowLevelCall<'a> {
}
F32 => backend.code_builder.f32_neg(),
F64 => backend.code_builder.f64_neg(),
Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_NEGATE),
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
}
}

View file

@ -53,6 +53,7 @@ pub fn infer_expr(
function_kind: FunctionKind::LambdaSet,
module_params: None,
module_params_vars: Default::default(),
host_exposed_symbols: None,
#[cfg(debug_assertions)]
checkmate: None,
};
@ -189,6 +190,24 @@ pub fn can_expr_with<'a>(
// rules multiple times unnecessarily.
let loc_expr = desugar::desugar_expr(&mut env, &mut scope, &loc_expr);
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,
expr_str,
home,
Path::new("Test.roc"),
&dep_idents,
&module_ids,
None,
roc_can::env::FxMode::PurityInference,
);
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,

View file

@ -11054,8 +11054,8 @@ All branches in an `if` must have the same type!
4 Recursive := [Infinitely Recursive]
^^^^^^^^^
Recursion in opaquees is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
Recursion in opaque types is only allowed if recursion happens behind
a tagged union, at least one variant of which is not recursive.
"
);
@ -14812,7 +14812,7 @@ All branches in an `if` must have the same type!
Str.trim msg
"#
),
@r###"
@r#"
EFFECT IN PURE FUNCTION in /code/proj/Main.roc
This call to `Effect.putLine!` might produce an effect:
@ -14829,18 +14829,7 @@ All branches in an `if` must have the same type!
You can still run the program with this error, which can be helpful
when you're debugging.
UNNECESSARY EXCLAMATION in /code/proj/Main.roc
This function is pure, but its name suggests otherwise:
5 main! = \{} ->
^^^^^
The exclamation mark at the end is reserved for effectful functions.
Hint: Did you forget to run an effect? Is the type annotation wrong?
"###
"#
);
test_report!(
@ -15423,7 +15412,7 @@ All branches in an `if` must have the same type!
pureHigherOrder = \f, x -> f x
"#
),
@r###"
@r#"
TYPE MISMATCH in /code/proj/Main.roc
This 1st argument to `pureHigherOrder` has an unexpected type:
@ -15438,18 +15427,7 @@ All branches in an `if` must have the same type!
But `pureHigherOrder` needs its 1st argument to be:
Str -> {}
UNNECESSARY EXCLAMATION in /code/proj/Main.roc
This function is pure, but its name suggests otherwise:
5 main! = \{} ->
^^^^^
The exclamation mark at the end is reserved for effectful functions.
Hint: Did you forget to run an effect? Is the type annotation wrong?
"###
"#
);
test_report!(
@ -15467,7 +15445,7 @@ All branches in an `if` must have the same type!
pureHigherOrder = \f, x -> f x
"#
),
@r###"
@r#"
TYPE MISMATCH in /code/proj/Main.roc
This 1st argument to `pureHigherOrder` has an unexpected type:
@ -15482,17 +15460,6 @@ All branches in an `if` must have the same type!
But `pureHigherOrder` needs its 1st argument to be:
Str -> {}
UNNECESSARY EXCLAMATION in /code/proj/Main.roc
This function is pure, but its name suggests otherwise:
5 main! = \{} ->
^^^^^
The exclamation mark at the end is reserved for effectful functions.
Hint: Did you forget to run an effect? Is the type annotation wrong?
"###
"#
);
}

View file

@ -350,6 +350,8 @@ fn start_phase<'a>(
None
};
let is_host_exposed = state.root_id == module.module_id;
BuildTask::solve_module(
module,
ident_ids,
@ -367,6 +369,7 @@ fn start_phase<'a>(
state.cached_types.clone(),
derived_module,
state.exec_mode,
is_host_exposed,
//
#[cfg(debug_assertions)]
checkmate,
@ -922,6 +925,7 @@ enum BuildTask<'a> {
cached_subs: CachedTypeState,
derived_module: SharedDerivedModule,
exec_mode: ExecutionMode,
is_host_exposed: bool,
#[cfg(debug_assertions)]
checkmate: Option<roc_checkmate::Collector>,
@ -4331,6 +4335,7 @@ impl<'a> BuildTask<'a> {
cached_subs: CachedTypeState,
derived_module: SharedDerivedModule,
exec_mode: ExecutionMode,
is_host_exposed: bool,
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> Self {
@ -4355,6 +4360,7 @@ impl<'a> BuildTask<'a> {
cached_subs,
derived_module,
exec_mode,
is_host_exposed,
#[cfg(debug_assertions)]
checkmate,
@ -4661,6 +4667,7 @@ fn run_solve_solve(
var_store: VarStore,
module: Module,
derived_module: SharedDerivedModule,
is_host_exposed: bool,
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> SolveResult {
@ -4711,6 +4718,12 @@ fn run_solve_solve(
let (solve_output, solved_implementations, exposed_vars_by_symbol) = {
let module_id = module.module_id;
let host_exposed_idents = if is_host_exposed {
Some(&exposed_symbols)
} else {
None
};
let solve_config = SolveConfig {
home: module_id,
types,
@ -4724,6 +4737,7 @@ fn run_solve_solve(
checkmate,
module_params,
module_params_vars: imported_param_vars,
host_exposed_symbols: host_exposed_idents,
};
let solve_output = roc_solve::module::run_solve(
@ -4800,6 +4814,7 @@ fn run_solve<'a>(
cached_types: CachedTypeState,
derived_module: SharedDerivedModule,
exec_mode: ExecutionMode,
is_host_exposed: bool,
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
) -> Msg<'a> {
@ -4831,6 +4846,7 @@ fn run_solve<'a>(
var_store,
module,
derived_module,
is_host_exposed,
//
#[cfg(debug_assertions)]
checkmate,
@ -4863,6 +4879,7 @@ fn run_solve<'a>(
var_store,
module,
derived_module,
is_host_exposed,
//
#[cfg(debug_assertions)]
checkmate,
@ -6256,6 +6273,7 @@ fn run_task<'a>(
cached_subs,
derived_module,
exec_mode,
is_host_exposed,
#[cfg(debug_assertions)]
checkmate,
@ -6275,6 +6293,7 @@ fn run_task<'a>(
cached_subs,
derived_module,
exec_mode,
is_host_exposed,
//
#[cfg(debug_assertions)]
checkmate,

View file

@ -35,7 +35,7 @@ use roc_region::all::{Loc, Position, Region};
use crate::parser::Progress::{self, *};
fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> {
pub fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|_arena, state: State<'a>, _min_indent: u32| {
if state.has_reached_end() {
Ok((NoProgress, (), state))
@ -545,10 +545,6 @@ fn stmt_start<'a>(
EExpr::Expect,
expect_help(options, preceding_comment)
)),
loc(specialize_err(
EExpr::Dbg,
dbg_stmt_help(options, preceding_comment)
)),
loc(specialize_err(EExpr::Return, return_help(options))),
loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))),
map(
@ -2668,34 +2664,6 @@ fn return_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Stmt<'a>, ERetu
.trace("return_help")
}
fn dbg_stmt_help<'a>(
options: ExprParseOptions,
preceding_comment: Region,
) -> impl Parser<'a, Stmt<'a>, EExpect<'a>> {
(move |arena: &'a Bump, state: State<'a>, min_indent| {
let (_, _, state) =
parser::keyword(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?;
let (_, condition, state) = parse_block(
options,
arena,
state,
true,
EExpect::IndentCondition,
EExpect::Condition,
)
.map_err(|(_, f)| (MadeProgress, f))?;
let stmt = Stmt::ValueDef(ValueDef::Dbg {
condition: arena.alloc(condition),
preceding_comment,
});
Ok((MadeProgress, stmt, state))
})
.trace("dbg_stmt_help")
}
fn dbg_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
(move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, _, next_state) =
@ -3110,12 +3078,43 @@ fn stmts_to_defs<'a>(
}
Stmt::Expr(e) => {
if i + 1 < stmts.len() {
defs.push_value_def(
ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))),
sp_stmt.item.region,
sp_stmt.before,
&[],
);
if let Expr::Apply(
Loc {
value: Expr::Dbg, ..
},
args,
_,
) = e
{
if args.len() != 1 {
// TODO: this should be done in can, not parsing!
return Err(EExpr::Dbg(
EExpect::DbgArity(sp_stmt.item.region.start()),
sp_stmt.item.region.start(),
));
}
let condition = &args[0];
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
let e = Expr::DbgStmt(condition, arena.alloc(rest));
let e = if sp_stmt.before.is_empty() {
e
} else {
arena.alloc(e).before(sp_stmt.before)
};
last_expr = Some(Loc::at(sp_stmt.item.region, e));
// don't re-process the rest of the statements; they got consumed by the dbg expr
break;
} else {
defs.push_value_def(
ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))),
sp_stmt.item.region,
sp_stmt.before,
&[],
);
}
} else {
let e = if sp_stmt.before.is_empty() {
e

View file

@ -1465,6 +1465,7 @@ impl<'a> Normalize<'a> for EExpect<'a> {
EExpect::Continuation(arena.alloc(inner_err.normalize(arena)), Position::zero())
}
EExpect::IndentCondition(_) => EExpect::IndentCondition(Position::zero()),
EExpect::DbgArity(_) => EExpect::DbgArity(Position::zero()),
}
}
}

View file

@ -513,6 +513,7 @@ pub enum EExpect<'a> {
Condition(&'a EExpr<'a>, Position),
Continuation(&'a EExpr<'a>, Position),
IndentCondition(Position),
DbgArity(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -6,7 +6,7 @@ use roc_can::constraint::{Constraint, Constraints};
use roc_can::expr::PendingDerives;
use roc_can::module::{ExposedByModule, ModuleParams, ResolvedImplementations, RigidVariables};
use roc_collections::all::MutMap;
use roc_collections::VecMap;
use roc_collections::{VecMap, VecSet};
use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error;
use roc_module::symbol::{ModuleId, Symbol};
@ -76,6 +76,8 @@ pub struct SolveConfig<'a> {
/// Needed during solving to resolve lambda sets from derived implementations that escape into
/// the user module.
pub derived_module: SharedDerivedModule,
/// Symbols that are exposed to the host which might need special treatment.
pub host_exposed_symbols: Option<&'a VecSet<Symbol>>,
#[cfg(debug_assertions)]
/// The checkmate collector for this module.

View file

@ -19,7 +19,7 @@ use roc_can::constraint::{
};
use roc_can::expected::{Expected, PExpected};
use roc_can::module::ModuleParams;
use roc_collections::VecMap;
use roc_collections::{VecMap, VecSet};
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED;
@ -136,6 +136,7 @@ fn run_help(
function_kind,
module_params,
module_params_vars,
host_exposed_symbols,
..
} = config;
@ -190,6 +191,7 @@ fn run_help(
&mut awaiting_specializations,
module_params,
module_params_vars,
host_exposed_symbols,
);
RunSolveOutput {
@ -249,6 +251,7 @@ fn solve(
awaiting_specializations: &mut AwaitingSpecializations,
module_params: Option<ModuleParams>,
module_params_vars: VecMap<ModuleId, Variable>,
host_exposed_symbols: Option<&VecSet<Symbol>>,
) -> State {
let scope = Scope::new(module_params);
@ -455,6 +458,7 @@ fn solve(
solve_suffix_fx(
env,
problems,
host_exposed_symbols,
FxSuffixKind::Let(*symbol),
loc_var.value,
&loc_var.region,
@ -853,7 +857,7 @@ fn solve(
*type_index,
);
solve_suffix_fx(env, problems, *kind, actual, region);
solve_suffix_fx(env, problems, host_exposed_symbols, *kind, actual, region);
state
}
ExpectEffectful(variable, reason, region) => {
@ -1625,6 +1629,7 @@ fn solve(
fn solve_suffix_fx(
env: &mut InferenceEnv<'_>,
problems: &mut Vec<TypeError>,
host_exposed_symbols: Option<&VecSet<Symbol>>,
kind: FxSuffixKind,
variable: Variable,
region: &Region,
@ -1651,7 +1656,16 @@ fn solve_suffix_fx(
let fx = *fx;
match env.subs.get_content_without_compacting(fx) {
Content::Pure => {
problems.push(TypeError::SuffixedPureFunction(*region, kind));
match (kind.symbol(), host_exposed_symbols) {
(Some(sym), Some(host_exposed)) if host_exposed.contains(sym) => {
// If exposed to the platform, it's allowed to be suffixed but pure
// The platform might require a `main!` function that could perform
// effects, but that's not a requirement.
}
_ => {
problems.push(TypeError::SuffixedPureFunction(*region, kind));
}
}
}
Content::FlexVar(_) => {
env.subs.set_content(fx, Content::Effectful);

View file

@ -15,7 +15,7 @@ use roc_region::all::Region;
use roc_types::types::{Category, ErrorType, PatternCategory};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),

View file

@ -0,0 +1,40 @@
[package]
name = "roc_specialize_types"
description = "Convert a type-checked canonical IR to a monomorphized IR by creating specializations of all functions, such that they are monomorphic in types. We will specialize again later after computing lambda sets, which happens after this pass."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_can = { path = "../can" }
roc_region = { path = "../region" }
roc_types = { path = "../types" }
roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_solve = { path = "../solve" }
bitvec.workspace = true
arrayvec.workspace = true
bumpalo.workspace = true
hashbrown.workspace = true
parking_lot.workspace = true
static_assertions.workspace = true
indoc.workspace = true
soa.workspace = true
[dev-dependencies]
roc_builtins = { path = "../builtins" }
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
roc_load = { path = "../load" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
roc_solve = { path = "../solve" }
test_solve_helpers = { path = "../test_solve_helpers" }
test_compile.workspace = true
pretty_assertions.workspace = true

View file

@ -0,0 +1,8 @@
#[derive(Default)]
pub struct DebugInfo;
impl DebugInfo {
pub fn new() -> Self {
DebugInfo
}
}

View file

@ -0,0 +1,651 @@
use roc_can::expr::{
ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranch,
WhenBranchPattern,
};
use roc_region::all::Loc;
use roc_types::subs::{
AliasVariables, Content, Descriptor, FlatType, LambdaSet, RecordFields, Subs, SubsSlice,
TupleElems, UnionLabels, Variable, VariableSubsSlice,
};
use roc_types::types::{Type, Uls};
pub fn monomorphize(expr: Loc<Expr>, subs: &mut Subs) -> Loc<Expr> {
Loc {
region: expr.region,
value: monomorphize_expr(expr.value, subs),
}
}
fn monomorphize_expr(expr: Expr, subs: &mut Subs) -> Expr {
match expr {
Expr::Num(var, str, int_value, bound) => {
Expr::Num(monomorphize_var(var, subs), str, int_value, bound)
}
Expr::Int(var1, var2, str, int_value, bound) => Expr::Int(
monomorphize_var(var1, subs),
monomorphize_var(var2, subs),
str,
int_value,
bound,
),
Expr::Float(var1, var2, str, float_value, bound) => Expr::Float(
monomorphize_var(var1, subs),
monomorphize_var(var2, subs),
str,
float_value,
bound,
),
Expr::Str(s) => Expr::Str(s),
Expr::IngestedFile(path, bytes, var) => {
Expr::IngestedFile(path, bytes, monomorphize_var(var, subs))
}
Expr::SingleQuote(var1, var2, c, bound) => Expr::SingleQuote(
monomorphize_var(var1, subs),
monomorphize_var(var2, subs),
c,
bound,
),
Expr::List {
elem_var,
loc_elems,
} => Expr::List {
elem_var: monomorphize_var(elem_var, subs),
loc_elems: loc_elems
.into_iter()
.map(|loc_elem| monomorphize(loc_elem, subs))
.collect(),
},
Expr::Var(symbol, var) => Expr::Var(symbol, monomorphize_var(var, subs)),
Expr::ParamsVar {
symbol,
var,
params_symbol,
params_var,
} => Expr::ParamsVar {
symbol,
var: monomorphize_var(var, subs),
params_symbol,
params_var: monomorphize_var(params_var, subs),
},
Expr::AbilityMember(symbol, spec_id, var) => {
Expr::AbilityMember(symbol, spec_id, monomorphize_var(var, subs))
}
Expr::When {
cond_var,
expr_var,
region,
loc_cond,
branches,
branches_cond_var,
exhaustive,
} => Expr::When {
cond_var: monomorphize_var(cond_var, subs),
expr_var: monomorphize_var(expr_var, subs),
region,
loc_cond: Box::new(monomorphize(*loc_cond, subs)),
branches: branches
.into_iter()
.map(|branch| monomorphize_when_branch(branch, subs))
.collect(),
branches_cond_var: monomorphize_var(branches_cond_var, subs),
exhaustive,
},
Expr::If {
cond_var,
branch_var,
branches,
final_else,
} => Expr::If {
cond_var: monomorphize_var(cond_var, subs),
branch_var: monomorphize_var(branch_var, subs),
branches: branches
.into_iter()
.map(|(cond, expr)| (monomorphize(cond, subs), monomorphize(expr, subs)))
.collect(),
final_else: Box::new(monomorphize(*final_else, subs)),
},
Expr::LetRec(defs, expr, cycle_mark) => Expr::LetRec(
defs.into_iter()
.map(|def| monomorphize_def(def, subs))
.collect(),
Box::new(monomorphize(*expr, subs)),
cycle_mark,
),
Expr::LetNonRec(def, expr) => Expr::LetNonRec(
Box::new(monomorphize_def(*def, subs)),
Box::new(monomorphize(*expr, subs)),
),
Expr::Call(boxed, args, called_via) => {
let (fn_var, loc_expr, lambda_set_var, ret_var) = *boxed;
Expr::Call(
Box::new((
monomorphize_var(fn_var, subs),
monomorphize(loc_expr, subs),
monomorphize_var(lambda_set_var, subs),
monomorphize_var(ret_var, subs),
)),
args.into_iter()
.map(|(var, loc_expr)| {
(monomorphize_var(var, subs), monomorphize(loc_expr, subs))
})
.collect(),
called_via,
)
}
Expr::Closure(closure_data) => Expr::Closure(monomorphize_closure_data(closure_data, subs)),
Expr::Record { record_var, fields } => Expr::Record {
record_var: monomorphize_var(record_var, subs),
fields: fields
.into_iter()
.map(|(k, v)| (k, monomorphize_field(v, subs)))
.collect(),
},
Expr::EmptyRecord => Expr::EmptyRecord,
Expr::Tuple { tuple_var, elems } => Expr::Tuple {
tuple_var: monomorphize_var(tuple_var, subs),
elems: elems
.into_iter()
.map(|(var, loc_expr)| {
(
monomorphize_var(var, subs),
Box::new(monomorphize(*loc_expr, subs)),
)
})
.collect(),
},
Expr::ImportParams(module_id, region, params) => Expr::ImportParams(
module_id,
region,
params.map(|(var, expr)| {
(
monomorphize_var(var, subs),
Box::new(monomorphize_expr(*expr, subs)),
)
}),
),
Expr::Crash { msg, ret_var } => Expr::Crash {
msg: Box::new(monomorphize(*msg, subs)),
ret_var: monomorphize_var(ret_var, subs),
},
Expr::RecordAccess {
record_var,
ext_var,
field_var,
loc_expr,
field,
} => Expr::RecordAccess {
record_var: monomorphize_var(record_var, subs),
ext_var: monomorphize_var(ext_var, subs),
field_var: monomorphize_var(field_var, subs),
loc_expr: Box::new(monomorphize(*loc_expr, subs)),
field,
},
Expr::RecordAccessor(data) => {
Expr::RecordAccessor(monomorphize_struct_accessor_data(data, subs))
}
Expr::TupleAccess {
tuple_var,
ext_var,
elem_var,
loc_expr,
index,
} => Expr::TupleAccess {
tuple_var: monomorphize_var(tuple_var, subs),
ext_var: monomorphize_var(ext_var, subs),
elem_var: monomorphize_var(elem_var, subs),
loc_expr: Box::new(monomorphize(*loc_expr, subs)),
index,
},
Expr::RecordUpdate {
record_var,
ext_var,
symbol,
updates,
} => Expr::RecordUpdate {
record_var: monomorphize_var(record_var, subs),
ext_var: monomorphize_var(ext_var, subs),
symbol,
updates: updates
.into_iter()
.map(|(k, v)| (k, monomorphize_field(v, subs)))
.collect(),
},
Expr::Tag {
tag_union_var,
ext_var,
name,
arguments,
} => Expr::Tag {
tag_union_var: monomorphize_var(tag_union_var, subs),
ext_var: monomorphize_var(ext_var, subs),
name,
arguments: arguments
.into_iter()
.map(|(var, loc_expr)| (monomorphize_var(var, subs), monomorphize(loc_expr, subs)))
.collect(),
},
Expr::ZeroArgumentTag {
closure_name,
variant_var,
ext_var,
name,
} => Expr::ZeroArgumentTag {
closure_name,
variant_var: monomorphize_var(variant_var, subs),
ext_var: monomorphize_var(ext_var, subs),
name,
},
Expr::OpaqueRef {
opaque_var,
name,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => Expr::OpaqueRef {
opaque_var: monomorphize_var(opaque_var, subs),
name,
argument: Box::new((
monomorphize_var(argument.0, subs),
monomorphize(argument.1, subs),
)),
specialized_def_type: Box::new(monomorphize_type(*specialized_def_type, subs)),
type_arguments,
lambda_set_variables,
},
Expr::OpaqueWrapFunction(data) => {
Expr::OpaqueWrapFunction(monomorphize_opaque_wrap_function_data(data, subs))
}
Expr::Expect {
loc_condition,
loc_continuation,
lookups_in_cond,
} => Expr::Expect {
loc_condition: Box::new(monomorphize(*loc_condition, subs)),
loc_continuation: Box::new(monomorphize(*loc_continuation, subs)),
lookups_in_cond,
},
Expr::ExpectFx {
loc_condition,
loc_continuation,
lookups_in_cond,
} => Expr::ExpectFx {
loc_condition: Box::new(monomorphize(*loc_condition, subs)),
loc_continuation: Box::new(monomorphize(*loc_continuation, subs)),
lookups_in_cond,
},
Expr::Dbg {
source_location,
source,
loc_message,
loc_continuation,
variable,
symbol,
} => Expr::Dbg {
source_location,
source,
loc_message: Box::new(monomorphize(*loc_message, subs)),
loc_continuation: Box::new(monomorphize(*loc_continuation, subs)),
variable: monomorphize_var(variable, subs),
symbol,
},
Expr::TypedHole(var) => Expr::TypedHole(monomorphize_var(var, subs)),
Expr::RuntimeError(error) => Expr::RuntimeError(error),
Expr::RunLowLevel { op, args, ret_var } => Expr::RunLowLevel {
op,
args: args
.into_iter()
.map(|(var, expr)| (monomorphize_var(var, subs), monomorphize_expr(expr, subs)))
.collect(),
ret_var: monomorphize_var(ret_var, subs),
},
Expr::ForeignCall {
foreign_symbol,
args,
ret_var,
} => Expr::ForeignCall {
foreign_symbol,
args: args
.into_iter()
.map(|(var, expr)| (monomorphize_var(var, subs), monomorphize_expr(expr, subs)))
.collect(),
ret_var: monomorphize_var(ret_var, subs),
},
}
}
fn monomorphize_var(var: Variable, subs: &mut Subs) -> Variable {
let root = subs.get_root_key_without_compacting(var);
let content = subs.get_content_without_compacting(root).clone();
match content {
Content::Structure(flat_type) => match flat_type {
FlatType::Apply(symbol, args) => {
let new_args: Vec<Variable> = args
.into_iter()
.map(|arg| monomorphize_var(subs[arg], subs))
.collect();
let new_slice = VariableSubsSlice::insert_into_subs(subs, new_args);
let new_flat_type = FlatType::Apply(symbol, new_slice);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::Func(args, closure_var, ret_var) => {
let new_args: Vec<Variable> = args
.into_iter()
.map(|arg| monomorphize_var(subs[arg], subs))
.collect();
let new_args_slice = VariableSubsSlice::insert_into_subs(subs, new_args);
let new_closure_var = monomorphize_var(closure_var, subs);
let new_ret_var = monomorphize_var(ret_var, subs);
let new_flat_type = FlatType::Func(new_args_slice, new_closure_var, new_ret_var);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::Record(record_fields, ext_var) => {
let new_variables: Vec<Variable> = record_fields
.variables()
.into_iter()
.map(|v| monomorphize_var(subs[v], subs))
.collect();
let new_variables_slice = VariableSubsSlice::insert_into_subs(subs, new_variables);
let new_record_fields = RecordFields {
length: record_fields.length,
field_names_start: record_fields.field_names_start,
variables_start: new_variables_slice.start,
field_types_start: record_fields.field_types_start,
};
let new_ext_var = monomorphize_var(ext_var, subs);
let new_flat_type = FlatType::Record(new_record_fields, new_ext_var);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::Tuple(tuple_elems, ext_var) => {
let new_variables: Vec<Variable> = tuple_elems
.variables()
.into_iter()
.map(|v| monomorphize_var(subs[v], subs))
.collect();
let new_variables_slice = VariableSubsSlice::insert_into_subs(subs, new_variables);
let new_tuple_elems = TupleElems {
length: tuple_elems.length,
elem_index_start: tuple_elems.elem_index_start,
variables_start: new_variables_slice.start,
};
let new_ext_var = monomorphize_var(ext_var, subs);
let new_flat_type = FlatType::Tuple(new_tuple_elems, new_ext_var);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::TagUnion(union_labels, tag_ext) => {
let new_variable_slices =
SubsSlice::reserve_variable_slices(subs, union_labels.len());
for (old_slice_index, new_slice_index) in union_labels
.variables()
.into_iter()
.zip(new_variable_slices)
{
let old_slice = subs[old_slice_index];
let new_variables: Vec<Variable> = old_slice
.into_iter()
.map(|v| monomorphize_var(subs[v], subs))
.collect();
let new_slice = VariableSubsSlice::insert_into_subs(subs, new_variables);
subs[new_slice_index] = new_slice;
}
let new_union_labels =
UnionLabels::from_slices(union_labels.labels(), new_variable_slices);
let new_tag_ext = tag_ext.map(|v| monomorphize_var(v, subs));
let new_flat_type = FlatType::TagUnion(new_union_labels, new_tag_ext);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::FunctionOrTagUnion(tag_names, symbols, tag_ext) => {
let new_tag_ext = tag_ext.map(|v| monomorphize_var(v, subs));
let new_flat_type = FlatType::FunctionOrTagUnion(tag_names, symbols, new_tag_ext);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::RecursiveTagUnion(rec_var, union_labels, tag_ext) => {
let new_rec_var = monomorphize_var(rec_var, subs);
let new_variable_slices =
SubsSlice::reserve_variable_slices(subs, union_labels.len());
for (old_slice_index, new_slice_index) in union_labels
.variables()
.into_iter()
.zip(new_variable_slices)
{
let old_slice = subs[old_slice_index];
let new_variables: Vec<Variable> = old_slice
.into_iter()
.map(|v| monomorphize_var(subs[v], subs))
.collect();
let new_slice = VariableSubsSlice::insert_into_subs(subs, new_variables);
subs[new_slice_index] = new_slice;
}
let new_union_labels =
UnionLabels::from_slices(union_labels.labels(), new_variable_slices);
let new_tag_ext = tag_ext.map(|v| monomorphize_var(v, subs));
let new_flat_type =
FlatType::RecursiveTagUnion(new_rec_var, new_union_labels, new_tag_ext);
subs.fresh(Descriptor::from(Content::Structure(new_flat_type)))
}
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => var,
},
Content::Alias(symbol, alias_variables, aliased_var, alias_kind) => {
let new_variables: Vec<Variable> = alias_variables
.all_variables()
.into_iter()
.map(|v| monomorphize_var(subs[v], subs))
.collect();
let new_variables_slice = VariableSubsSlice::insert_into_subs(subs, new_variables);
let new_alias_variables = AliasVariables {
variables_start: new_variables_slice.start,
all_variables_len: alias_variables.all_variables_len,
lambda_set_variables_len: alias_variables.lambda_set_variables_len,
type_variables_len: alias_variables.type_variables_len,
};
let new_aliased_var = monomorphize_var(aliased_var, subs);
let new_content =
Content::Alias(symbol, new_alias_variables, new_aliased_var, alias_kind);
subs.fresh(Descriptor::from(new_content))
}
Content::LambdaSet(lambda_set) => {
let new_solved_slices =
SubsSlice::reserve_variable_slices(subs, lambda_set.solved.len());
for (old_slice_index, new_slice_index) in lambda_set
.solved
.variables()
.into_iter()
.zip(new_solved_slices)
{
let old_slice = subs[old_slice_index];
let new_variables: Vec<Variable> = old_slice
.into_iter()
.map(|v| monomorphize_var(subs[v], subs))
.collect();
let new_slice = VariableSubsSlice::insert_into_subs(subs, new_variables);
subs[new_slice_index] = new_slice;
}
let new_solved =
UnionLabels::from_slices(lambda_set.solved.labels(), new_solved_slices);
let new_recursion_var = lambda_set.recursion_var.map(|v| monomorphize_var(v, subs));
let new_unspecialized =
SubsSlice::reserve_uls_slice(subs, lambda_set.unspecialized.len());
for (i, uls) in lambda_set.unspecialized.into_iter().enumerate() {
let Uls(var, sym, region) = subs[uls];
let new_var = monomorphize_var(var, subs);
if let Some(i) = new_unspecialized.into_iter().nth(i) {
subs[i] = Uls(new_var, sym, region);
} else {
debug_panic!("new_unspecialized is too short");
}
}
let new_ambient_function = monomorphize_var(lambda_set.ambient_function, subs);
let new_lambda_set = LambdaSet {
solved: new_solved,
recursion_var: new_recursion_var,
unspecialized: new_unspecialized,
ambient_function: new_ambient_function,
};
let new_content = Content::LambdaSet(new_lambda_set);
subs.fresh(Descriptor::from(new_content))
}
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::RecursionVar { .. }
| Content::ErasedLambda
| Content::RangedNumber(_)
| Content::Error => var,
}
}
fn monomorphize_when_branch(branch: WhenBranch, subs: &mut Subs) -> WhenBranch {
WhenBranch {
patterns: branch
.patterns
.into_iter()
.map(|p| WhenBranchPattern {
pattern: p.pattern,
degenerate: p.degenerate,
})
.collect(),
value: monomorphize(branch.value, subs),
guard: branch.guard.map(|g| monomorphize(g, subs)),
redundant: branch.redundant,
}
}
fn monomorphize_def(def: roc_can::def::Def, subs: &mut Subs) -> roc_can::def::Def {
roc_can::def::Def {
loc_pattern: def.loc_pattern,
loc_expr: monomorphize(def.loc_expr, subs),
expr_var: monomorphize_var(def.expr_var, subs),
pattern_vars: def
.pattern_vars
.into_iter()
.map(|(k, v)| (k, monomorphize_var(v, subs)))
.collect(),
annotation: def.annotation.map(|a| monomorphize_annotation(a, subs)),
}
}
fn monomorphize_closure_data(data: ClosureData, subs: &mut Subs) -> ClosureData {
ClosureData {
function_type: monomorphize_var(data.function_type, subs),
closure_type: monomorphize_var(data.closure_type, subs),
return_type: monomorphize_var(data.return_type, subs),
name: data.name,
captured_symbols: data
.captured_symbols
.into_iter()
.map(|(s, v)| (s, monomorphize_var(v, subs)))
.collect(),
recursive: data.recursive,
arguments: data
.arguments
.into_iter()
.map(|(v, m, p)| (monomorphize_var(v, subs), m, p))
.collect(),
loc_body: Box::new(monomorphize(*data.loc_body, subs)),
}
}
fn monomorphize_field(field: Field, subs: &mut Subs) -> Field {
Field {
var: monomorphize_var(field.var, subs),
region: field.region,
loc_expr: Box::new(monomorphize(*field.loc_expr, subs)),
}
}
fn monomorphize_struct_accessor_data(
data: StructAccessorData,
subs: &mut Subs,
) -> StructAccessorData {
StructAccessorData {
name: data.name,
function_var: monomorphize_var(data.function_var, subs),
record_var: monomorphize_var(data.record_var, subs),
closure_var: monomorphize_var(data.closure_var, subs),
ext_var: monomorphize_var(data.ext_var, subs),
field_var: monomorphize_var(data.field_var, subs),
field: data.field,
}
}
fn monomorphize_opaque_wrap_function_data(
data: OpaqueWrapFunctionData,
subs: &mut Subs,
) -> OpaqueWrapFunctionData {
OpaqueWrapFunctionData {
opaque_name: data.opaque_name,
opaque_var: monomorphize_var(data.opaque_var, subs),
specialized_def_type: monomorphize_type(data.specialized_def_type, subs),
type_arguments: data.type_arguments,
lambda_set_variables: data.lambda_set_variables,
function_name: data.function_name,
function_var: monomorphize_var(data.function_var, subs),
argument_var: monomorphize_var(data.argument_var, subs),
closure_var: monomorphize_var(data.closure_var, subs),
}
}
fn monomorphize_annotation(
annotation: roc_can::def::Annotation,
subs: &mut Subs,
) -> roc_can::def::Annotation {
roc_can::def::Annotation {
signature: monomorphize_type(annotation.signature, subs),
introduced_variables: annotation.introduced_variables,
aliases: annotation.aliases,
region: annotation.region,
}
}
fn monomorphize_type(typ: Type, subs: &mut Subs) -> Type {
match typ {
Type::Tuple(elems, ext) => Type::Tuple(
elems
.into_iter()
.map(|(idx, elem)| (idx, monomorphize_type(elem, subs)))
.collect(),
ext,
),
Type::Record(fields, ext) => Type::Record(
fields
.into_iter()
.map(|(name, field_type)| {
(name, field_type.map(|t| monomorphize_type(t.clone(), subs)))
})
.collect(),
ext,
),
Type::Apply(name, args, region) => Type::Apply(
name,
args.into_iter()
.map(|arg| arg.map(|t| monomorphize_type(t.clone(), subs)))
.collect(),
region,
),
Type::Function(args, ret, ext) => Type::Function(
args.into_iter()
.map(|arg| monomorphize_type(arg, subs))
.collect(),
Box::new(monomorphize_type(*ret, subs)),
ext,
),
Type::TagUnion(tags, ext) => Type::TagUnion(
tags.into_iter()
.map(|(name, tag_types)| {
(
name,
tag_types
.into_iter()
.map(|t| monomorphize_type(t, subs))
.collect(),
)
})
.collect(),
ext,
),
other => other,
}
}

View file

@ -0,0 +1,37 @@
use roc_module::ident::ForeignSymbol;
use soa::Index;
#[derive(Debug, Default)]
pub struct ForeignSymbols {
inner: Vec<ForeignSymbol>,
}
impl ForeignSymbols {
pub fn get(&mut self, id: ForeignSymbolId) -> &ForeignSymbol {
// Safety: we only ever get indices that correspond to actual Vec entries
unsafe { self.inner.get_unchecked(id.inner.index()) }
}
pub fn new() -> Self {
Self {
inner: Default::default(),
}
}
}
impl ForeignSymbols {
pub fn push(&mut self, entry: ForeignSymbol) -> ForeignSymbolId {
let id = self.inner.len();
self.inner.push(entry);
ForeignSymbolId {
inner: Index::new(id as u32),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ForeignSymbolId {
inner: Index<ForeignSymbol>,
}

View file

@ -0,0 +1,24 @@
// TODO [mono2]: re-enable when ready
#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
mod debug_info;
mod foreign_symbol;
mod mono_expr;
mod mono_ir;
mod mono_module;
mod mono_num;
mod mono_struct;
mod mono_type;
// mod specialize_expr;
mod specialize_type;
pub use debug_info::DebugInfo;
pub use foreign_symbol::{ForeignSymbolId, ForeignSymbols};
pub use mono_expr::Env;
pub use mono_ir::{MonoExpr, MonoExprId, MonoExprs};
pub use mono_module::{InternedStrId, Interns};
pub use mono_num::Number;
pub use mono_struct::MonoFieldId;
pub use mono_type::{MonoType, MonoTypeId, MonoTypes};
pub use specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds};

View file

@ -0,0 +1,502 @@
use crate::{
mono_ir::{MonoExpr, MonoExprId, MonoExprs},
mono_module::Interns,
mono_num::Number,
mono_type::{MonoType, MonoTypes, Primitive},
specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds},
DebugInfo, MonoTypeId,
};
use bumpalo::{collections::Vec, Bump};
use roc_can::expr::{Expr, IntValue};
use roc_collections::{Push, VecMap};
use roc_module::symbol::ModuleId;
use roc_region::all::Region;
use roc_solve::module::Solved;
use roc_types::subs::{Content, Subs, Variable};
use soa::NonEmptySlice;
/// Function bodies that have already been specialized.
pub struct MonoFnCache {
inner: VecMap<(ModuleId, Variable, MonoTypeId), MonoExprId>,
}
impl MonoFnCache {
pub fn monomorphize_fn<'a, F: 'a + FnOnce(ModuleId, Variable) -> &'a Expr>(
&mut self,
// Sometimes we need to create specializations of functions that are defined in other modules.
module_id: ModuleId,
// The function Variable stored in the original function's canonical Expr. We use this as a way to
// uniquely identify the function expr within its module, since each fn Expr gets its own unique var.
// Doing it with Variable instead of IdentId lets us cache specializations of anonymous functions too.
fn_var: Variable,
// Given a ModuleId and Variable (to uniquely identify the canonical fn Expr within its module),
// get the canonical Expr of the function itself. We need this to create a specialization of it.
// TODO [mono2]
_get_fn_expr: F,
// This tells us which specialization of the function we want.
mono_type_id: MonoTypeId,
) -> MonoExprId {
*self
.inner
.get_or_insert((module_id, fn_var, mono_type_id), || {
todo!("TODO lower the fn_expr using Env etc. (May need to add args to this method, not sure.)");
})
}
}
pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> {
arena: &'a Bump,
subs: &'s mut Subs,
types_cache: &'c mut MonoTypeCache,
mono_types: &'t mut MonoTypes,
mono_exprs: &'t mut MonoExprs,
record_field_ids: RecordFieldIds,
tuple_elem_ids: TupleElemIds,
debug_info: &'d mut Option<DebugInfo>,
string_interns: &'i mut Interns<'a>,
problems: P,
}
impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
pub fn new(
arena: &'a Bump,
subs: &'s mut Solved<Subs>,
types_cache: &'c mut MonoTypeCache,
mono_types: &'t mut MonoTypes,
mono_exprs: &'t mut MonoExprs,
record_field_ids: RecordFieldIds,
tuple_elem_ids: TupleElemIds,
string_interns: &'i mut Interns<'a>,
debug_info: &'d mut Option<DebugInfo>,
problems: P,
) -> Self {
Env {
arena,
subs: subs.inner_mut(),
types_cache,
mono_types,
mono_exprs,
record_field_ids,
tuple_elem_ids,
string_interns,
debug_info,
problems,
}
}
pub fn to_mono_expr(&mut self, can_expr: &Expr) -> Option<MonoExpr> {
let problems = &mut self.problems;
let mono_types = &mut self.mono_types;
let mut mono_from_var = |var| {
self.types_cache.monomorphize_var(
self.arena,
self.subs,
mono_types,
&mut self.record_field_ids,
&mut self.tuple_elem_ids,
problems,
self.debug_info,
var,
)
};
macro_rules! compiler_bug {
($problem:expr) => {{
problems.push($problem);
Some(MonoExpr::CompilerBug($problem))
}};
}
match can_expr {
Expr::Float(var, _precision_var, _str, val, _bound) => {
match self.subs.get_content_without_compacting(*var) {
Content::FlexVar(_) => {
// Plain decimal number literals like `4.2` can still have an unbound var.
Some(MonoExpr::Number(Number::Dec(*val)))
}
_ => match mono_from_var(*var) {
Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => {
Some(to_frac(*primitive, *val, problems))
}
other => {
compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other)))
}
},
None => {
compiler_bug!(Problem::NumSpecializedToWrongType(None))
}
},
}
}
Expr::Num(var, _, int_value, _) | Expr::Int(var, _, _, int_value, _) => {
// Number literals and int literals both specify integer numbers, so to_num() can work on both.
match mono_from_var(*var) {
Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => {
Some(to_num(*primitive, *int_value, problems))
}
other => compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))),
},
None => compiler_bug!(Problem::NumSpecializedToWrongType(None)),
}
}
Expr::SingleQuote(var, _, char, _) => match mono_from_var(*var) {
// Single-quote characters monomorphize to an integer.
// TODO if we store these using the same representation as other ints (e.g. Expr::Int,
// or keeping a separate value but storing an IntValue instead of a char), then
// even though we verify them differently, we can combine this branch with Num and Int.
Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => {
Some(char_to_int(*primitive, *char, problems))
}
other => compiler_bug!(Problem::CharSpecializedToWrongType(Some(*other))),
},
None => compiler_bug!(Problem::CharSpecializedToWrongType(None)),
},
Expr::Str(contents) => Some(MonoExpr::Str(self.string_interns.get_id(
self.arena,
// TODO should be able to remove this alloc_str() once canonical Expr stores an arena-allocated string.
self.arena.alloc_str(contents),
))),
Expr::EmptyRecord => {
// Empty records are zero-sized and should be discarded.
None
}
Expr::Record {
record_var: _,
fields,
} => {
// TODO [mono2]: store debuginfo for the record type, including ideally type alias and/or opaque type names. Do this before early-returning for single-field records.
// Check for records with 0-1 fields before sorting or reserving a slice of IDs (which might be unnecessary).
// We'll check again after discarding zero-sized fields, because we might end up with 0 or 1 fields remaining.
if fields.len() <= 1 {
return fields
.into_iter()
.next()
.and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value));
}
// Sort the fields alphabetically by name.
let mut fields = Vec::from_iter_in(fields, self.arena);
fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
// We want to end up with a Slice<MonoExpr> of these, so accumulate a buffer of them
// and then add them to MonoExprs all at once, so they're added as one contiguous slice
// regardless of what else got added to MonoExprs as we were monomorphizing them.
let mut buf: Vec<(MonoExpr, Region)> =
Vec::with_capacity_in(fields.len(), self.arena);
buf.extend(
// flat_map these so we discard all the fields that monomorphized to None
fields.into_iter().flat_map(|(_name, field)| {
self.to_mono_expr(&field.loc_expr.value)
.map(|mono_expr| (mono_expr, field.loc_expr.region))
}),
);
// If we ended up with exactly 1 field, return it unwrapped.
if buf.len() == 1 {
return buf.pop().map(|(expr, _region)| expr);
}
NonEmptySlice::from_slice(self.mono_exprs.extend(buf.iter().copied()))
.map(MonoExpr::Struct)
}
// Expr::Call((fn_var, fn_expr, capture_var, ret_var), args, called_via) => {
// let opt_ret_type = mono_from_var(*var);
// if opt_ret_type.is_none() {
// let fn_type = match self.subs.get_content_without_compacting(fn_var) {
// Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)) => {
// let todo = (); // TODO make is_effectful actually use the function's effectfulness!
// let is_effectful = false;
// // Calls to pure functions that return zero-sized types should be discarded.
// if !is_effectful {
// return None;
// }
// // Use the Content we already have to directly monomorphize the function, rather than
// // calling monomorphize_var and having it redo the Subs lookup and conditionals we just did.
// self.types_cache.monomorphize_fn(
// self.subs,
// self.mono_types,
// &mut self.record_field_ids,
// &mut self.tuple_elem_ids,
// &mut self.problems,
// self.debug_info,
// *arg_vars,
// *ret_var,
// )?
// }
// _ => {
// // This function didn't have a function type. Compiler bug!
// return Some(MonoExpr::CompilerBug(Problem::FnDidNotHaveFnType));
// }
// };
// let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type?
// let fn_expr = self.to_mono_expr(can_expr, stmts)?;
// let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices!
// let capture_type = mono_from_var(*capture_var);
// let todo = (); // How do we pre-reserve the statements? Is that possible? It does seem necessary...might not be possible though. Maybe we just need to make Vec rather than Slice on these.
// // We aren't returning anything, and this is an effectful function, so just push a statement to call it and move on.
// stmts.push(self.mono_stmts.add(MonoStmt::CallVoid {
// fn_type,
// fn_expr,
// args,
// capture_type,
// }));
// None
// } else {
// let fn_type = mono_from_var(*fn_var)?;
// let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type?
// let fn_expr = self.to_mono_expr(can_expr, stmts)?;
// let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices!
// let capture_type = mono_from_var(*capture_var);
// Some(MonoExpr::Call {
// fn_type,
// fn_expr,
// args,
// capture_type,
// })
// }
// }
// Expr::Var(symbol, var) => Some(MonoExpr::Lookup(*symbol, mono_from_var(*var)?)),
// Expr::LetNonRec(def, loc) => {
// let expr = self.to_mono_expr(def.loc_expr.value, stmts)?;
// let todo = (); // TODO if this is an underscore pattern and we're doing a fn call, convert it to Stmt::CallVoid
// let pattern = self.to_mono_pattern(def.loc_pattern.value);
// // TODO do we need to use any of these other fields? e.g. for the types?
// // pub struct Def {
// // pub loc_pattern: Loc<Pattern>,
// // pub loc_expr: Loc<Expr>,
// // pub expr_var: Variable,
// // pub pattern_vars: SendMap<Symbol, Variable>,
// // pub annotation: Option<Annotation>,
// // }
// todo!("split up the pattern into various Assign statements.");
// }
// Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(),
_ => todo!(),
// Expr::List {
// elem_var,
// loc_elems,
// } => todo!(),
// Expr::IngestedFile(path_buf, arc, variable) => todo!(),
// Expr::ParamsVar {
// symbol,
// var,
// params_symbol,
// params_var,
// } => todo!(),
// Expr::AbilityMember(symbol, specialization_id, variable) => todo!(),
// Expr::When {
// loc_cond,
// cond_var,
// expr_var,
// region,
// branches,
// branches_cond_var,
// exhaustive,
// } => todo!(),
// Expr::If {
// cond_var,
// branch_var,
// branches,
// final_else,
// } => todo!(),
// Expr::Call(_, vec, called_via) => todo!(),
// Expr::RunLowLevel { op, args, ret_var } => todo!(),
// Expr::ForeignCall {
// foreign_symbol,
// args,
// ret_var,
// } => todo!(),
// Expr::Closure(closure_data) => todo!(),
// Expr::Tuple { tuple_var, elems } => todo!(),
// Expr::ImportParams(module_id, region, _) => todo!(),
// Expr::Crash { msg, ret_var } => todo!(),
// Expr::RecordAccess {
// record_var,
// ext_var,
// field_var,
// loc_expr,
// field,
// } => todo!(),
// Expr::RecordAccessor(struct_accessor_data) => todo!(),
// Expr::TupleAccess {
// tuple_var,
// ext_var,
// elem_var,
// loc_expr,
// index,
// } => todo!(),
// Expr::RecordUpdate {
// record_var,
// ext_var,
// symbol,
// updates,
// } => todo!(),
// Expr::Tag {
// tag_union_var,
// ext_var,
// name,
// arguments,
// } => todo!(),
// Expr::ZeroArgumentTag {
// closure_name,
// variant_var,
// ext_var,
// name,
// } => todo!(),
// Expr::OpaqueRef {
// opaque_var,
// name,
// argument,
// specialized_def_type,
// type_arguments,
// lambda_set_variables,
// } => todo!(),
// Expr::OpaqueWrapFunction(opaque_wrap_function_data) => todo!(),
// Expr::Expect {
// loc_condition,
// loc_continuation,
// lookups_in_cond,
// } => todo!(),
// Expr::ExpectFx {
// loc_condition,
// loc_continuation,
// lookups_in_cond,
// } => todo!(),
// Expr::Dbg {
// source_location,
// source,
// loc_message,
// loc_continuation,
// variable,
// symbol,
// } => todo!(),
// Expr::TypedHole(variable) => todo!(),
// Expr::RuntimeError(_runtime_error) => {
// todo!("generate a MonoExpr::Crash based on the runtime error");
// }
}
}
}
/// Convert a number literal (e.g. `42`) or integer literal (e.g. `0x42`) to a monomorphized type.
/// Nums are allowed to convert to either an integer or a fraction. Integer literals should have
/// given a compile-time error if they ended up unifying to a fractional type, but we can
/// gracefully allow them to compile to that type anyway.
fn to_num(primitive: Primitive, val: IntValue, problems: &mut impl Push<Problem>) -> MonoExpr {
match primitive {
// These are ordered roughly by most to least common integer types
Primitive::U8 => MonoExpr::Number(Number::U8(val.as_i128() as u8)),
Primitive::I8 => MonoExpr::Number(Number::I8(val.as_i128() as i8)),
Primitive::U16 => MonoExpr::Number(Number::U16(val.as_i128() as u16)),
Primitive::I16 => MonoExpr::Number(Number::I16(val.as_i128() as i16)),
Primitive::U32 => MonoExpr::Number(Number::U32(val.as_i128() as u32)),
Primitive::I32 => MonoExpr::Number(Number::I32(val.as_i128() as i32)),
Primitive::U64 => MonoExpr::Number(Number::U64(val.as_i128() as u64)),
Primitive::I64 => MonoExpr::Number(Number::I64(val.as_i128() as i64)),
Primitive::F32 => MonoExpr::Number(Number::F32(val.as_i128() as f32)),
Primitive::F64 => MonoExpr::Number(Number::F64(val.as_i128() as f64)),
Primitive::Dec => MonoExpr::Number(Number::Dec(match val {
IntValue::I128(bytes) => i128::from_ne_bytes(bytes) as f64,
IntValue::U128(bytes) => u128::from_ne_bytes(bytes) as f64,
})),
Primitive::U128 => MonoExpr::Number(Number::U128(val.as_u128())),
Primitive::I128 => MonoExpr::Number(Number::I128(val.as_i128())),
Primitive::Str | Primitive::Crash => {
let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive)));
problems.push(problem);
MonoExpr::CompilerBug(problem)
}
}
}
/// Convert a fractional literal (e.g. `0.5`) to a monomorphized type.
/// If somehow its type was not a fractional type, that's a compiler bug!
fn to_frac(primitive: Primitive, val: f64, problems: &mut impl Push<Problem>) -> MonoExpr {
match primitive {
// These are ordered roughly by most to least common fractional types
Primitive::F32 => MonoExpr::Number(Number::F32(val as f32)),
Primitive::F64 => MonoExpr::Number(Number::F64(val)),
Primitive::Dec => MonoExpr::Number(Number::Dec(val)),
Primitive::U8
| Primitive::I8
| Primitive::U16
| Primitive::I16
| Primitive::U32
| Primitive::I32
| Primitive::U64
| Primitive::I64
| Primitive::U128
| Primitive::I128
| Primitive::Str
| Primitive::Crash => {
let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive)));
problems.push(problem);
MonoExpr::CompilerBug(problem)
}
}
}
/// Convert a single-quote character (e.g. `'r'`) to a monomorphized type.
/// If somehow its type was not an integer type, that's a compiler bug!
fn char_to_int(primitive: Primitive, ch: char, problems: &mut impl Push<Problem>) -> MonoExpr {
match primitive {
// These are ordered roughly by most to least common character types
Primitive::U8 => MonoExpr::Number(Number::U8(ch as u8)),
Primitive::U64 => MonoExpr::Number(Number::U64(ch as u64)),
Primitive::U16 => MonoExpr::Number(Number::U16(ch as u16)),
Primitive::U32 => MonoExpr::Number(Number::U32(ch as u32)),
Primitive::U128 => MonoExpr::Number(Number::U128(ch as u128)),
Primitive::I64 => MonoExpr::Number(Number::I64(ch as i64)),
Primitive::I32 => MonoExpr::Number(Number::I32(ch as i32)),
Primitive::I128 => MonoExpr::Number(Number::I128(ch as i128)),
Primitive::I16 => MonoExpr::Number(Number::I16(ch as i16)),
Primitive::I8 => MonoExpr::Number(Number::I8(ch as i8)),
Primitive::Str | Primitive::Dec | Primitive::F32 | Primitive::F64 | Primitive::Crash => {
let problem = Problem::CharSpecializedToWrongType(Some(MonoType::Primitive(primitive)));
problems.push(problem);
MonoExpr::CompilerBug(problem)
}
}
}
// /// Convert a fraction literal (e.g. `4.2`) to a monomorphized type.
// /// If somehow its type was not a fraction type, that's a compiler bug!
// fn to_frac(
// number: mono_type::Number,
// val: FracValue,
// problems: &mut impl Push<Problem>,
// ) -> MonoExpr {
// match number {
// mono_type::Number::Dec => Number::Dec(val.to_dec()),
// mono_type::Number::F32 => Number::F32(val.to_f32()),
// mono_type::Number::F64 => Number::F64(val.to_f64()),
// mono_type::Number::U8
// | mono_type::Number::I8
// | mono_type::Number::U16
// | mono_type::Number::I16
// | mono_type::Number::U32
// | mono_type::Number::I32
// | mono_type::Number::U64
// | mono_type::Number::I64
// | mono_type::Number::U128
// | mono_type::Number::I128 => {
// // TODO push problem of frac monomorphizing to int, return Malformed expr
// }
// }
// }

View file

@ -0,0 +1,372 @@
use crate::{
foreign_symbol::ForeignSymbolId, mono_module::InternedStrId, mono_num::Number,
mono_struct::MonoFieldId, mono_type::MonoTypeId, specialize_type::Problem,
};
use roc_can::expr::Recursive;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use soa::{Index, NonEmptySlice, Slice, Slice2, Slice3};
use std::iter;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MonoPatternId {
inner: u32,
}
pub type IdentId = Symbol; // TODO make this an Index into an array local to this module
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Def {
pub pattern: MonoPatternId,
/// Named variables in the pattern, e.g. `a` in `Ok a ->`
pub pattern_vars: Slice2<IdentId, MonoTypeId>,
pub expr: MonoExprId,
pub expr_type: MonoTypeId,
}
#[derive(Debug, Default)]
pub struct MonoExprs {
// TODO convert to Vec2
exprs: Vec<MonoExpr>,
regions: Vec<Region>,
}
impl MonoExprs {
pub fn new() -> Self {
Self {
exprs: Vec::new(),
regions: Vec::new(),
}
}
pub fn add(&mut self, expr: MonoExpr, region: Region) -> MonoExprId {
let index = self.exprs.len() as u32;
self.exprs.push(expr);
self.regions.push(region);
MonoExprId {
inner: Index::new(index),
}
}
pub fn get_expr(&self, id: MonoExprId) -> &MonoExpr {
debug_assert!(
self.exprs.get(id.inner.index()).is_some(),
"A MonoExprId was not found in MonoExprs. This should never happen!"
);
// Safety: we should only ever hand out MonoExprIds that are valid indices into here.
unsafe { self.exprs.get_unchecked(id.inner.index()) }
}
pub fn get_region(&self, id: MonoExprId) -> Region {
debug_assert!(
self.regions.get(id.inner.index()).is_some(),
"A MonoExprId was not found in MonoExprs. This should never happen!"
);
// Safety: we should only ever hand out MonoExprIds that are valid indices into here.
unsafe { *self.regions.get_unchecked(id.inner.index()) }
}
pub fn reserve_id(&mut self) -> MonoExprId {
let answer = MonoExprId {
inner: Index::new(self.exprs.len() as u32),
};
// These should all be overwritten; if they aren't, that's a problem!
self.exprs
.push(MonoExpr::CompilerBug(Problem::UninitializedReservedExpr));
self.regions.push(Region::zero());
answer
}
pub fn reserve_ids(&mut self, len: u16) -> Slice<MonoExprId> {
let answer = Slice::new(self.exprs.len() as u32, len);
// These should all be overwritten; if they aren't, that's a problem!
self.exprs.extend(
iter::repeat(MonoExpr::CompilerBug(Problem::UninitializedReservedExpr))
.take(len as usize),
);
self.regions
.extend(iter::repeat(Region::zero()).take(len as usize));
answer
}
pub(crate) fn insert(&mut self, id: MonoExprId, mono_expr: MonoExpr, region: Region) {
debug_assert!(
self.exprs.get(id.inner.index()).is_some(),
"A MonoExprId was not found in MonoExprs. This should never happen!"
);
debug_assert!(
self.regions.get(id.inner.index()).is_some(),
"A MonoExprId was not found in MonoExprs. This should never happen!"
);
let index = id.inner.index();
// Safety: we should only ever hand out MonoExprIds that are valid indices into here.
unsafe {
*self.exprs.get_unchecked_mut(index) = mono_expr;
*self.regions.get_unchecked_mut(index) = region;
}
}
pub fn iter_slice(&self, exprs: Slice<MonoExpr>) -> impl Iterator<Item = &MonoExpr> {
exprs.indices().map(|index| {
debug_assert!(
self.exprs.get(index).is_some(),
"A Slice index was not found in MonoExprs. This should never happen!"
);
// Safety: we should only ever hand out MonoExprId slices that are valid indices into here.
unsafe { self.exprs.get_unchecked(index) }
})
}
pub fn extend(
&mut self,
exprs: impl Iterator<Item = (MonoExpr, Region)> + Clone,
) -> Slice<MonoExpr> {
let start = self.exprs.len();
self.exprs.extend(exprs.clone().map(|(expr, _region)| expr));
self.regions.extend(exprs.map(|(_expr, region)| region));
let len = self.exprs.len() - start;
Slice::new(start as u32, len as u16)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MonoExprId {
inner: Index<MonoExpr>,
}
impl MonoExprId {
pub(crate) unsafe fn new_unchecked(inner: Index<MonoExpr>) -> Self {
Self { inner }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MonoStmtId {
inner: Index<MonoStmt>,
}
impl MonoStmtId {
pub(crate) unsafe fn new_unchecked(inner: Index<MonoStmt>) -> Self {
Self { inner }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MonoStmt {
/// Assign to a variable.
Assign(IdentId, MonoExprId),
AssignRec(IdentId, MonoExprId),
/// Introduce a variable, e.g. `var foo_` (we'll MonoStmt::Assign to it later.)
Declare(IdentId),
/// The `return` statement
Return(MonoExprId),
/// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this"
Crash {
msg: MonoExprId,
/// The type of the `crash` expression (which will have unified to whatever's around it)
expr_type: MonoTypeId,
},
Expect {
condition: MonoExprId,
/// If the expectation fails, we print the values of all the named variables
/// in the final expr. These are those values.
lookups_in_cond: Slice2<MonoTypeId, IdentId>,
},
Dbg {
source_location: InternedStrId,
source: InternedStrId,
expr: MonoExprId,
expr_type: MonoTypeId,
name: IdentId,
},
// Call a function that has no return value (or which we are discarding due to an underscore pattern).
CallVoid {
fn_type: MonoTypeId,
fn_expr: MonoExprId,
args: Slice2<MonoTypeId, MonoExprId>,
/// This is the type of the closure based only on canonical IR info,
/// not considering what other closures might later influence it.
/// Lambda set specialization may change this type later!
capture_type: MonoTypeId,
},
// Branching
When {
/// The actual condition of the when expression.
cond: MonoExprId,
cond_type: MonoTypeId,
/// Type of each branch (and therefore the type of the entire `when` expression)
branch_type: MonoTypeId,
/// Note: if the branches weren't exhaustive, we will have already generated a default
/// branch which crashes if it's reached. (The compiler will have reported an error already;
/// this is for if you want to run anyway.)
branches: NonEmptySlice<WhenBranch>,
},
If {
/// Type of each branch (and therefore the type of the entire `if` expression)
branch_type: MonoTypeId,
branches: Slice<(MonoStmtId, MonoStmtId)>,
final_else: Option<MonoTypeId>,
},
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MonoExpr {
Str(InternedStrId),
Number(Number),
List {
elem_type: MonoTypeId,
elems: Slice<MonoExprId>,
},
Lookup(IdentId, MonoTypeId),
/// Like Lookup, but from a module with params
ParameterizedLookup {
name: IdentId,
lookup_type: MonoTypeId,
params_name: IdentId,
params_type: MonoTypeId,
},
/// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it.
Call {
fn_type: MonoTypeId,
fn_expr: MonoExprId,
args: Slice2<MonoTypeId, MonoExprId>,
/// This is the type of the closure based only on canonical IR info,
/// not considering what other closures might later influence it.
/// Lambda set specialization may change this type later!
capture_type: MonoTypeId,
},
RunLowLevel {
op: LowLevel,
args: Slice<(MonoTypeId, MonoExprId)>,
ret_type: MonoTypeId,
},
ForeignCall {
foreign_symbol: ForeignSymbolId,
args: Slice<(MonoTypeId, MonoExprId)>,
ret_type: MonoTypeId,
},
Lambda {
fn_type: MonoTypeId,
arguments: Slice<(MonoTypeId, MonoPatternId)>,
body: MonoExprId,
captured_symbols: Slice<(IdentId, MonoTypeId)>,
recursive: Recursive,
},
/// A record literal or a tuple literal.
/// These have already been sorted alphabetically.
Struct(NonEmptySlice<MonoExpr>),
/// Look up exactly one field on a record, tuple, or tag payload.
/// At this point we've already unified those concepts and have
/// converted (for example) record field names to indices, and have
/// also dropped all fields that have no runtime representation (e.g. empty records).
///
/// In a later compilation phase, these indices will be re-sorted
/// by alignment and converted to byte offsets, but we in this
/// phase we aren't concerned with alignment or sizes, just indices.
StructAccess {
record_expr: MonoExprId,
record_type: MonoTypeId,
field_type: MonoTypeId,
field_id: MonoFieldId,
},
RecordUpdate {
record_type: MonoTypeId,
record_name: IdentId,
updates: Slice2<MonoFieldId, MonoExprId>,
},
/// Same as BigTag but with u8 discriminant instead of u16
SmallTag {
discriminant: u8,
tag_union_type: MonoTypeId,
args: Slice2<MonoTypeId, MonoExprId>,
},
/// Same as SmallTag but with u16 discriminant instead of u8
BigTag {
discriminant: u16,
tag_union_type: MonoTypeId,
args: Slice2<MonoTypeId, MonoExprId>,
},
Block {
stmts: Slice<MonoStmtId>,
final_expr: MonoExprId,
},
CompilerBug(Problem),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WhenBranch {
pub patterns: Slice<MonoPatternId>,
pub body: Slice<MonoStmtId>,
pub guard: Option<MonoExprId>,
}
#[derive(Clone, Copy, Debug)]
pub enum MonoPattern {
Identifier(IdentId),
As(MonoPatternId, IdentId),
StrLiteral(InternedStrId),
NumberLiteral(Number),
AppliedTag {
tag_union_type: MonoTypeId,
tag_name: IdentId,
args: Slice<MonoPatternId>,
},
StructDestructure {
struct_type: MonoTypeId,
destructs: Slice3<IdentId, MonoFieldId, DestructType>,
},
List {
elem_type: MonoTypeId,
patterns: Slice<MonoPatternId>,
/// Where a rest pattern splits patterns before and after it, if it does at all.
/// If present, patterns at index >= the rest index appear after the rest pattern.
/// For example:
/// [ .., A, B ] -> patterns = [A, B], rest = 0
/// [ A, .., B ] -> patterns = [A, B], rest = 1
/// [ A, B, .. ] -> patterns = [A, B], rest = 2
/// Optionally, the rest pattern can be named - e.g. `[ A, B, ..others ]`
opt_rest: Option<(u16, Option<IdentId>)>,
},
Underscore,
}
#[derive(Clone, Copy, Debug)]
pub enum DestructType {
Required,
Optional(MonoTypeId, MonoExprId),
Guard(MonoTypeId, MonoPatternId),
}

View file

@ -0,0 +1,78 @@
use bumpalo::Bump;
use roc_solve::module::Solved;
use roc_types::subs::Subs;
use crate::{foreign_symbol::ForeignSymbols, mono_type::MonoTypes, DebugInfo};
pub struct MonoModule {
mono_types: MonoTypes,
foreign_symbols: ForeignSymbols,
// TODO [mono2]: interner type
interned_strings: Vec<String>,
debug_info: DebugInfo,
}
impl MonoModule {
pub fn from_typed_can_module(_subs: &Solved<Subs>) -> Self {
Self {
mono_types: MonoTypes::new(),
foreign_symbols: ForeignSymbols::new(),
interned_strings: Vec::new(),
debug_info: DebugInfo,
}
}
}
/// TODO move this to its own crate
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct InternedStrId(u32);
/// TODO move this to its own crate
#[derive(Debug, Default)]
pub struct Interns<'a> {
interned: Vec<&'a str>,
}
impl<'a> Interns<'a> {
pub fn new() -> Self {
Self {
interned: Vec::new(),
}
}
pub fn get_str(&self, _arena: &'a Bump, id: InternedStrId) -> &'a str {
let index = id.0 as usize;
#[cfg(debug_assertions)]
{
assert!(self.interned.get(index).is_some(), "Got an InternedStringId ({index}) that was outside the bounds of the backing array. This should never happen!");
}
// Safety: We should only ever give out InternedStrId values that are in this range.
unsafe { self.interned.get_unchecked(index) }
}
pub fn get_id(&mut self, _arena: &'a Bump, string: &'a str) -> InternedStrId {
match self
.interned
.iter()
.position(|&interned| interned == string)
{
Some(index) => InternedStrId(index as u32),
None => {
let answer = InternedStrId(self.interned.len() as u32);
self.interned.push(string);
answer
}
}
}
pub fn try_get_id(&self, _arena: &'a Bump, string: &'a str) -> Option<InternedStrId> {
self.interned
.iter()
.position(|&interned| interned == string)
.map(|index| InternedStrId(index as u32))
}
}

View file

@ -0,0 +1,16 @@
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Number {
I8(i8),
U8(u8),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
I128(i128),
U128(u128),
F32(f32),
F64(f64),
Dec(f64),
}

View file

@ -0,0 +1,13 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MonoFieldId {
inner: u16,
}
impl MonoFieldId {
pub fn new(index: u16) -> Self {
Self { inner: index }
}
pub fn as_index(self) -> usize {
self.inner as usize
}
}

View file

@ -0,0 +1,296 @@
use core::num::NonZeroU16;
use soa::{Index, NonEmptySlice, Slice};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MonoTypeId {
inner: Index<MonoType>,
}
impl MonoTypeId {
pub const CRASH: Self = Self {
inner: Index::new(0),
};
pub const STR: Self = Self {
inner: Index::new(1),
};
pub const U8: Self = Self {
inner: Index::new(2),
};
pub const I8: Self = Self {
inner: Index::new(3),
};
pub const U16: Self = Self {
inner: Index::new(4),
};
pub const I16: Self = Self {
inner: Index::new(5),
};
pub const U32: Self = Self {
inner: Index::new(6),
};
pub const I32: Self = Self {
inner: Index::new(7),
};
pub const U64: Self = Self {
inner: Index::new(8),
};
pub const I64: Self = Self {
inner: Index::new(9),
};
pub const U128: Self = Self {
inner: Index::new(10),
};
pub const I128: Self = Self {
inner: Index::new(11),
};
pub const F32: Self = Self {
inner: Index::new(12),
};
pub const F64: Self = Self {
inner: Index::new(13),
};
pub const DEC: Self = Self {
inner: Index::new(14),
};
pub const DEFAULT_INT: Self = Self::I64; // TODO change this to I128
pub const DEFAULT_FRAC: Self = Self::DEC;
fn new(inner: Index<MonoType>) -> Self {
Self { inner }
}
}
#[derive(Debug, Default)]
pub struct MonoTypes {
entries: Vec<MonoType>,
ids: Vec<MonoTypeId>,
slices: Vec<(NonZeroU16, MonoTypeId)>, // TODO make this a Vec2
}
impl MonoTypes {
pub fn new() -> Self {
Self {
entries: vec![
MonoType::Primitive(Primitive::Crash),
MonoType::Primitive(Primitive::Str),
MonoType::Primitive(Primitive::U8),
MonoType::Primitive(Primitive::I8),
MonoType::Primitive(Primitive::U16),
MonoType::Primitive(Primitive::I16),
MonoType::Primitive(Primitive::U32),
MonoType::Primitive(Primitive::I32),
MonoType::Primitive(Primitive::U64),
MonoType::Primitive(Primitive::I64),
MonoType::Primitive(Primitive::U128),
MonoType::Primitive(Primitive::I128),
MonoType::Primitive(Primitive::F32),
MonoType::Primitive(Primitive::F64),
MonoType::Primitive(Primitive::Dec),
],
ids: Vec::new(),
slices: Vec::new(),
}
}
pub fn get(&self, id: MonoTypeId) -> &MonoType {
// Future strategy:
// - Look at the three high bits to figure out which of the 8 MonoTypes we're dealing with
// - The non-parameterized builtins have 000 as their high bits, and the whole MonoTypeId can be cast to a Primitive.
// - The parameterized builtins don't need to store a length, just an index. We store that index inline.
// - The non-builtins all store a length and an index. We store the index inline, and the length out of band.
// - Dictionaries store their second param adjacent to the first.
// - This means we use 2 bits for discriminant and another 2 bits for which parameterized type it is
// - This means we get 29-bit indices, so a maximum of ~500M MonoTypes per module. Should be plenty.
// - In the future, we can promote common collection types (e.g. List Str, List U8) to Primitives.
let opt = self.entries.get(id.inner.index());
debug_assert!(opt.is_some(), "A MonoTypeId corresponded to an index that wasn't in MonoTypes. This should never happen!");
unsafe { opt.unwrap_unchecked() }
}
pub(crate) fn add_primitive(&mut self, _primitive: Primitive) -> MonoTypeId {
todo!("if it's one of the hardcoded ones, find the associated MonoTypeId; otherwise, store it etc.");
}
pub(crate) fn add_function(
&mut self,
ret: Option<MonoTypeId>,
args: impl IntoIterator<Item = MonoTypeId>,
) -> MonoTypeId {
let mono_type = match ret {
Some(ret) => {
let ret_then_args = {
let start = self.ids.len();
self.ids.push(ret);
self.ids.extend(args);
// Safety: we definitely have at least 2 elements in here, even if the iterator is empty.
let length =
unsafe { NonZeroU16::new_unchecked((self.ids.len() - start) as u16) };
NonEmptySlice::new(start as u32, length)
};
MonoType::Func { ret_then_args }
}
None => {
let args = {
let start = self.ids.len();
self.ids.extend(args);
let length = (self.ids.len() - start) as u16;
Slice::new(start as u32, length)
};
MonoType::VoidFunc { args }
}
};
let index = self.entries.len();
self.entries.push(mono_type);
MonoTypeId::new(Index::new(index as u32))
}
/// This should only be given iterators with at least 2 elements in them.
/// We receive the fields in sorted order (e.g. by record field name or by tuple index).
/// A later compiler phase will stable-sort them by alignment (we don't deal with alignment here),
/// and that phase will also sort its DebugInfo struct fields in the same way.
pub(crate) unsafe fn add_struct_unchecked(
&mut self,
fields: impl Iterator<Item = MonoTypeId>,
) -> MonoTypeId {
let start = self.ids.len();
self.extend_ids(fields);
let len = self.ids.len() - start;
let non_empty_slice =
// Safety: This definitely has at least 2 elements in it, because we just added them.
unsafe { NonEmptySlice::new_unchecked(start as u32, len as u16)};
let index = self.entries.len();
self.entries.push(MonoType::Struct(non_empty_slice));
MonoTypeId::new(Index::new(index as u32))
}
/// We receive the payloads in sorted order (sorted by tag name).
pub(crate) fn add_tag_union(
&mut self,
first_payload: MonoTypeId,
second_payload: MonoTypeId,
other_payloads: impl Iterator<Item = MonoTypeId>,
) -> MonoTypeId {
let start = self.ids.len();
self.ids.push(first_payload);
self.ids.push(second_payload);
self.extend_ids(other_payloads);
let len = self.ids.len() - start;
let non_empty_slice =
// Safety: This definiely has at least 2 elements in it, because we just added them.
unsafe { NonEmptySlice::new_unchecked(start as u32, len as u16)};
let index = self.entries.len();
self.entries.push(MonoType::Struct(non_empty_slice));
MonoTypeId::new(Index::new(index as u32))
}
fn extend_ids(&mut self, iter: impl Iterator<Item = MonoTypeId>) -> Slice<MonoTypeId> {
let start = self.ids.len();
self.ids.extend(iter);
let length = self.ids.len() - start;
Slice::new(start as u32, length as u16)
}
pub(crate) fn add(&mut self, entry: MonoType) -> MonoTypeId {
let id = Index::new(self.entries.len() as u32);
self.entries.push(entry);
MonoTypeId { inner: id }
}
}
// TODO: we can make all of this take up a minimal amount of memory as follows:
// 1. Arrange it so that for each MonoType variant we need at most one length and one start index.
// 2. Store all MonoType discriminants in one array (there are only 5 of them, so u3 is plenty;
// if we discard record field names, can unify record and tuple and use u2 for the 4 variants)
// 3. Store all the MonoType variant slice lengths in a separate array (u8 should be plenty)
// 4. Store all the MonoType start indices in a separate array (u32 should be plenty)
/// Primitive means "Builtin type that has no type parameters" (so, numbers, Str, and Unit)
///
/// In the future, we may promote common builtin types to Primitives, e.g. List U8, List Str, etc.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Primitive {
Crash,
Str,
U8,
I8,
U16,
I16,
U32,
I32,
U64,
I64,
U128,
I128,
F32,
F64,
Dec,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MonoType {
Primitive(Primitive),
Box(MonoTypeId),
List(MonoTypeId),
/// Records, tuples, and tag union payloads all end up here. (Empty ones are handled separate.)
///
/// Slice of field types, ordered alphabetically by field name (or by tuple elem index).
/// The strings for the field names (or tuple indices) are stored out of band in DebugInfo,
/// which references this MonoTypeId. A later compiler phase will sort these by alignment
/// (this phase is not aware of alignment), and will sort the DebugInfo structs accordingly.
Struct(NonEmptySlice<MonoTypeId>),
/// Slice of payloads, where each payload is a struct or Unit. (Empty tag unions become Unit.)
///
/// These have already been sorted alphabetically by tag name, and the tag name strings
/// have already been recorded out of band in DebugInfo.
TagUnion(NonEmptySlice<MonoTypeId>),
/// A function that has a return value and 0 or more arguments.
/// To avoid wasting memory, we store the return value first in the nonempty slice,
/// and then the arguments after it.
Func {
ret_then_args: NonEmptySlice<MonoTypeId>,
},
/// A function that does not have a return value (e.g. the return value was {}, which
/// got eliminated), and which has 0 or more arguments.
/// This has to be its own variant because the other function variant uses one slice to
/// store the return value followed by the arguments. Without this separate variant,
/// the other one couldn't distinguish between a function with a return value and 0 arguments,
/// and a function with 1 argument but no return value.
VoidFunc {
args: Slice<MonoTypeId>,
},
// This last slot is tentatively reserved for Dict, because in the past we've discussed wanting to
// implement Dict in Zig (for performance) instead of on top of List, like it is as of this writing.
//
// Assuming we do that, Set would still be implemented as a Dict with a unit type for the value,
// so we would only need one variant for both.
//
// The second type param would be stored adjacent to the first, so we only need to store one index.
// Dict(MonoTypeId),
}

View file

@ -0,0 +1,650 @@
/// Given a Subs that's been populated from type inference, and a Variable,
/// ensure that Variable is monomorphic by going through and creating
/// specializations of that type wherever necessary.
///
/// This only operates at the type level. It does not create new function implementations (for example).
use crate::{
debug_info::DebugInfo,
mono_type::{MonoTypeId, MonoTypes},
MonoFieldId, MonoType,
};
use bumpalo::{collections::Vec, Bump};
use roc_collections::{Push, VecMap};
use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_solve::module::Solved;
use roc_types::{
subs::{Content, FlatType, Subs, SubsSlice, Variable},
types::AliasKind,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Problem {
// Compiler bugs; these should never happen!
TagUnionExtWasNotTagUnion,
RecordExtWasNotRecord,
TupleExtWasNotTuple,
/// This can be either an integer specializing to a fractional number type (or vice versa),
/// or the type parameter specializing to a non-numeric type (e.g. Num Str), which should
/// have been caught during type-checking and changed to an Error type.
NumSpecializedToWrongType(
Option<MonoType>, // `None` means it specialized to Unit
),
CharSpecializedToWrongType(
Option<MonoType>, // `None` means it specialized to Unit
),
BadNumTypeParam,
UninitializedReservedExpr,
FnDidNotHaveFnType,
}
/// For MonoTypes that are records, store their field indices.
pub type RecordFieldIds = VecMap<MonoTypeId, VecMap<Lowercase, MonoFieldId>>;
/// For MonoTypes that are tuples, store their element indices.
/// (These are not necessarily the same as their position in the monomorphized tuple,
/// because we may have deleted some zero-sized types in the middle - yet expressions
/// will still refer to e.g. `tuple.1`, so we still need to know which element `.1`
/// referred to originally before we deleted things.
pub type TupleElemIds = VecMap<MonoTypeId, VecMap<u16, MonoFieldId>>;
/// Variables that have already been monomorphized.
pub struct MonoTypeCache {
inner: VecMap<Variable, MonoTypeId>,
}
impl MonoTypeCache {
pub fn from_solved_subs(subs: &Solved<Subs>) -> Self {
Self {
inner: VecMap::with_capacity(subs.inner().len()),
}
}
/// Returns None if it monomorphizes to a type that should be eliminated
/// (e.g. a zero-sized type like empty record, empty tuple, a record of just those, etc.)
pub fn monomorphize_var(
&mut self,
arena: &Bump,
subs: &Subs,
mono_types: &mut MonoTypes,
field_indices: &mut RecordFieldIds,
elem_indices: &mut TupleElemIds,
problems: &mut impl Push<Problem>,
debug_info: &mut Option<DebugInfo>,
var: Variable,
) -> Option<MonoTypeId> {
let mut env = Env {
arena,
cache: self,
mono_types,
field_ids: field_indices,
elem_ids: elem_indices,
problems,
debug_info,
};
env.lower_var(subs, var)
}
}
struct Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
arena: &'a Bump,
cache: &'c mut MonoTypeCache,
mono_types: &'m mut MonoTypes,
field_ids: &'f mut RecordFieldIds,
elem_ids: &'e mut TupleElemIds,
problems: &'p mut P,
debug_info: &'d mut Option<DebugInfo>,
}
impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
fn lower_builtin(
&mut self,
subs: &Subs,
symbol: Symbol,
args: SubsSlice<Variable>,
) -> MonoTypeId {
if symbol == Symbol::NUM_NUM {
number_args_to_mono_id(args, subs, self.problems)
} else if symbol == Symbol::NUM_FLOATINGPOINT {
num_num_args_to_mono_id(symbol, args, subs, self.problems)
} else if symbol == Symbol::LIST_LIST {
todo!();
// let mut new_args = args
// .into_iter()
// .flat_map(|var_index| self.lower_var( subs, subs[var_index]));
// let arg = new_args.next();
} else {
todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this.");
}
}
/// Exposed separately because sometimes we already looked up the Content and know it's a function,
/// and want to continue from there without redoing the lookup.
pub fn monomorphize_fn(
&mut self,
subs: &Subs,
arg_vars: SubsSlice<Variable>,
ret_var: Variable,
// TODO [mono2]
_fx_var: Variable,
) -> MonoTypeId {
let func = self.lower_var(subs, ret_var);
let mut mono_args = Vec::with_capacity_in(arg_vars.len(), self.arena);
mono_args.extend(
arg_vars
.into_iter()
.flat_map(|var_index| self.lower_var(subs, subs[var_index])),
);
// TODO [mono2] populate debuginfo as appropriate
self.mono_types.add_function(func, mono_args)
}
fn lower_var(&mut self, subs: &Subs, var: Variable) -> Option<MonoTypeId> {
let root_var = subs.get_root_key_without_compacting(var);
// TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId)
// and then overwrite it rather than having a separate cache. That memory is already in cache
// for sure, and the lookups should be faster because they're O(1) but don't require hashing.
// Kinda creates a cyclic dep though.
if let Some(mono_id) = self.cache.inner.get(&root_var) {
return Some(*mono_id);
}
// Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations.
let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) {
Content::Structure(flat_type) => match flat_type {
FlatType::Apply(symbol, args) => {
if symbol.is_builtin() {
self.lower_builtin(subs, symbol, args)
} else {
todo!("handle non-builtin Apply");
}
}
FlatType::Func(args, _capture, ret, fx) => {
self.monomorphize_fn(subs, args, ret, fx)
}
_ => {
todo!();
} /*
FlatType::Record(fields, ext) => {
let mut labeled_mono_ids = lower_record(env, fields, ext);
// Handle the special cases of 0 fields and 1 field.
match labeled_mono_ids.first() {
Some((label, first_field_id)) => {
if labeled_mono_ids.len() == 1 {
// If we ended up with a single field, return it unwrapped.
let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it)
let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one.
return Some(*first_field_id);
}
}
None => {
// If we ended up with an empty record,
// after removing other empty things, return None.
return None;
}
}
// Now we know we have at least 2 fields, so sort them by field name.
// This can be unstable sort because all field names are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
// Safety: we already verified that this has at least 2 elements, and
// we would have early returned before this point if we had fewer than 2.
let mono_id = unsafe {
mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id))
};
let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16))));
self.field_ids.insert(mono_id, labeled_indices);
Some(mono_id)
}
FlatType::Tuple(elems, ext) => {
let indexed_mono_ids = lower_tuple(env, elems, ext);
// This can be unstable sort because all indices are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id))
}
FlatType::TagUnion(tags, ext) => {
let tagged_payload_ids = lower_tag_union(env, tags, ext);
// This can be unstable sort because all tag names are known to be unique,
// so sorting unstable won't be observable (and is faster than stable).
tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2));
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id))
}
FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => {
// If this is still a FunctionOrTagUnion, turn it into a TagUnion.
// First, resolve the ext var.
let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext);
// Now lower all the tags we gathered from the ext var.
// (Do this in a separate pass to avoid borrow errors on Subs.)
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
// Then, add the tag names with no payloads. (There are no variables to lower here.)
for index in tag_names.into_iter() {
tags.push(((subs[index]).clone(), Vec::new()));
}
Content::Structure(FlatType::TagUnion(
UnionTags::insert_into_subs(subs, tags),
TagExt::Any(Variable::EMPTY_TAG_UNION),
))
}
FlatType::RecursiveTagUnion(rec, tags, ext) => {
let mut tags = resolve_tag_ext(subs, problems, *tags, *ext);
// Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs.
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
Content::Structure(FlatType::RecursiveTagUnion(
lower_var(cache, subs, problems, *rec),
UnionTags::insert_into_subs(subs, tags),
TagExt::Any(Variable::EMPTY_TAG_UNION),
))
}
FlatType::EmptyRecord|
FlatType::EmptyTuple |
FlatType::EmptyTagUnion => None,
},
Content::Error => Content::Error,
*/
},
Content::RangedNumber(range) => {
use roc_types::num::NumericRange::*;
match range {
IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
IntAtLeastEitherSign(int_lit_width) => {
int_lit_width_to_mono_type_id(int_lit_width)
}
NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
NumAtLeastEitherSign(int_lit_width) => {
int_lit_width_to_mono_type_id(int_lit_width)
}
}
}
Content::Alias(symbol, args, real, kind) => {
match kind {
AliasKind::Opaque if symbol.is_builtin() => {
let args_slice =
SubsSlice::new(args.variables_start, args.type_variables_len);
self.lower_builtin(subs, symbol, args_slice)
}
_ => {
// TODO [mono2] record in debuginfo the alias name for whatever we're lowering.
self.lower_var(subs, real)?
}
}
}
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => {
// The only way we should reach this branch is in something like a `crash`.
MonoTypeId::CRASH
}
Content::ErasedLambda | Content::LambdaSet(_) => {
unreachable!(
"This new monomorphization implementation must not do anything with lambda sets, because they'll be handled later!"
);
}
content => {
todo!("specialize this Content: {content:?}");
}
};
// This var is now known to be monomorphic, so we don't repeat this work again later.
// (We don't insert entries for Unit values.)
self.cache.inner.insert(root_var, mono_id);
Some(mono_id)
}
}
fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> MonoTypeId {
use roc_can::num::IntLitWidth;
match int_lit_width {
IntLitWidth::U8 => MonoTypeId::U8,
IntLitWidth::U16 => MonoTypeId::U16,
IntLitWidth::U32 => MonoTypeId::U32,
IntLitWidth::U64 => MonoTypeId::U64,
IntLitWidth::U128 => MonoTypeId::U128,
IntLitWidth::I8 => MonoTypeId::I8,
IntLitWidth::I16 => MonoTypeId::I16,
IntLitWidth::I32 => MonoTypeId::I32,
IntLitWidth::I64 => MonoTypeId::I64,
IntLitWidth::I128 => MonoTypeId::I128,
IntLitWidth::F32 => MonoTypeId::F32,
IntLitWidth::F64 => MonoTypeId::F64,
IntLitWidth::Dec => MonoTypeId::DEC,
}
}
/// This works on the arg(s) to a Num.Num
fn num_num_args_to_mono_id(
outer_symbol: Symbol,
args: SubsSlice<Variable>,
subs: &Subs,
problems: &mut impl Push<Problem>,
) -> MonoTypeId {
match args.into_iter().next() {
Some(arg_index) if args.len() == 1 => {
let mut content = subs.get_content_without_compacting(subs[arg_index]);
// Unroll aliases in this loop, as many aliases as we encounter.
loop {
match content {
Content::Structure(flat_type) => {
if let FlatType::Apply(inner_symbol, args) = flat_type {
let inner_symbol = *inner_symbol;
if args.is_empty() {
if outer_symbol == Symbol::NUM_INTEGER {
if inner_symbol == Symbol::NUM_UNSIGNED8 {
return MonoTypeId::U8;
} else if inner_symbol == Symbol::NUM_SIGNED8 {
return MonoTypeId::I8;
} else if inner_symbol == Symbol::NUM_UNSIGNED16 {
return MonoTypeId::U16;
} else if inner_symbol == Symbol::NUM_SIGNED16 {
return MonoTypeId::I16;
} else if inner_symbol == Symbol::NUM_UNSIGNED32 {
return MonoTypeId::U32;
} else if inner_symbol == Symbol::NUM_SIGNED32 {
return MonoTypeId::I32;
} else if inner_symbol == Symbol::NUM_UNSIGNED64 {
return MonoTypeId::U64;
} else if inner_symbol == Symbol::NUM_SIGNED64 {
return MonoTypeId::I64;
} else if inner_symbol == Symbol::NUM_UNSIGNED128 {
return MonoTypeId::U128;
} else if inner_symbol == Symbol::NUM_SIGNED128 {
return MonoTypeId::I128;
}
} else if outer_symbol == Symbol::NUM_FLOATINGPOINT {
if inner_symbol == Symbol::NUM_BINARY32 {
return MonoTypeId::F32;
} else if inner_symbol == Symbol::NUM_BINARY64 {
return MonoTypeId::F64;
} else if inner_symbol == Symbol::NUM_DECIMAL {
return MonoTypeId::DEC;
}
}
}
}
}
Content::FlexVar(_) => {
if outer_symbol == Symbol::NUM_INTEGER {
// Int *
return MonoTypeId::DEFAULT_INT;
} else if outer_symbol == Symbol::NUM_FLOATINGPOINT {
// Frac *
return MonoTypeId::DEFAULT_FRAC;
}
}
Content::Alias(_symbol, _alias_variables, variable, alias_kind) => {
match alias_kind {
AliasKind::Structural => {
// Unwrap the alias and continue the loop.
//
// (Unlike in most aliases, here we don't care about the name
// for debug info purposes; all we care about is determining
// whether it's one of the builtin types.)
content = subs.get_content_without_compacting(*variable);
}
AliasKind::Opaque => {
// This should never happen (type-checking should have caught it),
// so if an opaque type made it here, it's definitely a compiler bug!
break;
}
}
}
Content::RangedNumber(_numeric_range) => todo!(),
_ => {
// This is an invalid number type, so break out of
// the alias-unrolling loop in order to return an error.
break;
}
}
}
}
_ => {
// This is an invalid number type, so fall through to the error case.
}
}
// If we got here, it's because the Num type parameter(s) don't fit the form we expect.
// Specialize to a crash!
problems.push(Problem::BadNumTypeParam);
MonoTypeId::CRASH
}
fn number_args_to_mono_id(
args: SubsSlice<Variable>,
subs: &Subs,
problems: &mut impl Push<Problem>,
) -> MonoTypeId {
match args.into_iter().next() {
Some(arg_index) if args.len() == 1 => {
let mut content = subs.get_content_without_compacting(subs[arg_index]);
// Unroll aliases in this loop, as many aliases as we encounter.
loop {
match content {
Content::Structure(flat_type) => {
if let FlatType::Apply(outer_symbol, args) = flat_type {
return num_num_args_to_mono_id(*outer_symbol, *args, subs, problems);
} else {
break;
}
}
Content::FlexVar(_) => {
// Num *
return MonoTypeId::DEFAULT_INT;
}
Content::Alias(_symbol, _alias_variables, variable, alias_kind) => {
match alias_kind {
AliasKind::Structural => {
// Unwrap the alias and continue the loop.
//
// (Unlike in most aliases, here we don't care about the name
// for debug info purposes; all we care about is determining
// whether it's one of the builtin types.)
content = subs.get_content_without_compacting(*variable);
}
AliasKind::Opaque => {
// This should never happen (type-checking should have caught it),
// so if an opaque type made it here, it's definitely a compiler bug!
break;
}
}
}
Content::RangedNumber(_numeric_range) => todo!(),
_ => {
// This is an invalid number type, so break out of
// the alias-unrolling loop in order to return an error.
break;
}
}
}
}
_ => {
// The Num type did not have exactly 1 type parameter; fall through to the error case.
}
}
// If we got here, it's because the Num type parameter(s) don't fit the form we expect.
// Specialize to a crash!
problems.push(Problem::BadNumTypeParam);
MonoTypeId::CRASH
}
// fn resolve_tag_ext(
// subs: &mut Subs,
// mono_types: &mut MonoTypes,
// problems: &mut impl Push<Problem>,
// mut tags: UnionTags,
// mut ext: TagExt,
// ) -> Vec<(TagName, Vec<Variable>)> {
// let mut all_tags = Vec::new();
// // Collapse (recursively) all the tags in ext_var into a flat list of tags.
// loop {
// for (tag, vars) in tags.iter_from_subs(subs) {
// all_tags.push((tag.clone(), vars.to_vec()));
// }
// match subs.get_content_without_compacting(ext.var()) {
// Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => {
// // Update tags and ext and loop back again to process them.
// tags = *new_tags;
// ext = *new_ext;
// }
// Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _symbols, new_ext)) => {
// for index in tag_names.into_iter() {
// all_tags.push((subs[index].clone(), Vec::new()));
// }
// ext = *new_ext;
// }
// Content::Structure(FlatType::EmptyTagUnion) => break,
// Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break,
// Content::Alias(_, _, real, _) => {
// // Follow the alias and process it on the next iteration of the loop.
// ext = TagExt::Any(*real);
// // We just processed these tags, so don't process them again!
// tags = UnionLabels::default();
// }
// _ => {
// // This should never happen! If it does, record a Problem and break.
// problems.push(Problem::TagUnionExtWasNotTagUnion);
// break;
// }
// }
// }
// all_tags
// }
// fn lower_record<P: Push<Problem>>(
// env: &mut Env<'_, '_, '_, '_, '_, '_, P>,
// subs: &Subs,
// mut fields: RecordFields,
// mut ext: Variable,
// ) -> Vec<(Lowercase, Option<MonoTypeId>)> {
// let mut labeled_mono_ids = Vec::with_capacity(fields.len());
// // Collapse (recursively) all the fields in ext into a flat list of fields.
// loop {
// // Add all the current fields to the answer.
// labeled_mono_ids.extend(
// fields
// .sorted_iterator(subs, ext)
// .map(|(label, field)| (label, self.lower_var( subs, *field.as_inner()))),
// );
// // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back.
// match subs.get_content_without_compacting(ext) {
// Content::Structure(FlatType::Record(new_fields, new_ext)) => {
// // Update fields and ext and loop back again to process them.
// fields = *new_fields;
// ext = *new_ext;
// }
// Content::Structure(FlatType::EmptyRecord)
// | Content::FlexVar(_)
// | Content::FlexAbleVar(_, _) => return labeled_mono_ids,
// Content::Alias(_, _, real, _) => {
// // Follow the alias and process it on the next iteration of the loop.
// ext = *real;
// // We just processed these fields, so don't process them again!
// fields = RecordFields::empty();
// }
// _ => {
// // This should never happen! If it does, record a Problem and early return.
// env.problems.push(Problem::RecordExtWasNotRecord);
// return labeled_mono_ids;
// }
// }
// }
// }
// fn resolve_tuple_ext(
// subs: &mut Subs,
// mono_types: &mut MonoTypes,
// problems: &mut impl Push<Problem>,
// mut elems: TupleElems,
// mut ext: Variable,
// ) -> Vec<(usize, Variable)> {
// let mut all_elems = Vec::new();
// // Collapse (recursively) all the elements in ext into a flat list of elements.
// loop {
// for (idx, var_index) in elems.iter_all() {
// all_elems.push((idx.index as usize, subs[var_index]));
// }
// match subs.get_content_without_compacting(ext) {
// Content::Structure(FlatType::Tuple(new_elems, new_ext)) => {
// // Update elems and ext and loop back again to process them.
// elems = *new_elems;
// ext = *new_ext;
// }
// Content::Structure(FlatType::EmptyTuple) => break,
// Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break,
// Content::Alias(_, _, real, _) => {
// // Follow the alias and process it on the next iteration of the loop.
// ext = *real;
// // We just processed these elements, so don't process them again!
// elems = TupleElems::empty();
// }
// _ => {
// // This should never happen! If it does, record a Problem and break.
// problems.push(Problem::TupleExtWasNotTuple);
// break;
// }
// }
// }
// all_elems
// }
// /// Lower the given vars in-place.
// fn lower_vars<'a>(
// vars: impl Iterator<Item = &'a mut Variable>,
// cache: &mut MonoCache,
// subs: &mut Subs,
// mono_types: &mut MonoTypes,
// problems: &mut impl Push<Problem>,
// ) {
// for var in vars {
// if let Some(var) = self.lower_var( *var) // hmm not sure if this is still a good idea as a helper function
// *var = ;
// }
// }

View file

@ -0,0 +1,282 @@
// TODO [mono2]: re-enable when ready
#![allow(dead_code)]
use bumpalo::Bump;
use core::fmt::Write;
use roc_load::LoadedModule;
use roc_region::all::Region;
use roc_solve::FunctionKind;
use roc_specialize_types::{
DebugInfo, Env, Interns, MonoExpr, MonoExprs, MonoTypeCache, MonoTypes, RecordFieldIds,
TupleElemIds,
};
use test_compile::{trim_and_deindent, SpecializedExprOut};
use test_solve_helpers::{format_problems, run_load_and_infer};
fn specialize_expr<'a>(
arena: &'a Bump,
src: &str,
string_interns: &mut Interns<'a>,
) -> SpecializedExprOut {
let (
LoadedModule {
module_id: home,
mut declarations_by_id,
mut can_problems,
mut type_problems,
interns,
mut solved,
mut exposed_to_host,
abilities_store,
..
},
src,
) = run_load_and_infer(
trim_and_deindent(arena, src),
[],
false,
FunctionKind::LambdaSet,
)
.unwrap();
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
// Disregard UnusedDef problems, because those are unavoidable when
// returning a function from the test expression.
can_problems.retain(|prob| {
!matches!(
prob,
roc_problem::can::Problem::UnusedDef(_, _)
| roc_problem::can::Problem::UnusedBranchDef(..)
)
});
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
assert_eq!(can_problems, String::new());
assert_eq!(type_problems, String::new());
exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s));
let mut problems = Vec::new();
let mut debug_info: Option<DebugInfo> = None;
let mut types_cache = MonoTypeCache::from_solved_subs(&solved);
let mut mono_types = MonoTypes::new();
let mut mono_exprs = MonoExprs::new();
let mut env = Env::new(
arena,
&mut solved,
&mut types_cache,
&mut mono_types,
&mut mono_exprs,
RecordFieldIds::default(),
TupleElemIds::default(),
string_interns,
&mut debug_info,
&mut problems,
);
let mut home_decls = declarations_by_id.remove(&home).unwrap();
let main_expr = home_decls.expressions.pop().unwrap().value;
// This should be our only expr
assert_eq!(0, home_decls.expressions.len());
let region = Region::zero();
let mono_expr_id = env
.to_mono_expr(&main_expr)
.map(|mono_expr| mono_exprs.add(mono_expr, region));
SpecializedExprOut {
mono_expr_id,
problems,
mono_types,
mono_exprs,
region,
}
}
#[track_caller]
pub fn expect_no_expr(input: impl AsRef<str>) {
let arena = Bump::new();
let mut interns = Interns::new();
let out = specialize_expr(&arena, input.as_ref(), &mut interns);
let actual = out.mono_expr_id.map(|id| out.mono_exprs.get_expr(id));
assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref());
}
#[track_caller]
pub fn expect_mono_expr(input: impl AsRef<str>, mono_expr: MonoExpr) {
expect_mono_expr_with_interns(input, |_, _| mono_expr);
}
#[track_caller]
pub fn expect_mono_expr_str(input: impl AsRef<str>, expr_str: impl AsRef<str>) {
expect_mono_expr_custom(
input,
|_, _, _| expr_str.as_ref().to_string(),
|arena, mono_exprs, interns, expr| {
dbg_mono_expr(arena, mono_exprs, interns, expr).to_string()
},
);
}
fn dbg_mono_expr<'a>(
arena: &'a Bump,
mono_exprs: &MonoExprs,
interns: &Interns<'a>,
expr: &MonoExpr,
) -> &'a str {
let mut buf = bumpalo::collections::String::new_in(arena);
dbg_mono_expr_help(arena, mono_exprs, interns, expr, &mut buf);
buf.into_bump_str()
}
fn dbg_mono_expr_help<'a>(
arena: &'a Bump,
mono_exprs: &MonoExprs,
interns: &Interns<'a>,
expr: &MonoExpr,
buf: &mut impl Write,
) {
match expr {
MonoExpr::Str(interned_str_id) => {
write!(buf, "Str({:?})", interns.get_str(arena, *interned_str_id)).unwrap();
}
MonoExpr::Number(number) => {
write!(buf, "Number({:?})", number).unwrap();
}
MonoExpr::Struct(field_exprs) => {
write!(buf, "Struct([").unwrap();
for (index, expr) in mono_exprs.iter_slice(field_exprs.as_slice()).enumerate() {
if index > 0 {
write!(buf, ", ").unwrap();
}
dbg_mono_expr_help(arena, mono_exprs, interns, expr, buf);
}
write!(buf, "])").unwrap();
}
// MonoExpr::List { elem_type, elems } => todo!(),
// MonoExpr::Lookup(symbol, mono_type_id) => todo!(),
// MonoExpr::ParameterizedLookup {
// name,
// lookup_type,
// params_name,
// params_type,
// } => todo!(),
// MonoExpr::When {
// cond,
// cond_type,
// branch_type,
// branches,
// } => todo!(),
// MonoExpr::If {
// branch_type,
// branches,
// final_else,
// } => todo!(),
// MonoExpr::LetRec { defs, ending_expr } => todo!(),
// MonoExpr::LetNonRec { def, ending_expr } => todo!(),
// MonoExpr::Call {
// fn_type,
// fn_expr,
// args,
// closure_type,
// } => todo!(),
// MonoExpr::RunLowLevel { op, args, ret_type } => todo!(),
// MonoExpr::ForeignCall {
// foreign_symbol,
// args,
// ret_type,
// } => todo!(),
// MonoExpr::Lambda {
// fn_type,
// arguments,
// body,
// captured_symbols,
// recursive,
// } => todo!(),
// MonoExpr::Crash { msg, expr_type } => todo!(),
// MonoExpr::StructAccess {
// record_expr,
// record_type,
// field_type,
// field_id,
// } => todo!(),
// MonoExpr::RecordUpdate {
// record_type,
// record_name,
// updates,
// } => todo!(),
// MonoExpr::SmallTag {
// discriminant,
// tag_union_type,
// args,
// } => todo!(),
// MonoExpr::BigTag {
// discriminant,
// tag_union_type,
// args,
// } => todo!(),
// MonoExpr::Expect {
// condition,
// continuation,
// lookups_in_cond,
// } => todo!(),
// MonoExpr::Dbg {
// source_location,
// source,
// msg,
// continuation,
// expr_type,
// name,
// } => todo!(),
MonoExpr::CompilerBug(problem) => {
write!(buf, "CompilerBug({:?})", problem).unwrap();
}
other => {
todo!("Implement dbg_mono_expr for {:?}", other)
}
}
}
#[track_caller]
pub fn expect_mono_expr_with_interns(
input: impl AsRef<str>,
from_interns: impl for<'a> Fn(&'a Bump, &Interns<'a>) -> MonoExpr,
) {
expect_mono_expr_custom(
input,
|arena, _exprs, interns| from_interns(arena, interns),
|_, _, _, expr| *expr,
);
}
#[track_caller]
pub fn expect_mono_expr_custom<T: PartialEq + core::fmt::Debug>(
input: impl AsRef<str>,
to_expected: impl for<'a> Fn(&'a Bump, &MonoExprs, &Interns<'a>) -> T,
to_actual: impl for<'a> Fn(&'a Bump, &MonoExprs, &Interns<'a>, &MonoExpr) -> T,
) {
let arena = Bump::new();
let mut string_interns = Interns::new();
let out = specialize_expr(&arena, input.as_ref(), &mut string_interns);
let mono_expr_id = out
.mono_expr_id
.expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}");
let actual_expr = out.mono_exprs.get_expr(mono_expr_id); // Must run first, to populate string interns!
let actual = to_actual(&arena, &out.mono_exprs, &string_interns, actual_expr);
let expected = to_expected(&arena, &out.mono_exprs, &string_interns);
assert_eq!(expected, actual);
}

View file

@ -0,0 +1,173 @@
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
mod helpers;
#[cfg(test)]
mod specialize_primitives {
use roc_specialize_types::{MonoExpr, Number};
use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns};
#[test]
fn string_literal() {
let string = "foo";
let expected = format!("\"{string}\"");
expect_mono_expr_with_interns(expected, |arena, interns| {
MonoExpr::Str(interns.try_get_id(arena, string).unwrap())
});
}
#[test]
fn unbound_zero() {
let expected = 0;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I8(expected)),
);
}
#[test]
fn unbound_negative_i8() {
let expected = -42;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I8(expected)),
);
}
#[test]
fn unbound_positive_i8() {
let expected = 42;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I8(expected)),
);
}
#[test]
fn unbound_u8() {
let expected = 128;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::U8(expected)),
);
}
#[test]
fn unbound_negative_i16() {
let expected = -5_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I16(expected)),
);
}
#[test]
fn unbound_positive_i16() {
let expected = 5_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I16(expected)),
);
}
#[test]
fn unbound_u16() {
let expected = 65_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::U16(expected)),
);
}
#[test]
fn unbound_negative_i32() {
let expected = -2_000_000_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I32(expected)),
);
}
#[test]
fn unbound_positive_i32() {
let expected = 2_000_000_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I32(expected)),
);
}
#[test]
fn unbound_u32() {
let expected = 4_000_000_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::U32(expected)),
);
}
#[test]
fn unbound_negative_i64() {
let expected = -9_000_000_000_000_000_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I64(expected)),
);
}
#[test]
fn unbound_positive_i64() {
let expected = 9_000_000_000_000_000_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I64(expected)),
);
}
#[test]
fn unbound_u64() {
let expected = 18_000_000_000_000_000_000;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::U64(expected)),
);
}
#[test]
fn unbound_negative_i128() {
let expected = -170_141_183_460_469_231_731_687_303_715_884_105_728;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I128(expected)),
);
}
#[test]
fn unbound_positive_i128() {
let expected = 170_141_183_460_469_231_731_687_303_715_884_105_727;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::I128(expected)),
);
}
#[test]
fn unbound_u128() {
let expected = 340_282_366_920_938_463_463_374_607_431_768_211_455;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::U128(expected)),
);
}
#[test]
fn unbound_f64() {
let expected = std::f64::consts::PI;
expect_mono_expr(
format!("{expected}"),
MonoExpr::Number(Number::Dec(expected)),
);
}
}

View file

@ -0,0 +1,54 @@
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
mod helpers;
#[cfg(test)]
mod specialize_structs {
use roc_specialize_types::MonoExpr;
use crate::helpers::expect_mono_expr_str;
use super::helpers::{expect_mono_expr_with_interns, expect_no_expr};
#[test]
fn empty_record() {
expect_no_expr("{}");
}
#[test]
fn one_field_with_empty_record() {
expect_no_expr("{ discardedField: {} }");
}
#[test]
fn one_field_record_string_literal() {
let string = "foo";
let expected = format!("{{ discardedField: \"{string}\" }}");
expect_mono_expr_with_interns(expected, |arena, interns| {
MonoExpr::Str(interns.try_get_id(arena, string).unwrap())
});
}
#[test]
fn one_field_after_dropping_zero_sized() {
let string = "foo";
let expected =
format!("{{ discarded: {{}}, discardedToo: \"{string}\", alsoDiscarded: {{}} }}");
expect_mono_expr_with_interns(expected, |arena, interns| {
MonoExpr::Str(interns.try_get_id(arena, string).unwrap())
});
}
#[test]
fn two_fields() {
let one = 42;
let two = 50;
expect_mono_expr_str(
format!("{{ one: {one}, two: {two} }}"),
format!("Struct([Number(I8({one})), Number(I8({two}))])"),
);
}
}

View file

@ -439,6 +439,7 @@ fn check_derived_typechecks_and_golden(
derived_module: Default::default(),
module_params: None,
module_params_vars: imported_param_vars,
host_exposed_symbols: None,
#[cfg(debug_assertions)]
checkmate: None,

View file

@ -1607,7 +1607,7 @@ fn tail_call_elimination() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn int_negate() {
fn num_negate() {
assert_evals_to!("Num.neg 123i8", -123, i8);
assert_evals_to!("Num.neg Num.maxI8", -i8::MAX, i8);
assert_evals_to!("Num.neg (Num.minI8 + 1)", i8::MAX, i8);
@ -1623,6 +1623,26 @@ fn int_negate() {
assert_evals_to!("Num.neg 123", -123, i64);
assert_evals_to!("Num.neg Num.maxI64", -i64::MAX, i64);
assert_evals_to!("Num.neg (Num.minI64 + 1)", i64::MAX, i64);
assert_evals_to!("Num.neg 12.3f32", -12.3, f32);
assert_evals_to!("Num.neg 0.0f32", -0.0, f32);
assert_evals_to!("Num.neg Num.maxF32", -f32::MAX, f32);
assert_evals_to!("Num.neg Num.minF32", -f32::MIN, f32);
assert_evals_to!("Num.neg Num.infinityF32", -f32::INFINITY, f32);
// can't test equality for nan
assert_evals_to!("Num.isNaN (Num.neg Num.nanF32)", true, bool);
assert_evals_to!("Num.neg 12.3f64", -12.3, f64);
assert_evals_to!("Num.neg 0.0f64", -0.0, f64);
assert_evals_to!("Num.neg Num.maxF64", -f64::MAX, f64);
assert_evals_to!("Num.neg Num.minF64", -f64::MIN, f64);
assert_evals_to!("Num.neg Num.infinityF64", -f64::INFINITY, f64);
// can't test equality for nan
assert_evals_to!("Num.isNaN (Num.neg Num.nanF64)", true, bool);
assert_evals_to!("Num.neg 123dec", RocDec::from(-123), RocDec);
// 0 is signless, unlike f32/f64
assert_evals_to!("Num.neg 0dec", RocDec::from(0), RocDec);
}
#[test]

View file

@ -542,6 +542,7 @@ pub fn try_run_lib_function<T>(
#[allow(dead_code)]
// only used in tests
#[allow(dead_code)]
pub(crate) fn llvm_evals_to<T, U, F>(
src: &str,
expected: U,

View file

@ -4,7 +4,7 @@ To setup fuzzing you will need to install cargo-fuzz and run with rust nightly:
```sh
$ cargo install cargo-fuzz
$ cargo +nightly fuzz run -j<cores> <target> -- -dict=../parse/fuzz/dict.txt
$ cargo +nightly fuzz run -j<cores> <target> -- -dict=../dict.txt
```
The different targets can be found by running `cargo fuzz list`.

View file

@ -7,10 +7,10 @@ Defs(
@0-3,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-43,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-52,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-55,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [

View file

@ -10,12 +10,12 @@ SpaceAfter(
@53-104,
],
space_before: [
Slice { start: 0, length: 0 },
Slice { start: 0, length: 2 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 2 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice { start: 2, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 2, length: 0 },
],
spaces: [
Newline,

View file

@ -7,10 +7,10 @@ Defs(
@0-38,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-89,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -7,10 +7,10 @@ Defs(
@0-39,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-49,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-46,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-41,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -9,10 +9,10 @@ SpaceBefore(
@107-112,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

View file

@ -8,10 +8,10 @@ SpaceAfter(
@0-164,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [

View file

@ -7,10 +7,10 @@ Defs(
@0-3,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [

View file

@ -6,10 +6,10 @@ Defs {
@0-7,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 1 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
],
spaces: [
LineComment(

View file

@ -7,10 +7,10 @@ Defs(
@0-5,
],
space_before: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],

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