mod test_helpers;
mod optional {
use crate::test_helpers::*;
use slicec::diagnostics::{Diagnostic, Error};
use slicec::grammar::*;
use slicec::slice_file::Span;
use test_case::test_case;
#[test_case("bool"; "primitive")]
#[test_case("Foo"; "simple")]
#[test_case("Test::Foo"; "relatively scoped")]
#[test_case("::Test::Foo"; "globally scoped")]
fn optionals_are_parsed_correctly(type_name: &str) {
let slice = format!(
"
module Test
struct Foo {{}}
struct S {{
a: {type_name}?
}}
"
);
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(field.data_type.is_optional);
}
#[test_case("bool"; "primitive")]
#[test_case("Foo"; "user defined")]
fn optional_type_names_end_with_a_question_mark(type_name: &str) {
let slice = format!(
"
module Test
struct Foo {{}}
struct S {{
a: {type_name}?
}}
"
);
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert_eq!(field.data_type.type_string(), type_name.to_owned() + "?");
}
mod slice1 {
use super::*;
use test_case::test_case;
#[test_case("AnyClass")]
fn optional_builtin_types_are_allowed(type_name: &str) {
let slice = format!(
"
mode = Slice1
module Test
exception E {{
a: {type_name}?
}}
"
);
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::E::a").unwrap();
assert!(field.data_type.is_optional);
}
#[test_case("bool")]
#[test_case("int8")]
#[test_case("uint8")]
#[test_case("int16")]
#[test_case("uint16")]
#[test_case("int32")]
#[test_case("uint32")]
#[test_case("varint32")]
#[test_case("varuint32")]
#[test_case("int64")]
#[test_case("uint64")]
#[test_case("varint62")]
#[test_case("varuint62")]
#[test_case("float32")]
#[test_case("float64")]
#[test_case("string")]
fn optional_builtin_types_are_disallowed(type_name: &str) {
let slice = format!(
"
mode = Slice1
module Test
exception E {{
a: {type_name}?
}}
"
);
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: type_name.to_owned(),
})
.set_span(&Span::new(
(5, 24).into(),
(5, 24 + type_name.len() + 1).into(),
"string-0",
));
check_diagnostics(diagnostics, [expected]);
}
#[test_case("class Foo {}"; "class")]
#[test_case("custom Foo"; "custom type")]
fn optional_user_defined_types_are_allowed(definition: &str) {
let slice = format!(
"
mode = Slice1
module Test
{definition}
exception E {{
a: Foo?
}}
"
);
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::E::a").unwrap();
assert!(field.data_type.is_optional);
}
#[test_case("compact struct Foo {}", "struct"; "r#struct")]
#[test_case("unchecked enum Foo {}", "enum"; "r#enum")]
fn optional_user_defined_types_are_disallowed(definition: &str, type_name: &str) {
let slice = format!(
"
mode = Slice1
module Test
{definition}
exception E {{
a: Foo?
}}
"
);
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: type_name.to_owned(),
})
.set_span(&Span::new((6, 24).into(), (6, 28).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn sequences_of_optionals_are_allowed() {
let slice = "
mode = Slice1
module Test
exception E {
a: Sequence<AnyClass?>
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::E::a").unwrap();
let Types::Sequence(sequence) = field.data_type().concrete_type() else { panic!() };
assert!(sequence.element_type.is_optional);
}
#[test]
fn sequences_of_optionals_are_disallowed() {
let slice = "
mode = Slice1
module Test
exception E {
a: Sequence<bool?>
}
";
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: "bool".to_owned(),
})
.set_span(&Span::new((5, 33).into(), (5, 38).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn dictionaries_with_optional_keys_are_disallowed() {
let slice = "
mode = Slice1
module Test
exception E {
a: Dictionary<uint8?, float32>
}
";
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: "uint8".to_owned(),
})
.set_span(&Span::new((5, 35).into(), (5, 41).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn dictionaries_with_optional_values_are_disallowed() {
let slice = "
mode = Slice1
module Test
exception E {
a: Dictionary<string, int32?>
}
";
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: "int32".to_owned(),
})
.set_span(&Span::new((5, 43).into(), (5, 49).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn dictionaries_with_optional_values_are_allowed() {
let slice = "
mode = Slice1
module Test
exception E {
a: Dictionary<string, AnyClass?>
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::E::a").unwrap();
let Types::Dictionary(dictionary) = field.data_type().concrete_type() else { panic!() };
assert!(!dictionary.key_type.is_optional);
assert!(dictionary.value_type.is_optional);
}
#[test]
fn untagged_optional_parameters_are_disallowed() {
let slice = "
mode = Slice1
module Test
interface I {
op(a: bool?)
}
";
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: "bool".to_owned(),
})
.set_span(&Span::new((5, 27).into(), (5, 32).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn tagged_optional_parameters_are_allowed() {
let slice = "
mode = Slice1
module Test
interface I {
op(tag(1) a: float32?)
}
";
let ast = parse_for_ast(slice);
let parameter = ast.find_element::<Parameter>("Test::I::op::a").unwrap();
assert!(parameter.is_tagged());
assert!(parameter.data_type().is_optional);
}
#[test]
fn optional_return_types_are_disallowed() {
let slice = "
mode = Slice1
module Test
interface I {
op() -> float64?
}
";
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: "float64".to_owned(),
})
.set_span(&Span::new((5, 29).into(), (5, 37).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn untagged_optional_fields_are_disallowed() {
let slice = "
mode = Slice1
module Test
compact struct S {
a: bool?
}
";
let diagnostics = parse_for_diagnostics(slice);
let expected = Diagnostic::new(Error::OptionalsNotSupported {
kind: "bool".to_owned(),
})
.set_span(&Span::new((5, 24).into(), (5, 29).into(), "string-0"));
check_diagnostics(diagnostics, [expected]);
}
#[test]
fn tagged_optional_fields_are_allowed() {
let slice = "
mode = Slice1
module Test
exception E {
tag(1) a: float32?
}
";
let ast = parse_for_ast(slice);
let member = ast.find_element::<Field>("Test::E::a").unwrap();
assert!(member.is_tagged());
assert!(member.data_type().is_optional);
}
}
mod slice2 {
use super::*;
use test_case::test_case;
#[test_case("bool")]
#[test_case("int8")]
#[test_case("uint8")]
#[test_case("int16")]
#[test_case("uint16")]
#[test_case("int32")]
#[test_case("uint32")]
#[test_case("varint32")]
#[test_case("varuint32")]
#[test_case("int64")]
#[test_case("uint64")]
#[test_case("varint62")]
#[test_case("varuint62")]
#[test_case("float32")]
#[test_case("float64")]
#[test_case("string")]
fn optional_builtin_types_are_allowed(type_name: &str) {
let slice = format!(
"
module Test
struct S {{
a: {type_name}?
}}
"
);
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(field.data_type.is_optional);
}
#[test_case("struct Foo {}"; "r#struct")]
#[test_case("unchecked enum Foo: uint8 {}"; "r#enum")]
#[test_case("custom Foo"; "custom type")]
fn optional_user_defined_types_are_allowed(definition: &str) {
let slice = format!(
"
module Test
{definition}
struct S {{
a: Foo?
}}
"
);
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(field.data_type.is_optional);
}
#[test]
fn optional_results_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Result<varuint62, string>?
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(field.data_type.is_optional);
let Types::ResultType(result_type) = field.data_type().concrete_type() else { panic!() };
assert!(!result_type.success_type.is_optional);
assert!(!result_type.failure_type.is_optional);
}
#[test]
fn results_with_optional_success_types_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Result<varuint62?, string>
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(!field.data_type.is_optional);
let Types::ResultType(result_type) = field.data_type().concrete_type() else { panic!() };
assert!(result_type.success_type.is_optional);
assert!(!result_type.failure_type.is_optional);
}
#[test]
fn results_with_optional_failure_types_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Result<varuint62, string?>
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(!field.data_type.is_optional);
let Types::ResultType(result_type) = field.data_type().concrete_type() else { panic!() };
assert!(!result_type.success_type.is_optional);
assert!(result_type.failure_type.is_optional);
}
#[test]
fn optional_sequences_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Sequence<int32>?
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(field.data_type.is_optional);
let Types::Sequence(sequence) = field.data_type().concrete_type() else { panic!() };
assert!(!sequence.element_type.is_optional);
}
#[test]
fn sequences_with_optional_elements_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Sequence<bool?>
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(!field.data_type.is_optional);
let Types::Sequence(sequence) = field.data_type().concrete_type() else { panic!() };
assert!(sequence.element_type.is_optional);
}
#[test]
fn optional_dictionaries_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Dictionary<varuint62, string>?
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(field.data_type.is_optional);
let Types::Dictionary(dictionary) = field.data_type().concrete_type() else { panic!() };
assert!(!dictionary.key_type.is_optional);
assert!(!dictionary.value_type.is_optional);
}
#[test]
fn dictionaries_with_optional_keys_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Dictionary<varuint62?, string>
}
";
let ast = parse(slice, None).ast;
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(!field.data_type.is_optional);
let Types::Dictionary(dictionary) = field.data_type().concrete_type() else { panic!() };
assert!(dictionary.key_type.is_optional);
assert!(!dictionary.value_type.is_optional);
}
#[test]
fn dictionaries_with_optional_values_are_parsed_correctly() {
let slice = "
module Test
struct S {
a: Dictionary<varuint62, string?>
}
";
let ast = parse_for_ast(slice);
let field = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(!field.data_type.is_optional);
let Types::Dictionary(dictionary) = field.data_type().concrete_type() else { panic!() };
assert!(!dictionary.key_type.is_optional);
assert!(dictionary.value_type.is_optional);
}
#[test]
fn operations_can_use_a_mix_of_optional_and_required_parameters() {
let slice = "
module Test
interface I {
op(a: bool?, b: string, c: stream int32?) -> (x: float32, y: uint8?, z: stream int16)
}
";
let ast = parse_for_ast(slice);
let parameter_a = ast.find_element::<Parameter>("Test::I::op::a").unwrap();
assert!(parameter_a.data_type().is_optional);
let parameter_b = ast.find_element::<Parameter>("Test::I::op::b").unwrap();
assert!(!parameter_b.data_type().is_optional);
let parameter_c = ast.find_element::<Parameter>("Test::I::op::c").unwrap();
assert!(parameter_c.data_type().is_optional);
let return_x = ast.find_element::<Parameter>("Test::I::op::x").unwrap();
assert!(!return_x.data_type().is_optional);
let return_y = ast.find_element::<Parameter>("Test::I::op::y").unwrap();
assert!(return_y.data_type().is_optional);
let return_z = ast.find_element::<Parameter>("Test::I::op::z").unwrap();
assert!(!return_z.data_type().is_optional);
}
#[test]
fn operations_can_return_single_optional_types() {
let slice = "
module Test
interface I {
op() -> float64?
}
";
let ast = parse_for_ast(slice);
let return_value = ast.find_element::<Parameter>("Test::I::op::returnValue").unwrap();
assert!(return_value.data_type().is_optional);
}
#[test]
fn structs_can_use_a_mix_of_optional_and_required_fields() {
let slice = "
module Test
struct S {
a: bool?
b: string
c: int32?
}
";
let ast = parse_for_ast(slice);
let member_a = ast.find_element::<Field>("Test::S::a").unwrap();
assert!(member_a.data_type().is_optional);
let member_b = ast.find_element::<Field>("Test::S::b").unwrap();
assert!(!member_b.data_type().is_optional);
let member_c = ast.find_element::<Field>("Test::S::c").unwrap();
assert!(member_c.data_type().is_optional);
}
}
}