use super::super::common::*;
use luau_common::flags;
#[test]
fn parse_declarations() {
with_parse_ok_with_declarations(
r#"
declare foo: number
declare function bar(x: number): string
declare function var(...: any)
"#,
|result| {
let [foo, bar, var] = statement_kinds(result.root.as_slice()).exact();
let foo = foo
.as_declare_global()
.expect("expected global declaration");
let bar = bar
.as_declare_function()
.expect("expected function declaration");
let var = var
.as_declare_function()
.expect("expected vararg function declaration");
assert_eq!(foo.name, "foo");
assert_eq!(foo.name_location, loc!(pos!(1, 16), pos!(1, 19)));
assert!(matches!(&foo.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(bar.name, "bar");
assert_eq!(bar.name_location, loc!(pos!(2, 25), pos!(2, 28)));
assert_eq!(bar.params.types.len(), 1);
assert!(matches!(
&bar.return_types.kind(),
TypePackKind::Explicit { type_list: TypeList { types, .. } } if types.len() == 1
));
assert_eq!(var.name, "var");
assert_eq!(var.name_location, loc!(pos!(3, 25), pos!(3, 28)));
assert!(var.variadic);
assert_eq!(var.vararg_location, loc!(pos!(3, 29), pos!(3, 32)));
assert!(var.params.tail_type.is_some());
parse_errors_with_declarations("declare function foo(x)")
.assert_first_message("All declaration parameters must be annotated");
parse_errors_with_declarations("declare foo").assert_first_message(
"Expected ':' when parsing global variable declaration, got <eof>",
);
},
);
}
#[test]
fn parse_global_declaration_called_class() {
let _flag = flags::LuauAllowGlobalDeclarationToBeCalledClass.scoped(true);
with_parse(
r#"
declare class: { x: number }
"#,
ParseOptions::default().with_declaration_syntax(true),
|result| {
let result = result.unwrap();
let [statement] = statement_kinds(result.root.as_slice()).exact();
let declaration = statement
.as_declare_global()
.expect("expected global declaration");
assert_eq!(declaration.name, "class");
assert!(matches!(declaration.ty.kind(), TypeKind::Table { .. }));
},
);
}
#[test]
fn parse_class_declarations_unaffected_by_global_flag() {
let _flag = flags::LuauAllowGlobalDeclarationToBeCalledClass.scoped(true);
with_parse(
r#"
declare class Foo
prop: number
end
"#,
ParseOptions::default().with_declaration_syntax(true),
|result| {
let result = result.unwrap();
let [statement] = statement_kinds(result.root.as_slice()).exact();
let declaration = statement
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(declaration.name, "Foo");
},
);
}
#[test]
fn deprecated_declare_class_syntax_is_rejected() {
let _disallow = flags::LuauDisallowExternClassInTypeDefinitions.scoped(true);
parse_errors_with_declarations(
r#"
declare class Foo
prop: number
end
"#,
)
.assert_first_message("Expected ':' when parsing global variable declaration, got 'Foo'");
}
#[test]
fn parse_class_declarations() {
with_parse_ok_with_declarations(
r#"
declare class Foo
prop: number
function method(self, foo: number): string
end
declare class Bar extends Foo
prop2: string
end
"#,
|result| {
let [base, subclass] = statement_kinds(result.root.as_slice()).exact();
let base = base
.as_declare_extern_type()
.expect("expected extern type declaration");
let subclass = subclass
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(base.name, "Foo");
assert_eq!(base.super_name, None);
assert_eq!(base.props.len(), 2);
let prop = &base.props[0];
assert_eq!(prop.name, "prop");
assert_eq!(prop.name_location, loc!(pos!(2, 12), pos!(2, 16)));
assert!(matches!(&prop.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop.location, loc!(pos!(2, 12), pos!(2, 24)));
let method = &base.props[1];
assert_eq!(method.name, "method");
assert_eq!(method.name_location, loc!(pos!(3, 21), pos!(3, 27)));
assert!(matches!(&method.ty.kind(), TypeKind::Function { .. }));
assert_eq!(method.location, loc!(pos!(3, 12), pos!(3, 54)));
assert!(method.is_method);
assert_eq!(subclass.name, "Bar");
assert_eq!(
subclass.super_name.map(|name| name.bytes()),
Some(b"Foo".as_slice())
);
assert_eq!(subclass.props.len(), 1);
let prop2 = &subclass.props[0];
assert_eq!(prop2.name, "prop2");
assert_eq!(prop2.name_location, loc!(pos!(7, 12), pos!(7, 17)));
assert!(matches!(&prop2.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop2.location, loc!(pos!(7, 12), pos!(7, 25)));
},
);
}
#[test]
fn parse_extern_type_declarations() {
with_parse_ok_with_declarations(
r#"
declare extern type Foo with
prop: number
function method(self, foo: number): string
end
declare extern type Bar extends Foo with
prop2: string
end
"#,
|result| {
let [base, subclass] = statement_kinds(result.root.as_slice()).exact();
let base = base
.as_declare_extern_type()
.expect("expected extern type declaration");
let subclass = subclass
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(base.name, "Foo");
assert!(base.super_name.is_none());
assert_eq!(base.props.len(), 2);
let prop = &base.props[0];
assert_eq!(prop.name, "prop");
assert_eq!(prop.name_location, loc!(pos!(2, 12), pos!(2, 16)));
assert!(matches!(&prop.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop.location, loc!(pos!(2, 12), pos!(2, 24)));
let method = &base.props[1];
assert_eq!(method.name, "method");
assert_eq!(method.name_location, loc!(pos!(3, 21), pos!(3, 27)));
assert!(matches!(&method.ty.kind(), TypeKind::Function { .. }));
assert_eq!(method.location, loc!(pos!(3, 12), pos!(3, 54)));
assert!(method.is_method);
assert_eq!(subclass.name, "Bar");
assert_eq!(
subclass.super_name.map(|name| name.bytes()),
Some(b"Foo".as_slice())
);
assert_eq!(subclass.props.len(), 1);
let prop2 = &subclass.props[0];
assert_eq!(prop2.name, "prop2");
assert_eq!(prop2.name_location, loc!(pos!(7, 12), pos!(7, 17)));
assert!(matches!(&prop2.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop2.location, loc!(pos!(7, 12), pos!(7, 25)));
},
);
}
#[test]
fn string_extern_type_properties_are_not_interned() {
with_parse_ok_with_declarations(
r#"
declare extern type Foo with
foo: number
["foo"]: string
end
"#,
|result| {
let [statement] = statement_kinds(result.root.as_slice()).exact();
let declaration = statement
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(declaration.props.len(), 2);
assert_eq!(declaration.props[0].name.bytes(), b"foo");
assert_eq!(declaration.props[1].name.bytes(), b"foo");
assert_ne!(declaration.props[0].name, declaration.props[1].name);
},
);
}
#[test]
fn parse_extern_type_declarations_missing_with() {
with_parse_ok_with_declarations(
r#"
declare extern type Foo
prop: number
function method(self, foo: number): string
end
declare extern type Bar extends Foo
prop2: string
end
"#,
|result| {
assert_eq!(result.metadata.errors.len(), 2);
assert_eq!(
result.metadata.errors[0].message,
"Expected `with` keyword before listing properties of the external type, but got prop instead"
);
assert_eq!(
result.metadata.errors[1].message,
"Expected `with` keyword before listing properties of the external type, but got prop2 instead"
);
let [base, subclass] = statement_kinds(result.root.as_slice()).exact();
let base = base
.as_declare_extern_type()
.expect("expected extern type declaration");
let subclass = subclass
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(base.name, "Foo");
assert!(base.super_name.is_none());
assert_eq!(base.props.len(), 2);
let prop = &base.props[0];
assert_eq!(prop.name, "prop");
assert_eq!(prop.name_location, loc!(pos!(2, 12), pos!(2, 16)));
assert!(matches!(&prop.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop.location, loc!(pos!(2, 12), pos!(2, 24)));
let method = &base.props[1];
assert_eq!(method.name, "method");
assert_eq!(method.name_location, loc!(pos!(3, 21), pos!(3, 27)));
assert!(matches!(&method.ty.kind(), TypeKind::Function { .. }));
assert_eq!(method.location, loc!(pos!(3, 12), pos!(3, 54)));
assert!(method.is_method);
assert_eq!(subclass.name, "Bar");
assert_eq!(
subclass.super_name.map(|name| name.bytes()),
Some(b"Foo".as_slice())
);
assert_eq!(subclass.props.len(), 1);
let prop2 = &subclass.props[0];
assert_eq!(prop2.name, "prop2");
assert_eq!(prop2.name_location, loc!(pos!(7, 12), pos!(7, 17)));
assert!(matches!(&prop2.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop2.location, loc!(pos!(7, 12), pos!(7, 25)));
},
);
}
#[test]
fn parse_extern_type_declarations_no_indexer() {
with_parse_ok_with_declarations(
r#"
declare extern type Foo with
prop: number
function method(self, foo: number): string
end
declare extern type Bar extends Foo with
prop2: string
end
"#,
|result| {
let [base, subclass] = statement_kinds(result.root.as_slice()).exact();
let base = base
.as_declare_extern_type()
.expect("expected extern type declaration");
let subclass = subclass
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(base.name, "Foo");
assert!(base.super_name.is_none());
assert!(base.indexer.is_none());
assert_eq!(base.props.len(), 2);
let prop = &base.props[0];
assert_eq!(prop.name, "prop");
assert_eq!(prop.name_location, loc!(pos!(2, 12), pos!(2, 16)));
assert!(matches!(&prop.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop.location, loc!(pos!(2, 12), pos!(2, 24)));
assert!(!prop.is_method);
let method = &base.props[1];
assert_eq!(method.name, "method");
assert_eq!(method.name_location, loc!(pos!(3, 21), pos!(3, 27)));
assert!(matches!(&method.ty.kind(), TypeKind::Function { .. }));
assert_eq!(method.location, loc!(pos!(3, 12), pos!(3, 54)));
assert!(method.is_method);
assert_eq!(subclass.name, "Bar");
assert_eq!(
subclass.super_name.map(|name| name.bytes()),
Some(b"Foo".as_slice())
);
assert!(subclass.indexer.is_none());
assert_eq!(subclass.props.len(), 1);
let prop2 = &subclass.props[0];
assert_eq!(prop2.name, "prop2");
assert_eq!(prop2.name_location, loc!(pos!(7, 12), pos!(7, 17)));
assert!(matches!(&prop2.ty.kind(), TypeKind::Reference { .. }));
assert_eq!(prop2.location, loc!(pos!(7, 12), pos!(7, 25)));
},
);
}
#[test]
fn parse_extern_type_declarations_missing_with_no_indexer() {
with_parse_ok_with_declarations(
r#"
declare extern type Foo
prop: number
function method(self, foo: number): string
end
declare extern type Bar extends Foo
prop2: string
end
"#,
|result| {
assert_eq!(result.metadata.errors.len(), 2);
assert_eq!(
result.metadata.errors[0].message,
"Expected `with` keyword before listing properties of the external type, but got prop instead"
);
assert_eq!(
result.metadata.errors[1].message,
"Expected `with` keyword before listing properties of the external type, but got prop2 instead"
);
let [base, subclass] = statement_kinds(result.root.as_slice()).exact();
let base = base
.as_declare_extern_type()
.expect("expected extern type declaration");
let subclass = subclass
.as_declare_extern_type()
.expect("expected extern type declaration");
assert_eq!(base.name, "Foo");
assert!(base.super_name.is_none());
assert!(base.indexer.is_none());
assert_eq!(base.props.len(), 2);
let prop = &base.props[0];
assert_eq!(prop.name, "prop");
assert_eq!(prop.name_location, loc!(pos!(2, 12), pos!(2, 16)));
assert!(
matches!(&prop.ty.kind(), TypeKind::Reference { name, .. } if name == "number"
)
);
assert_eq!(prop.location, loc!(pos!(2, 12), pos!(2, 24)));
assert!(!prop.is_method);
let method = &base.props[1];
assert_eq!(method.name, "method");
assert_eq!(method.name_location, loc!(pos!(3, 21), pos!(3, 27)));
assert!(matches!(&method.ty.kind(), TypeKind::Function { .. }));
assert_eq!(method.location, loc!(pos!(3, 12), pos!(3, 54)));
assert!(method.is_method);
assert_eq!(subclass.name, "Bar");
assert_eq!(
subclass.super_name.map(|name| name.bytes()),
Some(b"Foo".as_slice())
);
assert!(subclass.indexer.is_none());
assert_eq!(subclass.props.len(), 1);
let prop2 = &subclass.props[0];
assert_eq!(prop2.name, "prop2");
assert_eq!(prop2.name_location, loc!(pos!(7, 12), pos!(7, 17)));
assert!(
matches!(&prop2.ty.kind(), TypeKind::Reference { name, .. } if name == "string"
)
);
assert_eq!(prop2.location, loc!(pos!(7, 12), pos!(7, 25)));
},
);
}
#[test]
fn class_method_properties() {
with_parse_ok_with_declarations(
r#"
declare class Foo
function method(foo: number)
function method2(self)
end
"#,
|first_parameter| {
assert_eq!(
first_parameter.metadata.errors[0].message,
"'self' must be present as the unannotated first parameter"
);
let [statement] = statement_kinds(first_parameter.root.as_slice()).exact();
assert_eq!(
statement
.as_declare_extern_type()
.expect("expected extern type declaration")
.props
.len(),
2
);
with_parse_ok_with_declarations(
r#"
declare class Foo
function method(self, foo)
function method2()
end
"#,
|missing_annotation| {
assert_eq!(
missing_annotation.metadata.errors[0].message,
"All declaration parameters aside from 'self' must be annotated"
);
let [statement] = statement_kinds(missing_annotation.root.as_slice()).exact();
assert_eq!(
statement
.as_declare_extern_type()
.expect("expected extern type declaration")
.props
.len(),
2
)
},
);
},
);
}
#[test]
fn class_indexer() {
with_parse_ok_with_declarations(
r#"
declare class Foo
prop: boolean
[string]: number
end
"#,
|result| {
let [statement] = statement_kinds(result.root.as_slice()).exact();
let Some(indexer) = statement
.as_declare_extern_type()
.expect("expected class declaration")
.indexer
else {
panic!("expected indexer");
};
assert!(
matches!(&indexer.index_type.kind(), TypeKind::Reference { name, .. } if name == "string"
)
);
assert!(
matches!(&indexer.result_type.kind(), TypeKind::Reference { name, .. } if name == "number"
)
);
with_parse_ok_with_declarations(
r#"
declare class Foo
[string]: number
[number]: number
end
"#,
|duplicate| {
assert_eq!(
duplicate.metadata.errors[0].message,
"Cannot have more than one indexer on an extern type"
);
let [statement] = statement_kinds(duplicate.root.as_slice()).exact();
assert!(
statement
.as_declare_extern_type()
.expect("expected extern type declaration")
.indexer
.is_some()
)
},
);
},
);
}
#[test]
fn variadic_definition_parsing() {
with_parse_ok_with_declarations(
r#"
declare function foo(...: string): ...string
declare class Foo
function a(self, ...: string): ...string
end
"#,
|_result| {
parse_errors_with_declarations("declare function foo(...)")
.assert_first_message("All declaration parameters must be annotated");
parse_errors_with_declarations("declare class Foo function a(self, ...) end")
.assert_first_message(
"All declaration parameters aside from 'self' must be annotated",
);
},
);
}
#[test]
fn parse_variadics() {
with_parse(
r#"
function foo(bar, ...: number): ...string
end
type Foo = (string, number, ...number) -> ...boolean
type Bar = () -> (number, ...boolean)
"#,
ParseOptions::default(),
|result| {
let result = result.unwrap();
let [function, foo_type, bar_type] = statement_kinds(result.root.as_slice()).exact();
let function = function
.as_function_declaration()
.expect("expected function declaration")
.function;
let foo_type = foo_type.as_type_alias().expect("expected type alias").ty;
let bar_type = bar_type.as_type_alias().expect("expected type alias").ty;
assert!(function.vararg);
assert!(function.vararg_annotation.is_some());
let TypeKind::Function {
arg_types,
return_types,
..
} = &(foo_type).kind()
else {
panic!("expected function type alias");
};
assert!(
matches!(arg_types, TypeList { types, tail_type: Some(_) } if types.len() == 2)
);
assert!(matches!(
&return_types.kind(),
TypePackKind::Variadic { .. }
));
let TypeKind::Function {
arg_types,
return_types,
..
} = &(bar_type).kind()
else {
panic!("expected function type alias");
};
assert!(
matches!(arg_types, TypeList { types, tail_type: None } if types.is_empty()
)
);
assert!(
matches!(&return_types.kind(), TypePackKind::Explicit { type_list: TypeList { types, tail_type: Some(tail) } } if types.len() == 1 && matches!(&tail.kind(), TypePackKind::Variadic { .. })
)
);
},
);
}
#[test]
fn missing_declaration_prop() {
parse_errors_with_declarations(
r#"
declare class Foo
a: number,
end
"#,
)
.assert_first_message("Expected identifier when parsing property name, got ','");
}