mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Issue 7089: ?? operator
This commit is contained in:
parent
57cab7f69a
commit
c70ceb4f98
7 changed files with 367 additions and 16 deletions
|
@ -6,7 +6,7 @@ use crate::suffixed::{apply_try_function, unwrap_suffixed_expression, EUnwrapped
|
|||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::called_via::BinOp::Pizza;
|
||||
use roc_module::called_via::BinOp::{DoubleQuestion, Pizza};
|
||||
use roc_module::called_via::{BinOp, CalledVia};
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
|
@ -154,6 +154,61 @@ fn new_op_call_expr<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
DoubleQuestion => {
|
||||
let left = desugar_expr(env, scope, left);
|
||||
let right = desugar_expr(env, scope, right);
|
||||
|
||||
let mut branches = Vec::with_capacity_in(2, env.arena);
|
||||
let mut branch_1_patts = Vec::with_capacity_in(1, env.arena);
|
||||
let mut branch_1_patts_args = Vec::with_capacity_in(1, env.arena);
|
||||
let success_var = env.arena.alloc_str(
|
||||
format!(
|
||||
"success_BRANCH1_{}_{}",
|
||||
left.region.start().offset,
|
||||
left.region.end().offset
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
branch_1_patts_args.push(Loc::at(
|
||||
left.region,
|
||||
Pattern::Identifier { ident: success_var },
|
||||
));
|
||||
let branch_1_tag: &Loc<Pattern<'a>> =
|
||||
env.arena.alloc(Loc::at(left.region, Pattern::Tag("Ok")));
|
||||
branch_1_patts.push(Loc::at(
|
||||
left.region,
|
||||
Pattern::Apply(branch_1_tag, branch_1_patts_args.into_bump_slice()),
|
||||
));
|
||||
let branch_one: &WhenBranch<'_> = env.arena.alloc(WhenBranch {
|
||||
patterns: branch_1_patts.into_bump_slice(),
|
||||
value: Loc::at(
|
||||
left.region,
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: success_var,
|
||||
},
|
||||
),
|
||||
guard: None,
|
||||
});
|
||||
branches.push(branch_one);
|
||||
let mut branch_2_patts = Vec::with_capacity_in(1, env.arena);
|
||||
let mut branch_2_patts_args = Vec::with_capacity_in(1, env.arena);
|
||||
branch_2_patts_args.push(Loc::at(right.region, Pattern::Underscore("")));
|
||||
let branch_2_tag: &Loc<Pattern<'a>> =
|
||||
env.arena.alloc(Loc::at(left.region, Pattern::Tag("Err")));
|
||||
branch_2_patts.push(Loc::at(
|
||||
right.region,
|
||||
Pattern::Apply(branch_2_tag, branch_2_patts_args.into_bump_slice()),
|
||||
));
|
||||
let branch_two: &WhenBranch<'_> = env.arena.alloc(WhenBranch {
|
||||
patterns: branch_2_patts.into_bump_slice(),
|
||||
value: *right,
|
||||
guard: None,
|
||||
});
|
||||
branches.push(branch_two);
|
||||
|
||||
When(left, branches.into_bump_slice())
|
||||
}
|
||||
binop => {
|
||||
let left = desugar_expr(env, scope, left);
|
||||
let right = desugar_expr(env, scope, right);
|
||||
|
@ -1601,6 +1656,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
|
|||
And => (ModuleName::BOOL, "and"),
|
||||
Or => (ModuleName::BOOL, "or"),
|
||||
Pizza => unreachable!("Cannot desugar the |> operator"),
|
||||
DoubleQuestion => unreachable!("Cannot desugar the ?? operator"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
---
|
||||
source: crates/compiler/can/tests/test_suffixed.rs
|
||||
expression: snapshot
|
||||
---
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@0-85,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 1 },
|
||||
],
|
||||
spaces: [
|
||||
Newline,
|
||||
],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
Body(
|
||||
@0-4 Identifier {
|
||||
ident: "main",
|
||||
},
|
||||
@11-69 Apply(
|
||||
@11-69 Var {
|
||||
module_name: "Task",
|
||||
ident: "await",
|
||||
},
|
||||
[
|
||||
@11-69 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@11-69,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
AnnotatedBody {
|
||||
ann_pattern: @11-69 Identifier {
|
||||
ident: "#!0_stmt",
|
||||
},
|
||||
ann_type: @11-69 Apply(
|
||||
"",
|
||||
"Task",
|
||||
[
|
||||
@11-69 Record {
|
||||
fields: [],
|
||||
ext: None,
|
||||
},
|
||||
@11-69 Inferred,
|
||||
],
|
||||
),
|
||||
lines_between: [],
|
||||
body_pattern: @11-69 Identifier {
|
||||
ident: "#!0_stmt",
|
||||
},
|
||||
body_expr: @11-69 Apply(
|
||||
@11-69 Var {
|
||||
module_name: "",
|
||||
ident: "line",
|
||||
},
|
||||
[
|
||||
@11-56 Apply(
|
||||
@47-56 Var {
|
||||
module_name: "Num",
|
||||
ident: "toStr",
|
||||
},
|
||||
[
|
||||
@11-39 Apply(
|
||||
@24-39 When(
|
||||
@24-32 Var {
|
||||
module_name: "Str",
|
||||
ident: "toU8",
|
||||
},
|
||||
[
|
||||
WhenBranch {
|
||||
patterns: [
|
||||
@24-32 Apply(
|
||||
@24-32 Tag(
|
||||
"Ok",
|
||||
),
|
||||
[
|
||||
@24-32 Identifier {
|
||||
ident: "success_BRANCH1_24_32",
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
value: @24-32 Var {
|
||||
module_name: "",
|
||||
ident: "success_BRANCH1_24_32",
|
||||
},
|
||||
guard: None,
|
||||
},
|
||||
WhenBranch {
|
||||
patterns: [
|
||||
@36-39 Apply(
|
||||
@24-32 Tag(
|
||||
"Err",
|
||||
),
|
||||
[
|
||||
@36-39 Underscore(
|
||||
"",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
value: @36-39 Num(
|
||||
"255",
|
||||
),
|
||||
guard: None,
|
||||
},
|
||||
],
|
||||
),
|
||||
[
|
||||
@11-16 Str(
|
||||
PlainLine(
|
||||
"123",
|
||||
),
|
||||
),
|
||||
],
|
||||
BinOp(
|
||||
Pizza,
|
||||
),
|
||||
),
|
||||
],
|
||||
BinOp(
|
||||
Pizza,
|
||||
),
|
||||
),
|
||||
],
|
||||
BinOp(
|
||||
Pizza,
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
@11-69 Var {
|
||||
module_name: "",
|
||||
ident: "#!0_stmt",
|
||||
},
|
||||
),
|
||||
@11-69 Closure(
|
||||
[
|
||||
@11-69 Underscore(
|
||||
"#!stmt",
|
||||
),
|
||||
],
|
||||
@75-85 Apply(
|
||||
@75-82 Var {
|
||||
module_name: "Task",
|
||||
ident: "ok",
|
||||
},
|
||||
[
|
||||
@83-85 Record(
|
||||
[],
|
||||
),
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
],
|
||||
BangSuffix,
|
||||
),
|
||||
),
|
||||
],
|
||||
}
|
|
@ -22,7 +22,7 @@ mod test_can {
|
|||
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
use roc_types::subs::Variable;
|
||||
use std::{f64, i64};
|
||||
use std::{assert_eq, f64, i64};
|
||||
|
||||
fn assert_can_runtime_error(input: &str, expected: RuntimeError) {
|
||||
let arena = Bump::new();
|
||||
|
@ -971,6 +971,47 @@ mod test_can {
|
|||
assert_str_value(&cond_args[0].1.value, "123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_desugar_double_question_suffix() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
Str.toU64 "123" ?? Num.maxU64
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let out = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(out.problems, Vec::new());
|
||||
|
||||
// Assert that we desugar to:
|
||||
//
|
||||
// when Str.toU64 "123"
|
||||
// Ok success_BRANCH1_0_9 -> success_BRANCH1_0_9
|
||||
// Err _ -> Num.maxU64
|
||||
|
||||
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
|
||||
let cond_args = assert_func_call(cond_expr, "toU64", CalledVia::Space, &out.interns);
|
||||
|
||||
assert_eq!(cond_args.len(), 1);
|
||||
assert_str_value(&cond_args[0].1.value, "123");
|
||||
assert_eq!(branches.len(), 2);
|
||||
assert_eq!(branches[0].patterns.len(), 1);
|
||||
assert_eq!(branches[1].patterns.len(), 1);
|
||||
assert_pattern_tag_apply_with_ident(
|
||||
&branches[0].patterns[0].pattern.value,
|
||||
"Ok",
|
||||
"success_BRANCH1_0_15",
|
||||
&out.interns,
|
||||
);
|
||||
assert_var_usage(
|
||||
&branches[0].value.value,
|
||||
"success_BRANCH1_0_15",
|
||||
&out.interns,
|
||||
);
|
||||
assert_pattern_tag_apply_with_underscore(&branches[1].patterns[0].pattern.value, "Err");
|
||||
assert_var_usage(&branches[1].value.value, "maxU64", &out.interns);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_desugar_works_elsewhere() {
|
||||
let src = indoc!(
|
||||
|
@ -1090,6 +1131,51 @@ mod test_can {
|
|||
}
|
||||
}
|
||||
|
||||
fn assert_pattern_tag_apply_with_ident(
|
||||
pattern: &Pattern,
|
||||
name: &str,
|
||||
ident: &str,
|
||||
interns: &roc_module::symbol::Interns,
|
||||
) {
|
||||
match pattern {
|
||||
Pattern::AppliedTag {
|
||||
tag_name,
|
||||
arguments,
|
||||
..
|
||||
} if arguments.len() == 1 => {
|
||||
assert_eq!(tag_name.as_ident_str().as_str(), name);
|
||||
match arguments[0].1.value {
|
||||
Pattern::Identifier(sym) => assert_eq!(sym.as_str(interns), ident),
|
||||
_ => panic!(
|
||||
"The tag was expected to be applied with {:?} but we instead found {:?}",
|
||||
ident, arguments[0].1.value
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => panic!("Pattern was not an applied tag: {:?}", pattern),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_pattern_tag_apply_with_underscore(pattern: &Pattern, name: &str) {
|
||||
match pattern {
|
||||
Pattern::AppliedTag {
|
||||
tag_name,
|
||||
arguments,
|
||||
..
|
||||
} if arguments.len() == 1 => {
|
||||
assert_eq!(tag_name.as_ident_str().as_str(), name);
|
||||
match arguments[0].1.value {
|
||||
Pattern::Underscore => {},
|
||||
_ => panic!(
|
||||
"The tag was expected to be applied with an underscore but we instead found {:?}",
|
||||
arguments[0].1.value
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => panic!("Pattern was not an applied tag: {:?}", pattern),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_when(expr: &Expr) -> (&Expr, &Vec<WhenBranch>) {
|
||||
match expr {
|
||||
Expr::When {
|
||||
|
|
|
@ -150,6 +150,28 @@ mod suffixed_tests {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of unwrapping a Result with ?? operator
|
||||
*
|
||||
* Note that ?? is desugared into a when expression,
|
||||
* however this also tests the parser.
|
||||
*
|
||||
*/
|
||||
#[test]
|
||||
fn simple_double_question() {
|
||||
run_test!(
|
||||
r#"
|
||||
main =
|
||||
"123"
|
||||
|> Str.toU8 ?? 255
|
||||
|> Num.toStr
|
||||
|> line!
|
||||
|
||||
Task.ok {}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example with a parens suffixed sub-expression
|
||||
* in the function part of an Apply.
|
||||
|
|
|
@ -941,6 +941,7 @@ fn push_op(buf: &mut Buf, op: BinOp) {
|
|||
called_via::BinOp::And => buf.push_str("&&"),
|
||||
called_via::BinOp::Or => buf.push_str("||"),
|
||||
called_via::BinOp::Pizza => buf.push_str("|>"),
|
||||
called_via::BinOp::DoubleQuestion => buf.push_str("??"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2223,7 +2224,8 @@ pub fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
|||
| BinOp::GreaterThanOrEq
|
||||
| BinOp::And
|
||||
| BinOp::Or
|
||||
| BinOp::Pizza => true,
|
||||
| BinOp::Pizza
|
||||
| BinOp::DoubleQuestion => true,
|
||||
})
|
||||
}
|
||||
Expr::If { .. } => true,
|
||||
|
|
|
@ -3,7 +3,7 @@ use self::BinOp::*;
|
|||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
const PRECEDENCES: [(BinOp, u8); 16] = [
|
||||
const PRECEDENCES: [(BinOp, u8); 17] = [
|
||||
(Caret, 8),
|
||||
(Star, 7),
|
||||
(Slash, 7),
|
||||
|
@ -11,6 +11,7 @@ const PRECEDENCES: [(BinOp, u8); 16] = [
|
|||
(Percent, 6),
|
||||
(Plus, 5),
|
||||
(Minus, 5),
|
||||
(DoubleQuestion, 5),
|
||||
(Pizza, 4),
|
||||
(Equals, 3),
|
||||
(NotEquals, 3),
|
||||
|
@ -22,7 +23,7 @@ const PRECEDENCES: [(BinOp, u8); 16] = [
|
|||
(Or, 0),
|
||||
];
|
||||
|
||||
const ASSOCIATIVITIES: [(BinOp, Associativity); 16] = [
|
||||
const ASSOCIATIVITIES: [(BinOp, Associativity); 17] = [
|
||||
(Caret, RightAssociative),
|
||||
(Star, LeftAssociative),
|
||||
(Slash, LeftAssociative),
|
||||
|
@ -30,6 +31,7 @@ const ASSOCIATIVITIES: [(BinOp, Associativity); 16] = [
|
|||
(Percent, LeftAssociative),
|
||||
(Plus, LeftAssociative),
|
||||
(Minus, LeftAssociative),
|
||||
(DoubleQuestion, LeftAssociative),
|
||||
(Pizza, LeftAssociative),
|
||||
(Equals, NonAssociative),
|
||||
(NotEquals, NonAssociative),
|
||||
|
@ -41,7 +43,7 @@ const ASSOCIATIVITIES: [(BinOp, Associativity); 16] = [
|
|||
(Or, RightAssociative),
|
||||
];
|
||||
|
||||
const DISPLAY_STRINGS: [(BinOp, &str); 16] = [
|
||||
const DISPLAY_STRINGS: [(BinOp, &str); 17] = [
|
||||
(Caret, "^"),
|
||||
(Star, "*"),
|
||||
(Slash, "/"),
|
||||
|
@ -49,6 +51,7 @@ const DISPLAY_STRINGS: [(BinOp, &str); 16] = [
|
|||
(Percent, "%"),
|
||||
(Plus, "+"),
|
||||
(Minus, "-"),
|
||||
(DoubleQuestion, "??"),
|
||||
(Pizza, "|>"),
|
||||
(Equals, "=="),
|
||||
(NotEquals, "!="),
|
||||
|
@ -152,6 +155,7 @@ pub enum BinOp {
|
|||
Slash,
|
||||
DoubleSlash,
|
||||
Percent,
|
||||
DoubleQuestion,
|
||||
Plus,
|
||||
Minus,
|
||||
Pizza,
|
||||
|
@ -172,7 +176,7 @@ impl BinOp {
|
|||
match self {
|
||||
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
|
||||
DoubleSlash | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or
|
||||
| Pizza => 2,
|
||||
| Pizza | DoubleQuestion => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,13 +210,13 @@ pub enum Associativity {
|
|||
|
||||
impl BinOp {
|
||||
pub fn associativity(self) -> Associativity {
|
||||
const ASSOCIATIVITY_TABLE: [Associativity; 16] = generate_associativity_table();
|
||||
const ASSOCIATIVITY_TABLE: [Associativity; 17] = generate_associativity_table();
|
||||
|
||||
ASSOCIATIVITY_TABLE[self as usize]
|
||||
}
|
||||
|
||||
fn precedence(self) -> u8 {
|
||||
const PRECEDENCE_TABLE: [u8; 16] = generate_precedence_table();
|
||||
const PRECEDENCE_TABLE: [u8; 17] = generate_precedence_table();
|
||||
|
||||
PRECEDENCE_TABLE[self as usize]
|
||||
}
|
||||
|
@ -232,14 +236,14 @@ impl Ord for BinOp {
|
|||
|
||||
impl std::fmt::Display for BinOp {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
const DISPLAY_TABLE: [&str; 16] = generate_display_table();
|
||||
const DISPLAY_TABLE: [&str; 17] = generate_display_table();
|
||||
|
||||
write!(f, "{}", DISPLAY_TABLE[*self as usize])
|
||||
}
|
||||
}
|
||||
|
||||
const fn generate_precedence_table() -> [u8; 16] {
|
||||
let mut table = [0u8; 16];
|
||||
const fn generate_precedence_table() -> [u8; 17] {
|
||||
let mut table = [0u8; 17];
|
||||
let mut i = 0;
|
||||
|
||||
while i < PRECEDENCES.len() {
|
||||
|
@ -250,8 +254,8 @@ const fn generate_precedence_table() -> [u8; 16] {
|
|||
table
|
||||
}
|
||||
|
||||
const fn generate_associativity_table() -> [Associativity; 16] {
|
||||
let mut table = [NonAssociative; 16];
|
||||
const fn generate_associativity_table() -> [Associativity; 17] {
|
||||
let mut table = [NonAssociative; 17];
|
||||
let mut i = 0;
|
||||
|
||||
while i < ASSOCIATIVITIES.len() {
|
||||
|
@ -262,8 +266,8 @@ const fn generate_associativity_table() -> [Associativity; 16] {
|
|||
table
|
||||
}
|
||||
|
||||
const fn generate_display_table() -> [&'static str; 16] {
|
||||
let mut table = [""; 16];
|
||||
const fn generate_display_table() -> [&'static str; 17] {
|
||||
let mut table = [""; 17];
|
||||
let mut i = 0;
|
||||
|
||||
while i < DISPLAY_STRINGS.len() {
|
||||
|
|
|
@ -4046,6 +4046,7 @@ where
|
|||
"=" => good!(OperatorOrDef::Assignment, 1),
|
||||
":=" => good!(OperatorOrDef::AliasOrOpaque(AliasOrOpaque::Opaque), 2),
|
||||
":" => good!(OperatorOrDef::AliasOrOpaque(AliasOrOpaque::Alias), 1),
|
||||
"??" => good!(OperatorOrDef::BinOp(BinOp::DoubleQuestion), 2),
|
||||
"|>" => good!(OperatorOrDef::BinOp(BinOp::Pizza), 2),
|
||||
"==" => good!(OperatorOrDef::BinOp(BinOp::Equals), 2),
|
||||
"!=" => good!(OperatorOrDef::BinOp(BinOp::NotEquals), 2),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue