allow use of types as namespaces for constructing values.
Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
parent
c66bca5829
commit
3db9828fe8
|
@ -1,7 +1,6 @@
|
||||||
use super::Chain;
|
use super::Chain;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::well_known,
|
ast::well_known,
|
||||||
expr::UntypedExpr,
|
|
||||||
parser::{token::Token, ParseError},
|
parser::{token::Token, ParseError},
|
||||||
};
|
};
|
||||||
use chumsky::prelude::*;
|
use chumsky::prelude::*;
|
||||||
|
@ -11,25 +10,11 @@ pub(crate) fn parser() -> impl Parser<Token, Chain, Error = ParseError> {
|
||||||
.ignore_then(choice((
|
.ignore_then(choice((
|
||||||
select! { Token::Else => well_known::VALIDATOR_ELSE.to_string() },
|
select! { Token::Else => well_known::VALIDATOR_ELSE.to_string() },
|
||||||
select! { Token::Name { name } => name, },
|
select! { Token::Name { name } => name, },
|
||||||
|
select! { Token::UpName { name } => name, },
|
||||||
)))
|
)))
|
||||||
.map_with_span(Chain::FieldAccess)
|
.map_with_span(Chain::FieldAccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn constructor() -> impl Parser<Token, UntypedExpr, Error = ParseError> {
|
|
||||||
select! {Token::Name { name } => name}
|
|
||||||
.map_with_span(|module, span| (module, span))
|
|
||||||
.then_ignore(just(Token::Dot))
|
|
||||||
.then(select! {Token::UpName { name } => name})
|
|
||||||
.map_with_span(|((module, m_span), name), span| UntypedExpr::FieldAccess {
|
|
||||||
location: span,
|
|
||||||
label: name,
|
|
||||||
container: Box::new(UntypedExpr::Var {
|
|
||||||
location: m_span,
|
|
||||||
name: module,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::assert_expr;
|
use crate::assert_expr;
|
||||||
|
|
|
@ -53,7 +53,6 @@ pub fn chain_start<'a>(
|
||||||
pair(expression.clone()),
|
pair(expression.clone()),
|
||||||
record_update(expression.clone()),
|
record_update(expression.clone()),
|
||||||
record(expression.clone()),
|
record(expression.clone()),
|
||||||
field_access::constructor(),
|
|
||||||
and_or_chain(expression.clone()),
|
and_or_chain(expression.clone()),
|
||||||
var(),
|
var(),
|
||||||
tuple(expression.clone()),
|
tuple(expression.clone()),
|
||||||
|
|
|
@ -2279,7 +2279,7 @@ fn use_type_as_namespace_for_patterns() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
use thing.{Foo}
|
use thing.{Foo}
|
||||||
|
|
||||||
pub fn bar(foo: Foo) {
|
fn bar(foo: Foo) {
|
||||||
when foo is {
|
when foo is {
|
||||||
Foo.A -> True
|
Foo.A -> True
|
||||||
Foo.B -> False
|
Foo.B -> False
|
||||||
|
@ -2335,8 +2335,6 @@ fn use_wrong_type_as_namespace_for_patterns_fails() {
|
||||||
C
|
C
|
||||||
D
|
D
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
|
@ -2361,6 +2359,237 @@ fn use_wrong_type_as_namespace_for_patterns_fails() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_type_as_namespace_for_constructors() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo.{Foo}
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
and {
|
||||||
|
Foo.I(42) == foo.I(42),
|
||||||
|
foo.B(True) == Foo.B(True),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(matches!(result, Ok(..)), "{result:#?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_type_as_nested_namespace_for_constructors() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
trace foo.Foo.I(42)
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(matches!(result, Ok(..)), "{result:#?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_type_as_namespace_unknown_constructor() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo.{Foo}
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
Foo.A == Foo.I(42)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&result,
|
||||||
|
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "A" && warnings.is_empty(),
|
||||||
|
),
|
||||||
|
"{result:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_wrong_type_as_namespace() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Bar {
|
||||||
|
S(String)
|
||||||
|
L(List<Int>)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo.{Foo}
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
trace Foo.S(@"wut")
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&result,
|
||||||
|
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "S" && warnings.is_empty(),
|
||||||
|
),
|
||||||
|
"{result:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_wrong_nested_type_as_namespace() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Bar {
|
||||||
|
S(String)
|
||||||
|
L(List<Int>)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
trace foo.Foo.S(@"wut")
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&result,
|
||||||
|
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "S" && warnings.is_empty(),
|
||||||
|
),
|
||||||
|
"{result:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_private_type_as_nested_namespace_fails() {
|
||||||
|
let dependency = r#"
|
||||||
|
type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
trace foo.Foo.I(42)
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&result,
|
||||||
|
Err((warnings, Error::UnknownModuleType { name, .. })) if name == "Foo" && warnings.is_empty(),
|
||||||
|
),
|
||||||
|
"{result:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_opaque_type_as_namespace_for_constructors_fails() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub opaque type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo.{Foo}
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
trace Foo.I(42)
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&result,
|
||||||
|
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "I" && warnings.is_empty(),
|
||||||
|
),
|
||||||
|
"{result:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_opaque_type_as_nested_namespace_for_constructors_fails() {
|
||||||
|
let dependency = r#"
|
||||||
|
pub opaque type Foo {
|
||||||
|
I(Int)
|
||||||
|
B(Bool)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let source_code = r#"
|
||||||
|
use foo
|
||||||
|
|
||||||
|
test my_test() {
|
||||||
|
trace foo.Foo.I(42)
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
&result,
|
||||||
|
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "I" && warnings.is_empty(),
|
||||||
|
),
|
||||||
|
"{result:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forbid_importing_or_using_opaque_constructors() {
|
fn forbid_importing_or_using_opaque_constructors() {
|
||||||
let dependency = r#"
|
let dependency = r#"
|
||||||
|
|
|
@ -372,6 +372,53 @@ impl<'a> Environment<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
|
pub fn get_fully_qualified_value_constructor(
|
||||||
|
&self,
|
||||||
|
(module_name, module_location): (&str, Span),
|
||||||
|
(type_name, type_location): (&str, Span),
|
||||||
|
(value, value_location): (&str, Span),
|
||||||
|
) -> Result<&ValueConstructor, Error> {
|
||||||
|
let (_, module) =
|
||||||
|
self.imported_modules
|
||||||
|
.get(module_name)
|
||||||
|
.ok_or_else(|| Error::UnknownModule {
|
||||||
|
location: module_location,
|
||||||
|
name: module_name.to_string(),
|
||||||
|
known_modules: self
|
||||||
|
.importable_modules
|
||||||
|
.keys()
|
||||||
|
.map(|t| t.to_string())
|
||||||
|
.collect(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let constructors =
|
||||||
|
module
|
||||||
|
.types_constructors
|
||||||
|
.get(type_name)
|
||||||
|
.ok_or_else(|| Error::UnknownModuleType {
|
||||||
|
location: type_location,
|
||||||
|
name: type_name.to_string(),
|
||||||
|
module_name: module.name.clone(),
|
||||||
|
type_constructors: module.types.keys().map(|t| t.to_string()).collect(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let unknown_type_constructor = || Error::UnknownTypeConstructor {
|
||||||
|
location: value_location,
|
||||||
|
name: value.to_string(),
|
||||||
|
constructors: constructors.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !constructors.iter().any(|s| s.as_str() == value) {
|
||||||
|
return Err(unknown_type_constructor());
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
.values
|
||||||
|
.get(value)
|
||||||
|
.ok_or_else(unknown_type_constructor)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::result_large_err)]
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn get_type_constructor_mut(
|
pub fn get_type_constructor_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -464,35 +511,13 @@ impl<'a> Environment<'a> {
|
||||||
types: self.known_type_names(),
|
types: self.known_type_names(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (_, module) =
|
|
||||||
self.imported_modules
|
|
||||||
.get(&parent_type.module)
|
|
||||||
.ok_or_else(|| Error::UnknownModule {
|
|
||||||
name: parent_type.module.to_string(),
|
|
||||||
known_modules: self
|
|
||||||
.importable_modules
|
|
||||||
.keys()
|
|
||||||
.map(|t| t.to_string())
|
|
||||||
.collect(),
|
|
||||||
location,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
self.unused_modules.remove(&parent_type.module);
|
self.unused_modules.remove(&parent_type.module);
|
||||||
|
|
||||||
let empty_vec = vec![];
|
self.get_fully_qualified_value_constructor(
|
||||||
let constructors = module.types_constructors.get(t).unwrap_or(&empty_vec);
|
(parent_type.module.as_str(), location),
|
||||||
|
(t, location),
|
||||||
let unknown_type_constructor = || Error::UnknownTypeConstructor {
|
(name, location.map(|start, end| (start + t.len() + 1, end))),
|
||||||
location,
|
)
|
||||||
name: name.to_string(),
|
|
||||||
constructors: constructors.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !constructors.iter().any(|constructor| constructor == name) {
|
|
||||||
return Err(unknown_type_constructor());
|
|
||||||
}
|
|
||||||
|
|
||||||
module.values.get(name).ok_or_else(unknown_type_constructor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Namespace::Module(m)) => {
|
Some(Namespace::Module(m)) => {
|
||||||
|
|
|
@ -1068,6 +1068,41 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
return shortcircuit;
|
return shortcircuit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In case where we find an uppercase var name in a record access chain, we treat the type
|
||||||
|
// as a namespace and lookup the next constructor as if it were imported from the module's
|
||||||
|
// the type originally belong to.
|
||||||
|
match container {
|
||||||
|
UntypedExpr::Var {
|
||||||
|
name: ref type_name,
|
||||||
|
location: type_location,
|
||||||
|
} if TypeConstructor::might_be(type_name) => {
|
||||||
|
return self.infer_type_constructor_access(
|
||||||
|
(type_name, type_location),
|
||||||
|
(&label, access_location),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
UntypedExpr::FieldAccess {
|
||||||
|
location: type_location,
|
||||||
|
label: ref type_name,
|
||||||
|
container: ref type_container,
|
||||||
|
} if TypeConstructor::might_be(type_name) => {
|
||||||
|
if let UntypedExpr::Var {
|
||||||
|
name: ref module_name,
|
||||||
|
location: module_location,
|
||||||
|
} = type_container.as_ref()
|
||||||
|
{
|
||||||
|
return self.infer_inner_type_constructor_access(
|
||||||
|
(module_name, *module_location),
|
||||||
|
(type_name, type_location),
|
||||||
|
(&label, access_location),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
// Attempt to infer the container as a record access. If that fails, we may be shadowing the name
|
// Attempt to infer the container as a record access. If that fails, we may be shadowing the name
|
||||||
// of an imported module, so attempt to infer the container as a module access.
|
// of an imported module, so attempt to infer the container as a module access.
|
||||||
// TODO: Remove this cloning
|
// TODO: Remove this cloning
|
||||||
|
@ -1075,7 +1110,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
Ok(record_access) => Ok(record_access),
|
Ok(record_access) => Ok(record_access),
|
||||||
|
|
||||||
Err(err) => match container {
|
Err(err) => match container {
|
||||||
UntypedExpr::Var { name, location } => {
|
UntypedExpr::Var { name, location } if !TypeConstructor::might_be(&name) => {
|
||||||
let module_access =
|
let module_access =
|
||||||
self.infer_module_access(&name, label, &location, access_location);
|
self.infer_module_access(&name, label, &location, access_location);
|
||||||
|
|
||||||
|
@ -1162,6 +1197,51 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
|
fn infer_type_constructor_access(
|
||||||
|
&mut self,
|
||||||
|
(type_name, type_location): (&str, Span),
|
||||||
|
(label, label_location): (&str, Span),
|
||||||
|
) -> Result<TypedExpr, Error> {
|
||||||
|
let parent_type = self
|
||||||
|
.environment
|
||||||
|
.module_types
|
||||||
|
.get(type_name)
|
||||||
|
.ok_or_else(|| Error::UnknownType {
|
||||||
|
location: type_location,
|
||||||
|
name: type_name.to_string(),
|
||||||
|
types: self.environment.known_type_names(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let module_name = parent_type.module.clone();
|
||||||
|
|
||||||
|
self.infer_inner_type_constructor_access(
|
||||||
|
(module_name.as_str(), type_location),
|
||||||
|
(type_name, type_location),
|
||||||
|
(label, label_location),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
|
fn infer_inner_type_constructor_access(
|
||||||
|
&mut self,
|
||||||
|
(module_name, module_location): (&str, Span),
|
||||||
|
(type_name, type_location): (&str, Span),
|
||||||
|
(label, label_location): (&str, Span),
|
||||||
|
) -> Result<TypedExpr, Error> {
|
||||||
|
self.environment.get_fully_qualified_value_constructor(
|
||||||
|
(module_name, module_location),
|
||||||
|
(type_name, type_location),
|
||||||
|
(label, label_location),
|
||||||
|
)?;
|
||||||
|
self.infer_module_access(
|
||||||
|
module_name,
|
||||||
|
label.to_string(),
|
||||||
|
&type_location,
|
||||||
|
label_location,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::result_large_err)]
|
#[allow(clippy::result_large_err)]
|
||||||
fn infer_record_access(
|
fn infer_record_access(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1169,9 +1249,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
label: String,
|
label: String,
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> Result<TypedExpr, Error> {
|
) -> Result<TypedExpr, Error> {
|
||||||
// Infer the type of the (presumed) record
|
// Infer the type of the (presumed) record.
|
||||||
let record = self.infer(record)?;
|
let record = self.infer(record)?;
|
||||||
|
|
||||||
self.infer_known_record_access(record, label, location)
|
self.infer_known_record_access(record, label, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue