diff --git a/Cargo.lock b/Cargo.lock index d473e23664..667567fe6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3304,6 +3304,7 @@ dependencies = [ "pretty_assertions", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_parse", "roc_problem", @@ -3572,6 +3573,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", + "roc_error_macros", "roc_module", "roc_mono", "roc_parse", diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 65bbd42df3..a380ae6f7d 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -13,6 +13,7 @@ // use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; // use crate::procedure::References; use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; +use roc_error_macros::todo_opaques; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_parse::ast::{self, TypeHeader}; @@ -260,6 +261,8 @@ fn to_pending_def<'a>( } } + Opaque { .. } => todo_opaques!(), + Expect(_) => todo!(), SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { diff --git a/cli/src/format.rs b/cli/src/format.rs index bd019380a0..8212593c7c 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -9,8 +9,8 @@ use roc_fmt::module::fmt_module; use roc_fmt::Buf; use roc_module::called_via::{BinOp, UnaryOp}; use roc_parse::ast::{ - TypeHeader, AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag, - TypeAnnotation, WhenBranch, + AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, + TypeHeader, WhenBranch, }; use roc_parse::header::{ AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, @@ -411,6 +411,16 @@ impl<'a> RemoveSpaces<'a> for Def<'a> { }, ann: ann.remove_spaces(arena), }, + Def::Opaque { + header: TypeHeader { name, vars }, + typ, + } => Def::Opaque { + header: TypeHeader { + name: name.remove_spaces(arena), + vars: vars.remove_spaces(arena), + }, + typ: typ.remove_spaces(arena), + }, Def::Body(a, b) => Def::Body( arena.alloc(a.remove_spaces(arena)), arena.alloc(b.remove_spaces(arena)), diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 2e8bd639dd..0a83a9fc0c 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } +roc_error_macros = { path = "../../error_macros" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 412f519490..132ac9a90b 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -12,6 +12,7 @@ use crate::procedure::References; use crate::scope::create_alias; use crate::scope::Scope; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_error_macros::todo_opaques; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_parse::ast; @@ -1504,6 +1505,8 @@ fn to_pending_def<'a>( } } + Opaque { .. } => todo_opaques!(), + Expect(_condition) => todo!(), SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 04255f9175..1217bc99c5 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -95,6 +95,7 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)), SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def), alias @ Alias { .. } => *alias, + opaque @ Opaque { .. } => *opaque, ann @ Annotation(_, _) => *ann, AnnotatedBody { ann_pattern, @@ -415,7 +416,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { Or => (ModuleName::BOOL, "or"), Pizza => unreachable!("Cannot desugar the |> operator"), Assignment => unreachable!("Cannot desugar the = operator"), - HasType => unreachable!("Cannot desugar the : operator"), + IsAliasType => unreachable!("Cannot desugar the : operator"), + IsOpaqueType => unreachable!("Cannot desugar the := operator"), Backpassing => unreachable!("Cannot desugar the <- operator"), } } diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index e2d3673816..935e6c6e35 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::pattern::fmt_pattern; use crate::spaces::{fmt_spaces, INDENT}; use crate::Buf; -use roc_parse::ast::{TypeHeader, Def, Expr, Pattern}; +use roc_parse::ast::{Def, Expr, Pattern, TypeHeader}; use roc_region::all::Loc; /// A Located formattable value is also formattable @@ -12,6 +12,7 @@ impl<'a> Formattable for Def<'a> { match self { Alias { ann, .. } => ann.is_multiline(), + Opaque { typ, .. } => typ.is_multiline(), Annotation(loc_pattern, loc_annotation) => { loc_pattern.is_multiline() || loc_annotation.is_multiline() } @@ -60,6 +61,10 @@ impl<'a> Formattable for Def<'a> { Alias { header: TypeHeader { name, vars }, ann, + } + | Opaque { + header: TypeHeader { name, vars }, + typ: ann, } => { buf.indent(indent); buf.push_str(name.value); @@ -69,7 +74,11 @@ impl<'a> Formattable for Def<'a> { fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); } - buf.push_str(" :"); + buf.push_str(match self { + Alias { .. } => " :", + Opaque { .. } => " :=", + _ => unreachable!(), + }); buf.spaces(1); ann.format(buf, indent + INDENT) diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 8487c19761..c70525b94f 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -347,7 +347,8 @@ fn push_op(buf: &mut Buf, op: BinOp) { called_via::BinOp::Or => buf.push_str("||"), called_via::BinOp::Pizza => buf.push_str("|>"), called_via::BinOp::Assignment => unreachable!(), - called_via::BinOp::HasType => unreachable!(), + called_via::BinOp::IsAliasType => unreachable!(), + called_via::BinOp::IsOpaqueType => unreachable!(), called_via::BinOp::Backpassing => unreachable!(), } } @@ -1067,7 +1068,11 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { | BinOp::GreaterThanOrEq | BinOp::And | BinOp::Or => true, - BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false, + BinOp::Pizza + | BinOp::Assignment + | BinOp::IsAliasType + | BinOp::IsOpaqueType + | BinOp::Backpassing => false, }) } Expr::If(_, _) => true, diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 57f4ffd530..c4092784f5 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } +roc_error_macros = { path = "../../error_macros" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index eb6d0dfa00..89cd049616 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -4,6 +4,7 @@ use crate::docs::TypeAnnotation::{ }; use crate::file::LoadedModule; use roc_can::scope::Scope; +use roc_error_macros::todo_opaques; use roc_module::ident::ModuleName; use roc_module::symbol::IdentIds; use roc_parse::ast::CommentOrNewline; @@ -228,6 +229,8 @@ fn generate_entry_doc<'a>( (acc, None) } + Def::Opaque { .. } => todo_opaques!("figure out documentation for opaques"), + Def::Body(_, _) => (acc, None), Def::Expect(c) => todo!("documentation for tests {:?}", c), diff --git a/compiler/module/src/called_via.rs b/compiler/module/src/called_via.rs index 62d1ecbe3f..c83245d3e9 100644 --- a/compiler/module/src/called_via.rs +++ b/compiler/module/src/called_via.rs @@ -47,7 +47,8 @@ pub enum BinOp { Or, Pizza, Assignment, - HasType, + IsAliasType, + IsOpaqueType, Backpassing, // lowest precedence } @@ -59,7 +60,7 @@ impl BinOp { Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1, DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or | Pizza => 2, - Assignment | HasType | Backpassing => unreachable!(), + Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(), } } } @@ -103,7 +104,7 @@ impl BinOp { Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => { NonAssociative } - Assignment | HasType | Backpassing => unreachable!(), + Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(), } } @@ -116,7 +117,7 @@ impl BinOp { And => 3, Or => 2, Pizza => 1, - Assignment | HasType | Backpassing => unreachable!(), + Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(), } } } @@ -154,7 +155,8 @@ impl std::fmt::Display for BinOp { Or => "||", Pizza => "|>", Assignment => "=", - HasType => ":", + IsAliasType => ":", + IsOpaqueType => ":=", Backpassing => "<-", }; diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 05a41ecb6a..627ebcf2fc 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -257,6 +257,12 @@ pub enum Def<'a> { ann: Loc>, }, + /// An opaque type, wrapping its inner type. E.g. Age := U64. + Opaque { + header: TypeHeader<'a>, + typ: Loc>, + }, + // TODO in canonicalization, check to see if there are any newlines after the // annotation; if not, and if it's followed by a Body, then the annotation // applies to that expr! (TODO: verify that the pattern for both annotation and body match.) diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 98fe575d01..a3345950fe 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1,6 +1,6 @@ use crate::ast::{ - TypeHeader, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, - Spaceable, TypeAnnotation, + AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, Spaceable, + TypeAnnotation, TypeHeader, }; use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e}; use crate::ident::{lowercase_ident, parse_ident, Ident}; @@ -422,7 +422,7 @@ impl<'a> ExprState<'a> { arena: &'a Bump, loc_op: Loc, ) -> Result<(Loc>, Vec<'a, &'a Loc>>), EExpr<'a>> { - debug_assert_eq!(loc_op.value, BinOp::HasType); + debug_assert_eq!(loc_op.value, BinOp::IsAliasType); if !self.operators.is_empty() { // this `:` likely occurred inline; treat it as an invalid operator @@ -853,7 +853,7 @@ fn parse_defs_end<'a>( parse_defs_end(options, column, def_state, arena, state) } - Ok((_, BinOp::HasType, state)) => { + Ok((_, BinOp::IsAliasType, state)) => { let (_, ann_type, state) = specialize( EExpr::Type, space0_before_e( @@ -919,6 +919,127 @@ fn parse_defs_expr<'a>( } } +#[derive(Copy, Clone, PartialEq, Eq)] +enum TypeKind { + Alias, + Opaque, +} + +fn finish_parsing_alias_or_opaque<'a>( + min_indent: u32, + options: ExprParseOptions, + start_column: u32, + expr_state: ExprState<'a>, + loc_op: Loc, + arena: &'a Bump, + state: State<'a>, + spaces_after_operator: &'a [CommentOrNewline<'a>], + kind: TypeKind, +) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { + let expr_region = expr_state.expr.region; + let indented_more = start_column + 1; + + let (expr, arguments) = expr_state + .validate_has_type(arena, loc_op) + .map_err(|fail| (MadeProgress, fail, state.clone()))?; + + let (loc_def, state) = match &expr.value { + Expr::GlobalTag(name) => { + let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena); + + for argument in arguments { + match expr_to_pattern_help(arena, &argument.value) { + Ok(good) => { + type_arguments.push(Loc::at(argument.region, good)); + } + Err(_) => panic!(), + } + } + + let (_, ann_type, state) = specialize( + EExpr::Type, + space0_before_e( + type_annotation::located_help(indented_more, true), + min_indent, + EType::TIndentStart, + ), + ) + .parse(arena, state)?; + + let def_region = Region::span_across(&expr.region, &ann_type.region); + + let header = TypeHeader { + name: Loc::at(expr.region, name), + vars: type_arguments.into_bump_slice(), + }; + let type_def = match kind { + TypeKind::Alias => Def::Alias { + header, + ann: ann_type, + }, + TypeKind::Opaque => Def::Opaque { + header, + typ: ann_type, + }, + }; + + (&*arena.alloc(Loc::at(def_region, type_def)), state) + } + + _ => { + let call = to_call(arena, arguments, expr); + + match expr_to_pattern_help(arena, &call.value) { + Ok(good) => { + let parser = specialize( + EExpr::Type, + space0_before_e( + type_annotation::located_help(indented_more, false), + min_indent, + EType::TIndentStart, + ), + ); + + match parser.parse(arena, state) { + Err((_, fail, state)) => return Err((MadeProgress, fail, state)), + Ok((_, mut ann_type, state)) => { + // put the spaces from after the operator in front of the call + if !spaces_after_operator.is_empty() { + ann_type = arena + .alloc(ann_type.value) + .with_spaces_before(spaces_after_operator, ann_type.region); + } + + let def_region = Region::span_across(&call.region, &ann_type.region); + + let alias = Def::Annotation(Loc::at(expr_region, good), ann_type); + + (&*arena.alloc(Loc::at(def_region, alias)), state) + } + } + } + Err(_) => { + // this `:`/`:=` likely occurred inline; treat it as an invalid operator + let op = match kind { + TypeKind::Alias => ":", + TypeKind::Opaque => ":=", + }; + let fail = EExpr::BadOperator(op, loc_op.region.start()); + + return Err((MadeProgress, fail, state)); + } + } + } + }; + + let def_state = DefState { + defs: bumpalo::vec![in arena; loc_def], + spaces_after: &[], + }; + + parse_defs_expr(options, start_column, def_state, arena, state) +} + fn parse_expr_operator<'a>( min_indent: u32, options: ExprParseOptions, @@ -1059,102 +1180,21 @@ fn parse_expr_operator<'a>( Ok((MadeProgress, ret, state)) } - BinOp::HasType => { - let expr_region = expr_state.expr.region; - let indented_more = start_column + 1; - - let (expr, arguments) = expr_state - .validate_has_type(arena, loc_op) - .map_err(|fail| (MadeProgress, fail, state.clone()))?; - - let (loc_def, state) = match &expr.value { - Expr::GlobalTag(name) => { - let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena); - - for argument in arguments { - match expr_to_pattern_help(arena, &argument.value) { - Ok(good) => { - type_arguments.push(Loc::at(argument.region, good)); - } - Err(_) => panic!(), - } - } - - let (_, ann_type, state) = specialize( - EExpr::Type, - space0_before_e( - type_annotation::located_help(indented_more, true), - min_indent, - EType::TIndentStart, - ), - ) - .parse(arena, state)?; - - let alias_region = Region::span_across(&expr.region, &ann_type.region); - - let alias = Def::Alias { - header: TypeHeader { - name: Loc::at(expr.region, name), - vars: type_arguments.into_bump_slice(), - }, - ann: ann_type, - }; - - (&*arena.alloc(Loc::at(alias_region, alias)), state) - } - - _ => { - let call = to_call(arena, arguments, expr); - - match expr_to_pattern_help(arena, &call.value) { - Ok(good) => { - let parser = specialize( - EExpr::Type, - space0_before_e( - type_annotation::located_help(indented_more, false), - min_indent, - EType::TIndentStart, - ), - ); - - match parser.parse(arena, state) { - Err((_, fail, state)) => return Err((MadeProgress, fail, state)), - Ok((_, mut ann_type, state)) => { - // put the spaces from after the operator in front of the call - if !spaces_after_operator.is_empty() { - ann_type = arena.alloc(ann_type.value).with_spaces_before( - spaces_after_operator, - ann_type.region, - ); - } - - let alias_region = - Region::span_across(&call.region, &ann_type.region); - - let alias = - Def::Annotation(Loc::at(expr_region, good), ann_type); - - (&*arena.alloc(Loc::at(alias_region, alias)), state) - } - } - } - Err(_) => { - // this `:` likely occurred inline; treat it as an invalid operator - let fail = EExpr::BadOperator(":", loc_op.region.start()); - - return Err((MadeProgress, fail, state)); - } - } - } - }; - - let def_state = DefState { - defs: bumpalo::vec![in arena; loc_def], - spaces_after: &[], - }; - - parse_defs_expr(options, start_column, def_state, arena, state) - } + BinOp::IsAliasType | BinOp::IsOpaqueType => finish_parsing_alias_or_opaque( + min_indent, + options, + start_column, + expr_state, + loc_op, + arena, + state, + spaces_after_operator, + match op { + BinOp::IsAliasType => TypeKind::Alias, + BinOp::IsOpaqueType => TypeKind::Opaque, + _ => unreachable!(), + }, + ), _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Ok((_, mut new_expr, state)) => { @@ -2372,7 +2412,8 @@ where Err((NoProgress, to_error(".", state.pos()), state)) } "=" => good!(BinOp::Assignment, 1), - ":" => good!(BinOp::HasType, 1), + ":=" => good!(BinOp::IsOpaqueType, 2), + ":" => good!(BinOp::IsAliasType, 1), "|>" => good!(BinOp::Pizza, 2), "==" => good!(BinOp::Equals, 2), "!=" => good!(BinOp::NotEquals, 2),