use super::super::super::plan_expr_with_expected_source_stop_type;
use super::super::invalid_case_shape;
use super::{CaseClause, OrderedCaseClauseInput};
use crate::plan::{
BoolExpr, Expr, ExprKind, Step, UtfCodepointExpr, UtfCodepointLocalId, ValueType,
};
use crate::planner::context::PlanContext;
use crate::planner::error::{InvalidCaseShapeReason, PlanError};
use ecow::EcoString;
use gleam_core::ast::{Pattern, TypedExpr};
use gleam_core::type_::Type;
use std::sync::Arc;
pub(super) fn plan(
type_: Arc<Type>,
subject: TypedExpr,
clauses: Vec<CaseClause>,
context: &mut PlanContext<'_>,
) -> Result<Expr, PlanError> {
let subject =
plan_expr_with_expected_source_stop_type(subject, ValueType::UtfCodepoint, context)?;
let return_shape = context.value_shape(type_.as_ref());
let ExprKind::UtfCodepoint(subject) = subject.into_kind() else {
return Err(invalid_case_shape(
InvalidCaseShapeReason::PatternTypeMismatch,
));
};
let (subject_step, subject) = bind_case_subject(subject, context);
let mut ordered_clauses = Vec::new();
for clause in clauses {
for pattern in clause.patterns() {
let (pattern, reachable, exhaustive_remainder) = pattern.into_parts();
let pattern = plan_case_pattern(pattern)?;
let bindings = super::branch_bindings(pattern.bound_names(), subject.clone());
let is_total = clause.guard.is_none();
ordered_clauses.push(super::plan_ordered_case_clause(
OrderedCaseClauseInput {
case_type: type_.as_ref(),
return_shape: &return_shape,
then: clause.then.clone(),
branch_bindings: bindings,
guard: clause.guard.clone(),
match_condition: BoolExpr::value(true),
is_total,
reachable,
exhaustive_remainder,
},
context,
)?);
}
}
super::ordered_case_expr(ordered_clauses)
.map(|case| super::case_subject_block(subject_step, case))
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct UtfCodepointCasePattern {
bound_names: Vec<EcoString>,
}
impl UtfCodepointCasePattern {
fn bound_names(&self) -> &[EcoString] {
&self.bound_names
}
fn add_bound_name(&mut self, name: EcoString) {
self.bound_names.push(name);
}
}
fn plan_case_pattern(pattern: Pattern<Arc<Type>>) -> Result<UtfCodepointCasePattern, PlanError> {
match pattern {
Pattern::Variable { name, type_, .. }
if ValueType::from_gleam(type_.as_ref()) == Some(ValueType::UtfCodepoint) =>
{
Ok(UtfCodepointCasePattern {
bound_names: vec![name],
})
}
Pattern::Discard { type_, .. }
if ValueType::from_gleam(type_.as_ref()) == Some(ValueType::UtfCodepoint) =>
{
Ok(UtfCodepointCasePattern {
bound_names: Vec::new(),
})
}
Pattern::Assign { name, pattern, .. } => {
let mut pattern = plan_case_pattern(*pattern)?;
pattern.add_bound_name(name);
Ok(pattern)
}
Pattern::Invalid { .. } => Err(invalid_case_shape(InvalidCaseShapeReason::InvalidPattern)),
Pattern::Variable { .. }
| Pattern::Discard { .. }
| Pattern::Int { .. }
| Pattern::Float { .. }
| Pattern::String { .. }
| Pattern::BitArraySize(_)
| Pattern::List { .. }
| Pattern::Constructor { .. }
| Pattern::Tuple { .. }
| Pattern::BitArray { .. }
| Pattern::StringPrefix { .. } => Err(invalid_case_shape(
InvalidCaseShapeReason::PatternTypeMismatch,
)),
}
}
fn bind_case_subject(subject: UtfCodepointExpr, context: &mut PlanContext<'_>) -> (Step, Expr) {
let local = context.define_internal_utf_codepoint_local();
let name = internal_case_subject_name(local);
(
Step::let_utf_codepoint(local, name.clone(), subject),
Expr::utf_codepoint(UtfCodepointExpr::local_get(local, name)),
)
}
fn internal_case_subject_name(local: UtfCodepointLocalId) -> EcoString {
format!("<case:utf_codepoint:{}>", local.0).into()
}
#[cfg(test)]
mod tests {
use crate::planner::dsl::{
function, int, let_utf_codepoint_step, local_utf_codepoint, module,
utf_codepoint_return_block, utf_codepoint_return_expr,
};
use crate::planner::plan_module;
use crate::planner::support::dummy_span;
use crate::planner::{InvalidCaseShapeReason, InvalidTypedAstReason, PlanError};
use gleam_core::type_::error::VariableOrigin;
#[test]
fn plan_utf_codepoint_subject_binds_internal_subject_once() {
let actual = plan_module(crate::planner::support::compile(
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
case value {
bound -> bound
}
}
pub fn main() {
0
}
"#,
))
.expect("source should plan");
let expected = module(
"main",
function("main", int(0)),
[function(
"identity",
utf_codepoint_return_block(
[let_utf_codepoint_step(
1,
"<case:utf_codepoint:1>",
local_utf_codepoint(0, "value"),
)],
utf_codepoint_return_block(
[let_utf_codepoint_step(
2,
"bound",
local_utf_codepoint(1, "<case:utf_codepoint:1>"),
)],
utf_codepoint_return_expr(local_utf_codepoint(2, "bound")),
),
),
)
.param_utf_codepoint(0, "value")],
);
assert_eq!(actual, expected);
}
#[test]
fn plan_utf_codepoint_case_pattern_preserves_binding_order() {
let type_ = utf_codepoint_type();
let pattern = gleam_core::ast::Pattern::Assign {
location: dummy_span(),
name: "alias".into(),
pattern: Box::new(gleam_core::ast::Pattern::Variable {
location: dummy_span(),
name: "value".into(),
type_,
origin: VariableOrigin::generated(),
}),
};
let planned = super::plan_case_pattern(pattern).expect("pattern should plan");
assert_eq!(planned.bound_names(), &["value", "alias"]);
}
#[test]
fn reject_margin_utf_codepoint_case_pattern_shapes() {
let type_ = utf_codepoint_type();
assert_eq!(
super::plan_case_pattern(gleam_core::ast::Pattern::Discard {
location: dummy_span(),
name: "_".into(),
type_: type_.clone(),
})
.expect("discard should plan")
.bound_names(),
Vec::<ecow::EcoString>::new().as_slice(),
);
assert_eq!(
super::plan_case_pattern(gleam_core::ast::Pattern::Variable {
location: dummy_span(),
name: "value".into(),
type_: gleam_core::type_::int(),
origin: VariableOrigin::generated(),
}),
Err(pattern_type_mismatch()),
);
assert_eq!(
super::plan_case_pattern(gleam_core::ast::Pattern::Discard {
location: dummy_span(),
name: "_".into(),
type_: gleam_core::type_::int(),
}),
Err(pattern_type_mismatch()),
);
assert_eq!(
super::plan_case_pattern(gleam_core::ast::Pattern::Assign {
location: dummy_span(),
name: "alias".into(),
pattern: Box::new(gleam_core::ast::Pattern::Int {
location: dummy_span(),
value: "1".into(),
int_value: 1.into(),
}),
}),
Err(pattern_type_mismatch()),
);
assert_eq!(
super::plan_case_pattern(gleam_core::ast::Pattern::Invalid {
location: dummy_span(),
type_,
}),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::CaseShape {
reason: InvalidCaseShapeReason::InvalidPattern,
},
}),
);
assert_eq!(
super::plan_case_pattern(gleam_core::ast::Pattern::Tuple {
location: dummy_span(),
elements: Vec::new(),
}),
Err(pattern_type_mismatch()),
);
}
#[test]
fn reject_margin_utf_codepoint_subject_expression_family_mismatch() {
let mut module = crate::planner::support::compile(
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
case value {
bound -> bound
}
}
pub fn main() {
0
}
"#,
);
let (_, subjects, _) = super::super::super::expect_case_statement_mut(
&mut module.definitions.functions[0].body[0],
);
subjects[0] = gleam_core::ast::TypedExpr::Int {
location: dummy_span(),
type_: utf_codepoint_type(),
value: "1".into(),
int_value: 1.into(),
};
assert_eq!(plan_module(module), Err(pattern_type_mismatch()));
}
#[test]
fn reject_profile_utf_codepoint_case_subject_and_branch_errors_propagate() {
for source in [
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
case { <<1:native>> value } { bound -> bound }
}
pub fn main() { 0 }
"#,
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
case value { bound -> { <<1:native>> bound } }
}
pub fn main() { 0 }
"#,
] {
assert_eq!(
plan_module(crate::planner::support::compile(source)),
Err(PlanError::UnsupportedBitArraySegment {
reason: crate::planner::UnsupportedBitArraySegmentReason::NativeEndianness,
}),
);
}
}
#[test]
fn reject_margin_utf_codepoint_case_return_type_and_nested_pattern_errors_propagate() {
let mut invalid_return_type = crate::planner::support::compile(
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
case value { bound -> bound }
}
pub fn main() { 0 }
"#,
);
let (type_, _, _) = super::super::super::expect_case_statement_mut(
&mut invalid_return_type.definitions.functions[0].body[0],
);
*type_ = super::super::mismatched_generic_case_return_type();
assert_eq!(
plan_module(invalid_return_type),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::CaseShape {
reason: InvalidCaseShapeReason::BranchReturnTypeMismatch,
},
}),
);
let mut module = crate::planner::support::compile(
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
case value { bound -> bound }
}
pub fn main() { 0 }
"#,
);
let (_, _, clauses) = super::super::super::expect_case_statement_mut(
&mut module.definitions.functions[0].body[0],
);
clauses[0].pattern[0] = gleam_core::ast::Pattern::Invalid {
location: dummy_span(),
type_: utf_codepoint_type(),
};
assert_eq!(
plan_module(module),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::CaseShape {
reason: InvalidCaseShapeReason::InvalidPattern,
},
}),
);
}
fn utf_codepoint_type() -> std::sync::Arc<gleam_core::type_::Type> {
let module = crate::planner::support::compile(
r#"
fn identity(value: UtfCodepoint) -> UtfCodepoint {
value
}
pub fn main() {
0
}
"#,
);
module.definitions.functions[0].arguments[0].type_.clone()
}
fn pattern_type_mismatch() -> PlanError {
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::CaseShape {
reason: InvalidCaseShapeReason::PatternTypeMismatch,
},
}
}
}