use crate::plan::{
BitArrayBindingPattern, BitArrayLocalId, BitArrayPattern, BitArrayPatternSegment,
BitArrayPatternSize, BitArrayPatternSizeExpr, BitArrayPatternValue, BitArrayStringPattern,
Endianness, FloatLocalId, IntLocalId, LocalId, PatternBinding, Signedness, StringEncoding,
UtfCodepointLocalId, ValueType,
};
use crate::planner::bit_array::validate_supported_endianness_option;
use crate::planner::context::PlanContext;
use crate::planner::error::{
InvalidBitArraySegmentOptionsReason, InvalidPatternShapeReason, InvalidTypedAstReason,
PlanError,
};
use gleam_core::ast::{
BitArrayOption, BitArraySegment, BitArraySize, Constant, IntOperator, Pattern,
};
use gleam_core::strings::convert_string_escape_chars;
use gleam_core::type_::{Type, ValueConstructor, ValueConstructorVariant};
use std::sync::Arc;
pub(in crate::planner) fn plan_bit_array_pattern(
segments: Vec<BitArraySegment<Pattern<Arc<Type>>, Arc<Type>>>,
context: &mut PlanContext<'_>,
) -> Result<(BitArrayPattern, bool), PlanError> {
let is_total = is_total_pattern(&segments);
let mut planned = Vec::with_capacity(segments.len());
let segment_count = segments.len();
for (index, segment) in segments.into_iter().enumerate() {
let segment = plan_segment(segment, context)?;
validate_segment_position(index, segment_count, &segment)?;
planned.push(segment);
}
Ok((BitArrayPattern::new(planned), is_total))
}
fn plan_segment(
segment: BitArraySegment<Pattern<Arc<Type>>, Arc<Type>>,
context: &mut PlanContext<'_>,
) -> Result<BitArrayPatternSegment, PlanError> {
for option in &segment.options {
validate_supported_endianness_option(option)?;
}
let kind = validate_segment_options(&segment)?;
let endianness = segment_endianness(&segment);
let unit = segment.unit();
let explicit_size = segment
.size()
.map(|size| plan_segment_size(size, unit, context))
.transpose()?;
match kind {
SegmentKind::Int => Ok(BitArrayPatternSegment::Int {
pattern: plan_int_pattern(*segment.value, context)?,
size: explicit_size.unwrap_or_else(|| BitArrayPatternSize::new(size_value(8), 1)),
endianness,
signedness: if segment
.options
.iter()
.any(|option| matches!(option, BitArrayOption::Signed { .. }))
{
Signedness::Signed
} else {
Signedness::Unsigned
},
}),
SegmentKind::Float => Ok(BitArrayPatternSegment::Float {
pattern: plan_float_pattern(*segment.value, context)?,
size: explicit_size.unwrap_or_else(|| BitArrayPatternSize::new(size_value(64), 1)),
endianness,
}),
SegmentKind::Bits => Ok(BitArrayPatternSegment::Bits {
pattern: plan_bits_pattern(*segment.value, context)?,
size: explicit_size,
unit,
}),
SegmentKind::String(encoding) => Ok(BitArrayPatternSegment::String {
pattern: plan_string_pattern(*segment.value, context)?,
encoding,
}),
SegmentKind::UtfCodepoint(encoding) => Ok(BitArrayPatternSegment::UtfCodepoint {
pattern: plan_utf_codepoint_pattern(*segment.value, context)?,
encoding,
}),
}
}
#[derive(Clone, Copy)]
enum SegmentKind {
Int,
Float,
Bits,
String(StringEncoding),
UtfCodepoint(StringEncoding),
}
fn validate_segment_options(
segment: &BitArraySegment<Pattern<Arc<Type>>, Arc<Type>>,
) -> Result<SegmentKind, PlanError> {
let mut kind = None;
let mut kind_count = 0;
let mut signedness_count = 0;
let mut endianness_count = 0;
let mut size_count = 0;
let mut unit_count = 0;
let mut zero_unit = false;
for option in &segment.options {
match option {
BitArrayOption::Bytes { .. } | BitArrayOption::Bits { .. } => {
kind_count += 1;
kind = Some(SegmentKind::Bits);
}
BitArrayOption::Int { .. } => {
kind_count += 1;
kind = Some(SegmentKind::Int);
}
BitArrayOption::Float { .. } => {
kind_count += 1;
kind = Some(SegmentKind::Float);
}
BitArrayOption::Utf8 { .. } => {
kind_count += 1;
kind = Some(SegmentKind::String(StringEncoding::Utf8));
}
BitArrayOption::Utf16 { .. } => {
kind_count += 1;
kind = Some(SegmentKind::String(StringEncoding::Utf16(
segment_endianness(segment),
)));
}
BitArrayOption::Utf32 { .. } => {
kind_count += 1;
kind = Some(SegmentKind::String(StringEncoding::Utf32(
segment_endianness(segment),
)));
}
BitArrayOption::Utf8Codepoint { .. } => {
kind_count += 1;
kind = Some(SegmentKind::UtfCodepoint(StringEncoding::Utf8));
}
BitArrayOption::Utf16Codepoint { .. } => {
kind_count += 1;
kind = Some(SegmentKind::UtfCodepoint(StringEncoding::Utf16(
segment_endianness(segment),
)));
}
BitArrayOption::Utf32Codepoint { .. } => {
kind_count += 1;
kind = Some(SegmentKind::UtfCodepoint(StringEncoding::Utf32(
segment_endianness(segment),
)));
}
BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } => {
signedness_count += 1;
}
BitArrayOption::Big { .. }
| BitArrayOption::Little { .. }
| BitArrayOption::Native { .. } => endianness_count += 1,
BitArrayOption::Size { .. } => size_count += 1,
BitArrayOption::Unit { value, .. } => {
unit_count += 1;
zero_unit |= *value == 0;
}
}
}
for (count, reason) in [
(
kind_count,
InvalidBitArraySegmentOptionsReason::MultipleKinds,
),
(
signedness_count,
InvalidBitArraySegmentOptionsReason::MultipleSignedness,
),
(
endianness_count,
InvalidBitArraySegmentOptionsReason::MultipleEndianness,
),
(
size_count,
InvalidBitArraySegmentOptionsReason::MultipleSizes,
),
(
unit_count,
InvalidBitArraySegmentOptionsReason::MultipleUnits,
),
] {
if count > 1 {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySegmentOptions { reason },
},
});
}
}
if unit_count > size_count {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySegmentOptions {
reason: InvalidBitArraySegmentOptionsReason::UnitWithoutSize,
},
},
});
}
if zero_unit {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySegmentOptions {
reason: InvalidBitArraySegmentOptionsReason::ZeroUnit,
},
},
});
}
let kind = kind.unwrap_or_else(|| {
if matches!(segment.value_unwrapping_assign(), Pattern::String { .. }) {
SegmentKind::String(StringEncoding::Utf8)
} else {
SegmentKind::Int
}
});
let incompatible_modifier = match kind {
SegmentKind::Int => false,
SegmentKind::Float => signedness_count != 0,
SegmentKind::Bits => signedness_count + endianness_count != 0,
SegmentKind::String(StringEncoding::Utf8) => {
signedness_count + endianness_count + size_count + unit_count != 0
}
SegmentKind::String(StringEncoding::Utf16(_) | StringEncoding::Utf32(_)) => {
signedness_count + size_count + unit_count != 0
}
SegmentKind::UtfCodepoint(StringEncoding::Utf8) => {
signedness_count + endianness_count + size_count + unit_count != 0
}
SegmentKind::UtfCodepoint(StringEncoding::Utf16(_) | StringEncoding::Utf32(_)) => {
signedness_count + size_count + unit_count != 0
}
};
if incompatible_modifier {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySegmentOptions {
reason: InvalidBitArraySegmentOptionsReason::Incompatible,
},
},
});
}
Ok(kind)
}
fn validate_segment_position(
index: usize,
count: usize,
segment: &BitArrayPatternSegment,
) -> Result<(), PlanError> {
if index + 1 < count && matches!(segment, BitArrayPatternSegment::Bits { size: None, .. }) {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArrayUnsizedSegment { index, count },
},
});
}
Ok(())
}
fn segment_endianness(segment: &BitArraySegment<Pattern<Arc<Type>>, Arc<Type>>) -> Endianness {
if segment
.options
.iter()
.any(|option| matches!(option, BitArrayOption::Little { .. }))
{
Endianness::Little
} else {
Endianness::Big
}
}
fn plan_segment_size(
pattern: &Pattern<Arc<Type>>,
unit: u8,
context: &mut PlanContext<'_>,
) -> Result<BitArrayPatternSize, PlanError> {
let Pattern::BitArraySize(size) = pattern else {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizePattern {
actual: super::pattern_kind(pattern),
},
},
});
};
Ok(BitArrayPatternSize::new(plan_size(size, context)?, unit))
}
fn plan_size(
size: &BitArraySize<Arc<Type>>,
context: &mut PlanContext<'_>,
) -> Result<BitArrayPatternSizeExpr, PlanError> {
match size {
BitArraySize::Int { int_value, .. } => {
Ok(BitArrayPatternSizeExpr::value(int_value.clone()))
}
BitArraySize::Variable {
name, constructor, ..
} => {
let constructor = resolved_size_constructor(name, constructor.as_deref().cloned())?;
plan_size_variable(name.clone(), constructor, context)
}
BitArraySize::BinaryOperator {
operator,
left,
right,
..
} => {
let left = plan_size(left, context)?;
let right = plan_size(right, context)?;
Ok(match operator {
IntOperator::Add => BitArrayPatternSizeExpr::add(left, right),
IntOperator::Subtract => BitArrayPatternSizeExpr::subtract(left, right),
IntOperator::Multiply => BitArrayPatternSizeExpr::multiply(left, right),
IntOperator::Divide => BitArrayPatternSizeExpr::divide(left, right),
IntOperator::Remainder => BitArrayPatternSizeExpr::remainder(left, right),
})
}
BitArraySize::Block { inner, .. } => plan_size(inner, context),
}
}
fn plan_size_variable(
name: ecow::EcoString,
constructor: ValueConstructor,
context: &PlanContext<'_>,
) -> Result<BitArrayPatternSizeExpr, PlanError> {
let source = match constructor.variant {
ValueConstructorVariant::LocalVariable { .. } => {
let binding = context.resolve_local(&name)?;
let actual = binding.value_type();
match binding {
crate::planner::context::ResolvedLocal::Primitive(LocalId::Int(local)) => {
Some(BitArraySizeSource::Local(local))
}
_ => return Err(super::pattern_type_mismatch(ValueType::Int, actual)),
}
}
ValueConstructorVariant::ModuleConstant {
module,
name: constant_name,
literal,
..
} => {
context.module_constant_instantiation(
&module,
&constant_name,
&crate::plan::ValueShape::Int,
)?;
Some(BitArraySizeSource::Constant(literal))
}
_ => None,
};
let Some(source) = source else {
return Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeSource { name },
},
});
};
match source {
BitArraySizeSource::Local(local) => Ok(BitArrayPatternSizeExpr::local_get(local, name)),
BitArraySizeSource::Constant(constant) => plan_size_constant(constant, context),
}
}
enum BitArraySizeSource {
Local(IntLocalId),
Constant(Constant<Arc<Type>>),
}
fn plan_size_constant(
constant: Constant<Arc<Type>>,
context: &PlanContext<'_>,
) -> Result<BitArrayPatternSizeExpr, PlanError> {
match constant {
Constant::Int { int_value, .. } => Ok(BitArrayPatternSizeExpr::value(int_value)),
Constant::Var {
name, constructor, ..
} => {
let constructor = resolved_size_constructor(&name, constructor.map(|value| *value))?;
plan_size_variable(name, constructor, context)
}
_ => Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeConstant,
},
}),
}
}
fn resolved_size_constructor(
name: &ecow::EcoString,
constructor: Option<ValueConstructor>,
) -> Result<ValueConstructor, PlanError> {
constructor.ok_or_else(|| PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeUnresolved { name: name.clone() },
},
})
}
fn size_value(value: u8) -> BitArrayPatternSizeExpr {
BitArrayPatternSizeExpr::value(value.into())
}
fn plan_int_pattern(
pattern: Pattern<Arc<Type>>,
context: &mut PlanContext<'_>,
) -> Result<BitArrayPatternValue<num_bigint::BigInt, IntLocalId>, PlanError> {
match pattern {
Pattern::Int { int_value, .. } => Ok(BitArrayPatternValue::Literal(int_value)),
Pattern::Variable { name, type_, .. } if type_.is_int() => {
let local = context.define_int_local(name.clone());
Ok(BitArrayPatternValue::Bind(PatternBinding::new(local, name)))
}
Pattern::Discard { type_, .. } if type_.is_int() => Ok(BitArrayPatternValue::Discard),
Pattern::Assign { name, pattern, .. } => {
let pattern = plan_int_pattern(*pattern, context)?;
let local = context.define_int_local(name.clone());
Ok(BitArrayPatternValue::Alias {
pattern: Box::new(pattern),
binding: PatternBinding::new(local, name),
})
}
pattern => Err(super::unexpected_pattern(
&pattern,
&crate::plan::ValueShape::Int,
context,
)),
}
}
fn plan_float_pattern(
pattern: Pattern<Arc<Type>>,
context: &mut PlanContext<'_>,
) -> Result<BitArrayPatternValue<f64, FloatLocalId>, PlanError> {
match pattern {
Pattern::Float { float_value, .. } => {
Ok(BitArrayPatternValue::Literal(float_value.value()))
}
Pattern::Variable { name, type_, .. } if type_.is_float() => {
let local = context.define_float_local(name.clone());
Ok(BitArrayPatternValue::Bind(PatternBinding::new(local, name)))
}
Pattern::Discard { type_, .. } if type_.is_float() => Ok(BitArrayPatternValue::Discard),
Pattern::Assign { name, pattern, .. } => {
let pattern = plan_float_pattern(*pattern, context)?;
let local = context.define_float_local(name.clone());
Ok(BitArrayPatternValue::Alias {
pattern: Box::new(pattern),
binding: PatternBinding::new(local, name),
})
}
pattern => Err(super::unexpected_pattern(
&pattern,
&crate::plan::ValueShape::Float,
context,
)),
}
}
fn plan_bits_pattern(
pattern: Pattern<Arc<Type>>,
context: &mut PlanContext<'_>,
) -> Result<BitArrayBindingPattern<BitArrayLocalId>, PlanError> {
match pattern {
Pattern::Variable { name, type_, .. } if type_.is_bit_array() => {
let local = context.define_bit_array_local(name.clone());
Ok(BitArrayBindingPattern::Bind(PatternBinding::new(
local, name,
)))
}
Pattern::Discard { type_, .. } if type_.is_bit_array() => {
Ok(BitArrayBindingPattern::Discard)
}
Pattern::Assign { name, pattern, .. } => {
let pattern = plan_bits_pattern(*pattern, context)?;
let local = context.define_bit_array_local(name.clone());
Ok(BitArrayBindingPattern::Alias {
pattern: Box::new(pattern),
binding: PatternBinding::new(local, name),
})
}
pattern => Err(super::unexpected_pattern(
&pattern,
&crate::plan::ValueShape::BitArray,
context,
)),
}
}
fn plan_string_pattern(
pattern: Pattern<Arc<Type>>,
context: &PlanContext<'_>,
) -> Result<BitArrayStringPattern, PlanError> {
match pattern {
Pattern::String { value, .. } => Ok(BitArrayStringPattern::Literal(
convert_string_escape_chars(&value),
)),
Pattern::Discard { type_, .. } if type_.is_string() => Ok(BitArrayStringPattern::Discard),
pattern => Err(super::unexpected_pattern(
&pattern,
&crate::plan::ValueShape::String,
context,
)),
}
}
fn plan_utf_codepoint_pattern(
pattern: Pattern<Arc<Type>>,
context: &mut PlanContext<'_>,
) -> Result<BitArrayBindingPattern<UtfCodepointLocalId>, PlanError> {
match pattern {
Pattern::Variable { name, type_, .. } if type_.is_utf_codepoint() => {
let local = context.define_utf_codepoint_local(name.clone());
Ok(BitArrayBindingPattern::Bind(PatternBinding::new(
local, name,
)))
}
Pattern::Discard { type_, .. } if type_.is_utf_codepoint() => {
Ok(BitArrayBindingPattern::Discard)
}
Pattern::Assign { name, pattern, .. } => {
let pattern = plan_utf_codepoint_pattern(*pattern, context)?;
let local = context.define_utf_codepoint_local(name.clone());
Ok(BitArrayBindingPattern::Alias {
pattern: Box::new(pattern),
binding: PatternBinding::new(local, name),
})
}
pattern => Err(super::unexpected_pattern(
&pattern,
&crate::plan::ValueShape::UtfCodepoint,
context,
)),
}
}
fn is_total_pattern(segments: &[BitArraySegment<Pattern<Arc<Type>>, Arc<Type>>]) -> bool {
segments.len() == 1
&& segments[0].size().is_none()
&& matches!(
segments[0].options.as_slice(),
[BitArrayOption::Bits { .. }]
)
}
#[cfg(test)]
mod tests {
use crate::plan::{
BitArrayBindingPattern, BitArrayLocalId, BitArrayPattern, BitArrayPatternSegment,
BitArrayPatternSizeExpr, BitArrayStringPattern, Endianness, FloatLocalId, IntLocalId,
PatternBinding, Signedness, StringEncoding, UtfCodepointLocalId, ValueType,
};
use crate::planner::context::{AnonymousFunctions, PlanContext};
use crate::planner::error::{
InvalidBitArraySegmentOptionsReason, InvalidModuleReferenceReason,
InvalidPatternShapeReason, InvalidTypedAstReason, PatternKind, PlanError,
UnsupportedBitArraySegmentReason,
};
use crate::planner::support::{compile, dummy_span, expect_plan_error};
use gleam_core::ast::Publicity;
use gleam_core::ast::{BitArrayOption, BitArraySegment, Constant, Pattern};
use gleam_core::type_::{
self, Deprecation, ValueConstructor, ValueConstructorVariant, error::VariableOrigin,
};
#[test]
fn reject_profile_bit_array_pattern_native_endianness() {
assert_eq!(
expect_plan_error(
r#"
pub fn main() {
case <<1>> {
<<value:native>> -> value
_ -> 0
}
}
"#,
),
PlanError::UnsupportedBitArraySegment {
reason: UnsupportedBitArraySegmentReason::NativeEndianness,
},
);
}
#[test]
fn reject_margin_bit_array_pattern_invalid_typed_ast_shapes() {
let discard = |type_| Pattern::Discard {
location: dummy_span(),
name: "_".into(),
type_,
};
let segment = |value, options, type_| BitArraySegment {
location: dummy_span(),
value: Box::new(value),
options,
type_,
};
let alias = |name: &str, pattern: Pattern<std::sync::Arc<gleam_core::type_::Type>>| {
Pattern::Assign {
name: name.into(),
location: dummy_span(),
pattern: Box::new(pattern),
}
};
let local_constructor = |type_| ValueConstructor {
publicity: Publicity::Private,
deprecation: Deprecation::NotDeprecated,
type_,
variant: ValueConstructorVariant::LocalVariable {
location: dummy_span(),
origin: VariableOrigin::generated(),
},
};
let size = |value| BitArrayOption::Size {
location: dummy_span(),
value: Box::new(Pattern::BitArraySize(value)),
short_form: false,
};
let int_size = || gleam_core::ast::BitArraySize::Int {
location: dummy_span(),
value: "1".into(),
int_value: 1.into(),
};
let missing_constructor = || gleam_core::ast::BitArraySize::Variable {
location: dummy_span(),
name: "missing".into(),
constructor: None,
type_: type_::int(),
};
let invalid = |reason| {
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape { reason },
})
};
let mut cases = Vec::new();
cases.push((
vec![segment(
discard(type_::int()),
vec![BitArrayOption::Utf8Codepoint {
location: dummy_span(),
}],
type_::int(),
)],
invalid(InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::UtfCodepoint,
actual: ValueType::Int,
}),
));
cases.push((
vec![segment(
alias("codepoint_alias", discard(type_::int())),
vec![BitArrayOption::Utf8Codepoint {
location: dummy_span(),
}],
type_::utf_codepoint(),
)],
invalid(InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::UtfCodepoint,
actual: ValueType::Int,
}),
));
for options in [
vec![
BitArrayOption::Utf8Codepoint {
location: dummy_span(),
},
BitArrayOption::Big {
location: dummy_span(),
},
],
vec![
BitArrayOption::Utf16Codepoint {
location: dummy_span(),
},
BitArrayOption::Signed {
location: dummy_span(),
},
],
vec![
BitArrayOption::Utf32Codepoint {
location: dummy_span(),
},
size(int_size()),
],
] {
cases.push((
vec![segment(
discard(type_::utf_codepoint()),
options,
type_::utf_codepoint(),
)],
invalid(InvalidPatternShapeReason::BitArraySegmentOptions {
reason: InvalidBitArraySegmentOptionsReason::Incompatible,
}),
));
}
for (options, reason) in [
(
vec![
BitArrayOption::Int {
location: dummy_span(),
},
BitArrayOption::Int {
location: dummy_span(),
},
],
InvalidBitArraySegmentOptionsReason::MultipleKinds,
),
(
vec![BitArrayOption::Unit {
location: dummy_span(),
value: 2,
}],
InvalidBitArraySegmentOptionsReason::UnitWithoutSize,
),
(
vec![
BitArrayOption::Size {
location: dummy_span(),
value: Box::new(Pattern::BitArraySize(
gleam_core::ast::BitArraySize::Int {
location: dummy_span(),
value: "1".into(),
int_value: 1.into(),
},
)),
short_form: false,
},
BitArrayOption::Unit {
location: dummy_span(),
value: 0,
},
],
InvalidBitArraySegmentOptionsReason::ZeroUnit,
),
(
vec![
BitArrayOption::Float {
location: dummy_span(),
},
BitArrayOption::Signed {
location: dummy_span(),
},
],
InvalidBitArraySegmentOptionsReason::Incompatible,
),
(
vec![
BitArrayOption::Bits {
location: dummy_span(),
},
BitArrayOption::Little {
location: dummy_span(),
},
],
InvalidBitArraySegmentOptionsReason::Incompatible,
),
(
vec![
BitArrayOption::Utf8 {
location: dummy_span(),
},
BitArrayOption::Big {
location: dummy_span(),
},
],
InvalidBitArraySegmentOptionsReason::Incompatible,
),
(
vec![
BitArrayOption::Utf16 {
location: dummy_span(),
},
BitArrayOption::Signed {
location: dummy_span(),
},
],
InvalidBitArraySegmentOptionsReason::Incompatible,
),
] {
cases.push((
vec![segment(discard(type_::int()), options, type_::int())],
invalid(InvalidPatternShapeReason::BitArraySegmentOptions { reason }),
));
}
cases.push((
vec![segment(
Pattern::Variable {
location: dummy_span(),
name: "value".into(),
type_: type_::string(),
origin: gleam_core::type_::error::VariableOrigin::generated(),
},
vec![BitArrayOption::Utf8 {
location: dummy_span(),
}],
type_::string(),
)],
invalid(InvalidPatternShapeReason::KindMismatch {
expected: ValueType::String,
actual: PatternKind::Variable,
}),
));
cases.push((
vec![
segment(
discard(type_::bit_array()),
vec![BitArrayOption::Bits {
location: dummy_span(),
}],
type_::bit_array(),
),
segment(
discard(type_::int()),
vec![BitArrayOption::Int {
location: dummy_span(),
}],
type_::int(),
),
],
invalid(InvalidPatternShapeReason::BitArrayUnsizedSegment { index: 0, count: 2 }),
));
for (value, options, type_, expected) in [
(
discard(type_::int()),
vec![BitArrayOption::Size {
location: dummy_span(),
value: Box::new(discard(type_::int())),
short_form: false,
}],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizePattern {
actual: PatternKind::Discard,
},
},
},
),
(
discard(type_::string()),
vec![BitArrayOption::Int {
location: dummy_span(),
}],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::Int,
actual: ValueType::String,
},
},
},
),
(
discard(type_::int()),
vec![BitArrayOption::Float {
location: dummy_span(),
}],
type_::float(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::Float,
actual: ValueType::Int,
},
},
},
),
(
discard(type_::int()),
vec![BitArrayOption::Bits {
location: dummy_span(),
}],
type_::bit_array(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::BitArray,
actual: ValueType::Int,
},
},
},
),
(
Pattern::Int {
location: dummy_span(),
value: "1".into(),
int_value: 1.into(),
},
vec![BitArrayOption::Utf8 {
location: dummy_span(),
}],
type_::string(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::String,
actual: ValueType::Int,
},
},
},
),
(
alias("int_alias", discard(type_::string())),
vec![BitArrayOption::Int {
location: dummy_span(),
}],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::Int,
actual: ValueType::String,
},
},
},
),
(
alias("float_alias", discard(type_::int())),
vec![BitArrayOption::Float {
location: dummy_span(),
}],
type_::float(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::Float,
actual: ValueType::Int,
},
},
},
),
(
alias("bits_alias", discard(type_::int())),
vec![BitArrayOption::Bits {
location: dummy_span(),
}],
type_::bit_array(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::BitArray,
actual: ValueType::Int,
},
},
},
),
(
discard(type_::int()),
vec![size(missing_constructor())],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeUnresolved {
name: "missing".into(),
},
},
},
),
(
discard(type_::int()),
vec![size(gleam_core::ast::BitArraySize::Variable {
location: dummy_span(),
name: "unknown".into(),
constructor: Some(Box::new(local_constructor(type_::int()))),
type_: type_::int(),
})],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::UnknownLocal {
name: "unknown".into(),
},
},
),
(
discard(type_::int()),
vec![size(gleam_core::ast::BitArraySize::Variable {
location: dummy_span(),
name: "string_size".into(),
constructor: Some(Box::new(local_constructor(type_::string()))),
type_: type_::int(),
})],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::TypeMismatch {
expected: ValueType::Int,
actual: ValueType::String,
},
},
},
),
(
discard(type_::int()),
vec![size(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Add,
left: Box::new(missing_constructor()),
right: Box::new(int_size()),
})],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeUnresolved {
name: "missing".into(),
},
},
},
),
(
discard(type_::int()),
vec![size(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Add,
left: Box::new(int_size()),
right: Box::new(missing_constructor()),
})],
type_::int(),
PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeUnresolved {
name: "missing".into(),
},
},
},
),
] {
cases.push((vec![segment(value, options, type_)], Err(expected)));
}
for (segments, expected) in cases {
let module_name = "main".into();
let functions = std::collections::HashMap::new();
let mut anonymous = AnonymousFunctions::default();
let mut context = PlanContext::new(&module_name, &functions, &mut anonymous);
context.define_int_local("int_size".into());
context.define_string_local("string_size".into());
assert_eq!(
super::plan_bit_array_pattern(segments, &mut context),
expected,
);
}
}
#[test]
fn plan_bit_array_pattern_preserves_supported_segment_shapes() {
let discard = |type_| Pattern::Discard {
location: dummy_span(),
name: "_".into(),
type_,
};
let segment = |value, options, type_| BitArraySegment {
location: dummy_span(),
value: Box::new(value),
options,
type_,
};
let alias = |name: &str, pattern: Pattern<std::sync::Arc<gleam_core::type_::Type>>| {
Pattern::Assign {
name: name.into(),
location: dummy_span(),
pattern: Box::new(pattern),
}
};
let local_constructor = |type_| ValueConstructor {
publicity: Publicity::Private,
deprecation: Deprecation::NotDeprecated,
type_,
variant: ValueConstructorVariant::LocalVariable {
location: dummy_span(),
origin: VariableOrigin::generated(),
},
};
let size = |value| BitArrayOption::Size {
location: dummy_span(),
value: Box::new(Pattern::BitArraySize(value)),
short_form: false,
};
let int_size = || gleam_core::ast::BitArraySize::Int {
location: dummy_span(),
value: "1".into(),
int_value: 1.into(),
};
let mut cases = Vec::new();
cases.push((
vec![segment(
discard(type_::bit_array()),
vec![BitArrayOption::Bits {
location: dummy_span(),
}],
type_::bit_array(),
)],
Ok((
BitArrayPattern::new(vec![BitArrayPatternSegment::Bits {
pattern: BitArrayBindingPattern::Discard,
size: None,
unit: 1,
}]),
true,
)),
));
cases.push((
vec![
segment(
Pattern::Variable {
location: dummy_span(),
name: "codepoint".into(),
type_: type_::utf_codepoint(),
origin: VariableOrigin::generated(),
},
vec![BitArrayOption::Utf8Codepoint {
location: dummy_span(),
}],
type_::utf_codepoint(),
),
segment(
discard(type_::utf_codepoint()),
vec![
BitArrayOption::Utf16Codepoint {
location: dummy_span(),
},
BitArrayOption::Little {
location: dummy_span(),
},
],
type_::utf_codepoint(),
),
segment(
alias("codepoint_alias", discard(type_::utf_codepoint())),
vec![BitArrayOption::Utf32Codepoint {
location: dummy_span(),
}],
type_::utf_codepoint(),
),
],
Ok((
BitArrayPattern::new(vec![
BitArrayPatternSegment::UtfCodepoint {
pattern: BitArrayBindingPattern::Bind(PatternBinding::new(
UtfCodepointLocalId(0),
"codepoint".into(),
)),
encoding: StringEncoding::Utf8,
},
BitArrayPatternSegment::UtfCodepoint {
pattern: BitArrayBindingPattern::Discard,
encoding: StringEncoding::Utf16(Endianness::Little),
},
BitArrayPatternSegment::UtfCodepoint {
pattern: BitArrayBindingPattern::Alias {
pattern: Box::new(BitArrayBindingPattern::Discard),
binding: PatternBinding::new(
UtfCodepointLocalId(1),
"codepoint_alias".into(),
),
},
encoding: StringEncoding::Utf32(Endianness::Big),
},
]),
false,
)),
));
cases.push((
vec![
segment(
Pattern::Float {
location: dummy_span(),
value: "1.0".into(),
float_value: gleam_core::parse::LiteralFloatValue::ONE,
},
vec![BitArrayOption::Float {
location: dummy_span(),
}],
type_::float(),
),
segment(
Pattern::String {
location: dummy_span(),
value: "A".into(),
},
Vec::new(),
type_::string(),
),
],
Ok((
BitArrayPattern::new(vec![
BitArrayPatternSegment::Float {
pattern: crate::plan::BitArrayPatternValue::Literal(1.0),
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(64.into()),
1,
),
endianness: crate::plan::Endianness::Big,
},
BitArrayPatternSegment::String {
pattern: BitArrayStringPattern::Literal("A".into()),
encoding: StringEncoding::Utf8,
},
]),
false,
)),
));
let explicit_size = |value| {
vec![BitArrayOption::Size {
location: dummy_span(),
value: Box::new(Pattern::BitArraySize(value)),
short_form: false,
}]
};
cases.push((
vec![
segment(
Pattern::Int {
location: dummy_span(),
value: "258".into(),
int_value: 258.into(),
},
vec![
BitArrayOption::Int {
location: dummy_span(),
},
BitArrayOption::Signed {
location: dummy_span(),
},
BitArrayOption::Little {
location: dummy_span(),
},
size(int_size()),
],
type_::int(),
),
segment(
Pattern::Variable {
location: dummy_span(),
name: "integer".into(),
type_: type_::int(),
origin: VariableOrigin::generated(),
},
Vec::new(),
type_::int(),
),
segment(discard(type_::int()), Vec::new(), type_::int()),
segment(
alias("integer_alias", discard(type_::int())),
Vec::new(),
type_::int(),
),
],
Ok((
BitArrayPattern::new(vec![
BitArrayPatternSegment::Int {
pattern: crate::plan::BitArrayPatternValue::Literal(258.into()),
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(1.into()),
1,
),
endianness: Endianness::Little,
signedness: Signedness::Signed,
},
BitArrayPatternSegment::Int {
pattern: crate::plan::BitArrayPatternValue::Bind(PatternBinding::new(
IntLocalId(1),
"integer".into(),
)),
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(8.into()),
1,
),
endianness: Endianness::Big,
signedness: Signedness::Unsigned,
},
BitArrayPatternSegment::Int {
pattern: crate::plan::BitArrayPatternValue::Discard,
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(8.into()),
1,
),
endianness: Endianness::Big,
signedness: Signedness::Unsigned,
},
BitArrayPatternSegment::Int {
pattern: crate::plan::BitArrayPatternValue::Alias {
pattern: Box::new(crate::plan::BitArrayPatternValue::Discard),
binding: PatternBinding::new(IntLocalId(2), "integer_alias".into()),
},
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(8.into()),
1,
),
endianness: Endianness::Big,
signedness: Signedness::Unsigned,
},
]),
false,
)),
));
cases.push((
vec![
segment(
Pattern::Variable {
location: dummy_span(),
name: "float".into(),
type_: type_::float(),
origin: VariableOrigin::generated(),
},
vec![
BitArrayOption::Float {
location: dummy_span(),
},
BitArrayOption::Little {
location: dummy_span(),
},
size(int_size()),
],
type_::float(),
),
segment(
discard(type_::float()),
vec![BitArrayOption::Float {
location: dummy_span(),
}],
type_::float(),
),
segment(
alias("float_alias", discard(type_::float())),
vec![BitArrayOption::Float {
location: dummy_span(),
}],
type_::float(),
),
],
Ok((
BitArrayPattern::new(vec![
BitArrayPatternSegment::Float {
pattern: crate::plan::BitArrayPatternValue::Bind(PatternBinding::new(
FloatLocalId(0),
"float".into(),
)),
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(1.into()),
1,
),
endianness: Endianness::Little,
},
BitArrayPatternSegment::Float {
pattern: crate::plan::BitArrayPatternValue::Discard,
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(64.into()),
1,
),
endianness: Endianness::Big,
},
BitArrayPatternSegment::Float {
pattern: crate::plan::BitArrayPatternValue::Alias {
pattern: Box::new(crate::plan::BitArrayPatternValue::Discard),
binding: PatternBinding::new(FloatLocalId(1), "float_alias".into()),
},
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(64.into()),
1,
),
endianness: Endianness::Big,
},
]),
false,
)),
));
cases.push((
vec![
segment(
Pattern::Variable {
location: dummy_span(),
name: "head".into(),
type_: type_::bit_array(),
origin: VariableOrigin::generated(),
},
vec![
BitArrayOption::Bits {
location: dummy_span(),
},
size(int_size()),
],
type_::bit_array(),
),
segment(
discard(type_::bit_array()),
vec![
BitArrayOption::Bytes {
location: dummy_span(),
},
size(int_size()),
],
type_::bit_array(),
),
segment(
alias("tail", discard(type_::bit_array())),
vec![BitArrayOption::Bits {
location: dummy_span(),
}],
type_::bit_array(),
),
],
Ok((
BitArrayPattern::new(vec![
BitArrayPatternSegment::Bits {
pattern: BitArrayBindingPattern::Bind(PatternBinding::new(
BitArrayLocalId(0),
"head".into(),
)),
size: Some(crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(1.into()),
1,
)),
unit: 1,
},
BitArrayPatternSegment::Bits {
pattern: BitArrayBindingPattern::Discard,
size: Some(crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::value(1.into()),
8,
)),
unit: 8,
},
BitArrayPatternSegment::Bits {
pattern: BitArrayBindingPattern::Alias {
pattern: Box::new(BitArrayBindingPattern::Discard),
binding: PatternBinding::new(BitArrayLocalId(1), "tail".into()),
},
size: None,
unit: 1,
},
]),
false,
)),
));
cases.push((
vec![
segment(
discard(type_::string()),
vec![BitArrayOption::Utf8 {
location: dummy_span(),
}],
type_::string(),
),
segment(
Pattern::String {
location: dummy_span(),
value: "A".into(),
},
vec![
BitArrayOption::Utf16 {
location: dummy_span(),
},
BitArrayOption::Little {
location: dummy_span(),
},
],
type_::string(),
),
segment(
Pattern::String {
location: dummy_span(),
value: "B".into(),
},
vec![
BitArrayOption::Utf32 {
location: dummy_span(),
},
BitArrayOption::Big {
location: dummy_span(),
},
],
type_::string(),
),
],
Ok((
BitArrayPattern::new(vec![
BitArrayPatternSegment::String {
pattern: BitArrayStringPattern::Discard,
encoding: StringEncoding::Utf8,
},
BitArrayPatternSegment::String {
pattern: BitArrayStringPattern::Literal("A".into()),
encoding: StringEncoding::Utf16(Endianness::Little),
},
BitArrayPatternSegment::String {
pattern: BitArrayStringPattern::Literal("B".into()),
encoding: StringEncoding::Utf32(Endianness::Big),
},
]),
false,
)),
));
let arithmetic_size = gleam_core::ast::BitArraySize::Block {
location: dummy_span(),
inner: Box::new(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Add,
left: Box::new(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Subtract,
left: Box::new(gleam_core::ast::BitArraySize::Variable {
location: dummy_span(),
name: "int_size".into(),
constructor: Some(Box::new(local_constructor(type_::int()))),
type_: type_::int(),
}),
right: Box::new(int_size()),
}),
right: Box::new(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Multiply,
left: Box::new(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Divide,
left: Box::new(int_size()),
right: Box::new(int_size()),
}),
right: Box::new(gleam_core::ast::BitArraySize::BinaryOperator {
location: dummy_span(),
operator: gleam_core::ast::IntOperator::Remainder,
left: Box::new(int_size()),
right: Box::new(int_size()),
}),
}),
}),
};
cases.push((
vec![segment(
discard(type_::int()),
explicit_size(arithmetic_size),
type_::int(),
)],
Ok((
BitArrayPattern::new(vec![BitArrayPatternSegment::Int {
pattern: crate::plan::BitArrayPatternValue::Discard,
size: crate::plan::BitArrayPatternSize::new(
BitArrayPatternSizeExpr::add(
BitArrayPatternSizeExpr::subtract(
BitArrayPatternSizeExpr::local_get(
IntLocalId(0),
"int_size".into(),
),
BitArrayPatternSizeExpr::value(1.into()),
),
BitArrayPatternSizeExpr::multiply(
BitArrayPatternSizeExpr::divide(
BitArrayPatternSizeExpr::value(1.into()),
BitArrayPatternSizeExpr::value(1.into()),
),
BitArrayPatternSizeExpr::remainder(
BitArrayPatternSizeExpr::value(1.into()),
BitArrayPatternSizeExpr::value(1.into()),
),
),
),
1,
),
endianness: Endianness::Big,
signedness: Signedness::Unsigned,
}]),
false,
)),
));
for (segments, expected) in cases {
let module_name = "main".into();
let functions = std::collections::HashMap::new();
let mut anonymous = AnonymousFunctions::default();
let mut context = PlanContext::new(&module_name, &functions, &mut anonymous);
context.define_int_local("int_size".into());
context.define_string_local("string_size".into());
assert_eq!(
super::plan_bit_array_pattern(segments, &mut context),
expected,
);
}
}
#[test]
fn reject_margin_bit_array_pattern_size_invalid_constant_constructors() {
let module = compile(
r#"
const size = 8
const alias = size
pub fn main() { 0 }
"#,
);
let module_constant = |name: &str, module_name: &str, literal| {
let constant = module
.definitions
.constants
.iter()
.find(|constant| constant.name == name)
.expect("module constant should exist");
ValueConstructor {
publicity: constant.publicity,
deprecation: constant.deprecation.clone(),
type_: constant.type_.clone(),
variant: ValueConstructorVariant::ModuleConstant {
documentation: None,
location: constant.location,
module: module_name.into(),
name: name.into(),
literal,
implementations: constant.implementations,
},
}
};
let constant_value = |name: &str| {
module
.definitions
.constants
.iter()
.find(|constant| constant.name == name)
.expect("module constant should exist")
.value
.as_ref()
.clone()
};
let missing_alias = module_constant(
"alias",
"main",
Constant::Var {
location: dummy_span(),
module: None,
name: "missing".into(),
constructor: None,
type_: type_::int(),
},
);
let string = module_constant(
"size",
"main",
Constant::String {
location: dummy_span(),
value: "wrong".into(),
},
);
let external = module_constant("size", "other", constant_value("size"));
let module_name = "main".into();
let functions = std::collections::HashMap::new();
let mut anonymous = AnonymousFunctions::default();
let context = PlanContext::new(&module_name, &functions, &mut anonymous);
assert_eq!(
super::plan_size_variable("missing_alias".into(), missing_alias, &context),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::ModuleReference {
module: "main".into(),
name: "alias".into(),
reason: InvalidModuleReferenceReason::MissingConstant,
},
}),
);
assert_eq!(
super::plan_size_variable("string".into(), string, &context),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::ModuleReference {
module: "main".into(),
name: "size".into(),
reason: InvalidModuleReferenceReason::MissingConstant,
},
}),
);
assert_eq!(
super::plan_size_variable("external".into(), external, &context),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::ModuleReference {
module: "other".into(),
name: "size".into(),
reason: InvalidModuleReferenceReason::UnlinkedModule,
},
}),
);
assert_eq!(
super::plan_size_variable(
"record".into(),
ValueConstructor {
publicity: Publicity::Private,
deprecation: Deprecation::NotDeprecated,
type_: type_::int(),
variant: ValueConstructorVariant::Record {
name: "Record".into(),
arity: 0,
field_map: None,
location: dummy_span(),
module: "main".into(),
variants_count: 1,
variant_index: 0,
documentation: None,
},
},
&context,
),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeSource {
name: "record".into(),
},
},
}),
);
assert_eq!(
super::plan_size_constant(
Constant::String {
location: dummy_span(),
value: "wrong".into(),
},
&context,
),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeConstant,
},
}),
);
assert_eq!(
super::plan_size_constant(
Constant::Var {
location: dummy_span(),
module: None,
name: "missing".into(),
constructor: None,
type_: type_::int(),
},
&context,
),
Err(PlanError::InvalidTypedAst {
reason: InvalidTypedAstReason::PatternShape {
reason: InvalidPatternShapeReason::BitArraySizeUnresolved {
name: "missing".into(),
},
},
}),
);
}
}