Merge pull request #1119 from aiken-lang/types-as-namespaces

Types as namespaces
This commit is contained in:
Matthias Benkort 2025-03-17 14:35:24 +01:00 committed by GitHub
commit 0d33d43da6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1149 additions and 219 deletions

View File

@ -5,6 +5,7 @@
### Added
- **aiken**: New `-S` flag on `check` and `build` that blocks the printing of warnings but it still shows the total warning count. @rvcas
- **aiken-lang**: Allow types to be used as namespaces for constructors. Importing each constructor variants independently is no longer required in neither pattern-matches nor value construction. One can simply use the type name as a prefix/namespace now. @KtorZ
### Changed

View File

@ -11,7 +11,7 @@ authors = [
"Kasey White <kwhitemsg@gmail.com>",
"KtorZ <matthias.benkort@gmail.com>",
]
rust-version = "1.70.0"
rust-version = "1.80.0"
build = "build.rs"
[dependencies]

View File

@ -1535,8 +1535,8 @@ impl BinOp {
}
}
pub type UntypedPattern = Pattern<(), ()>;
pub type TypedPattern = Pattern<PatternConstructor, Rc<Type>>;
pub type UntypedPattern = Pattern<(), (), Namespace>;
pub type TypedPattern = Pattern<PatternConstructor, Rc<Type>, String>;
impl TypedPattern {
pub fn var(name: &str) -> Self {
@ -1654,7 +1654,13 @@ impl TypedPattern {
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Pattern<Constructor, Type> {
pub enum Namespace {
Module(String),
Type(Option<String>, String),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Pattern<Constructor, Type, NamespaceKind> {
Int {
location: Span,
value: String,
@ -1707,7 +1713,7 @@ pub enum Pattern<Constructor, Type> {
location: Span,
name: String,
arguments: Vec<CallArg<Self>>,
module: Option<String>,
module: Option<NamespaceKind>,
constructor: Constructor,
spread_location: Option<Span>,
tipo: Type,
@ -1725,7 +1731,7 @@ pub enum Pattern<Constructor, Type> {
},
}
impl<A, B> Pattern<A, B> {
impl<A, B, C> Pattern<A, B, C> {
pub fn location(&self) -> Span {
match self {
Pattern::Assign { pattern, .. } => pattern.location(),
@ -2201,22 +2207,23 @@ impl<T: Default> AssignmentKind<T> {
}
}
pub type MultiPattern<PatternConstructor, Type> = Vec<Pattern<PatternConstructor, Type>>;
pub type MultiPattern<PatternConstructor, Type, NamespaceKind> =
Vec<Pattern<PatternConstructor, Type, NamespaceKind>>;
pub type UntypedMultiPattern = MultiPattern<(), ()>;
pub type TypedMultiPattern = MultiPattern<PatternConstructor, Rc<Type>>;
pub type UntypedMultiPattern = MultiPattern<(), (), Namespace>;
pub type TypedMultiPattern = MultiPattern<PatternConstructor, Rc<Type>, String>;
#[derive(Debug, Clone, PartialEq)]
pub struct UntypedClause {
pub location: Span,
pub patterns: Vec1<Pattern<(), ()>>,
pub patterns: Vec1<UntypedPattern>,
pub then: UntypedExpr,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TypedClause {
pub location: Span,
pub pattern: Pattern<PatternConstructor, Rc<Type>>,
pub pattern: TypedPattern,
pub then: TypedExpr,
}

View File

@ -12,6 +12,7 @@ use crate::{
},
IdGenerator,
};
use std::{collections::BTreeSet, sync::LazyLock};
use indexmap::IndexMap;
use std::{collections::HashMap, rc::Rc};
@ -25,6 +26,16 @@ use uplc::{
pub const PRELUDE: &str = "aiken";
pub const BUILTIN: &str = "aiken/builtin";
pub static INTERNAL_FUNCTIONS: LazyLock<BTreeSet<&'static str>> = LazyLock::new(|| {
let mut set = BTreeSet::new();
set.insert("diagnostic");
set.insert("do_from_int");
set.insert("encode_base16");
set.insert("enumerate");
set.insert("from_int");
set
});
/// Build a prelude that can be injected
/// into a compiler pipeline
pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {

View File

@ -2,16 +2,15 @@ pub(crate) use crate::{
ast::{
self, Annotation, ArgBy, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point,
ByteArrayFormatPreference, CallArg, Curve, DataType, DataTypeKey, DefinitionLocation,
Located, LogicalOpChainKind, ParsedCallArg, Pattern, RecordConstructorArg,
RecordUpdateSpread, Span, TraceKind, TypedArg, TypedAssignmentKind, TypedClause,
TypedDataType, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg,
UntypedAssignmentKind, UntypedClause, UntypedIfBranch, UntypedRecordUpdateArg,
Located, LogicalOpChainKind, ParsedCallArg, RecordConstructorArg, RecordUpdateSpread, Span,
TraceKind, TypedArg, TypedAssignmentKind, TypedClause, TypedDataType, TypedIfBranch,
TypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause,
UntypedIfBranch, UntypedRecordUpdateArg,
},
parser::token::Base,
tipo::{
check_replaceable_opaque_type, convert_opaque_type, lookup_data_type_by_tipo,
ModuleValueConstructor, PatternConstructor, Type, TypeVar, ValueConstructor,
ValueConstructorVariant,
ModuleValueConstructor, Type, TypeVar, ValueConstructor, ValueConstructorVariant,
},
};
use indexmap::IndexMap;
@ -109,7 +108,7 @@ pub enum TypedExpr {
location: Span,
tipo: Rc<Type>,
value: Box<Self>,
pattern: Pattern<PatternConstructor, Rc<Type>>,
pattern: TypedPattern,
kind: TypedAssignmentKind,
},

View File

@ -2,7 +2,7 @@ use crate::{
ast::{
Annotation, ArgBy, ArgName, ArgVia, AssignmentKind, AssignmentPattern, BinOp,
ByteArrayFormatPreference, CallArg, CurveType, DataType, Definition, Function,
LogicalOpChainKind, ModuleConstant, OnTestFailure, Pattern, RecordConstructor,
LogicalOpChainKind, ModuleConstant, Namespace, OnTestFailure, Pattern, RecordConstructor,
RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg,
TypedValidator, UnOp, UnqualifiedImport, UntypedArg, UntypedArgVia, UntypedAssignmentKind,
UntypedClause, UntypedDefinition, UntypedFunction, UntypedIfBranch, UntypedModule,
@ -1202,7 +1202,7 @@ impl<'comments> Formatter<'comments> {
&mut self,
name: &'a str,
args: &'a [CallArg<UntypedPattern>],
module: &'a Option<String>,
module: &'a Option<Namespace>,
spread_location: Option<Span>,
is_record: bool,
) -> Document<'a> {
@ -1217,7 +1217,15 @@ impl<'comments> Formatter<'comments> {
}
let name = match module {
Some(m) => m.to_doc().append(".").append(name),
Some(Namespace::Module(m)) | Some(Namespace::Type(None, m)) => {
m.to_doc().append(".").append(name)
}
Some(Namespace::Type(Some(m), c)) => m
.to_doc()
.append(".")
.append(c.as_str())
.append(".")
.append(name),
None => name.to_doc(),
};

View File

@ -1,6 +1,6 @@
use super::Chain;
use crate::{
expr::UntypedExpr,
ast::well_known,
parser::{token::Token, ParseError},
};
use chumsky::prelude::*;
@ -8,27 +8,13 @@ use chumsky::prelude::*;
pub(crate) fn parser() -> impl Parser<Token, Chain, Error = ParseError> {
just(Token::Dot)
.ignore_then(choice((
select! { Token::Else => "else".to_string() },
select! { Token::Else => well_known::VALIDATOR_ELSE.to_string() },
select! { Token::Name { name } => name, },
select! { Token::UpName { name } => name, },
)))
.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)]
mod tests {
use crate::assert_expr;

View File

@ -4,6 +4,7 @@ pub(crate) mod call;
pub(crate) mod field_access;
pub(crate) mod tuple_index;
#[derive(Debug)]
pub(crate) enum Chain {
Call(Vec<ParsedCallArg>, Span),
FieldAccess(String, Span),

View File

@ -53,7 +53,6 @@ pub fn chain_start<'a>(
pair(expression.clone()),
record_update(expression.clone()),
record(expression.clone()),
field_access::constructor(),
and_or_chain(expression.clone()),
var(),
tuple(expression.clone()),

View File

@ -16,6 +16,12 @@ pub fn parser(
.map_with_span(|module, span: ast::Span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(
select! {Token::UpName { name } => name}
.map_with_span(|name, span| (name, span))
.then_ignore(just(Token::Dot))
.or_not(),
)
.then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
.then(
choice((
@ -117,6 +123,12 @@ pub fn parser(
.map_with_span(|module, span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(
select! {Token::UpName { name } => name}
.map_with_span(|name, span| (name, span))
.then_ignore(just(Token::Dot))
.or_not(),
)
.then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
.then(
select! {Token::Name {name} => name}
@ -157,29 +169,52 @@ pub fn parser(
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
),
))
.map_with_span(|((module, (name, n_span)), arguments), span| {
let fun = if let Some((module, m_span)) = module {
UntypedExpr::FieldAccess {
location: m_span.union(n_span),
label: name,
container: Box::new(UntypedExpr::Var {
location: m_span,
name: module,
}),
}
} else {
UntypedExpr::Var {
location: n_span,
name,
}
};
.map_with_span(
|(((module, namespace), (label, label_span)), arguments), span| {
let fun = match (module, namespace) {
(Some((module, module_span)), Some((namespace, namespace_span))) => {
UntypedExpr::FieldAccess {
location: module_span.union(namespace_span).union(label_span),
label,
container: Box::new(UntypedExpr::FieldAccess {
location: module_span.union(namespace_span),
label: namespace,
container: Box::new(UntypedExpr::Var {
location: module_span,
name: module,
}),
}),
}
}
(None, Some((namespace, namespace_span))) => UntypedExpr::FieldAccess {
location: namespace_span.union(label_span),
label,
container: Box::new(UntypedExpr::Var {
location: namespace_span,
name: namespace,
}),
},
(Some((module, module_span)), None) => UntypedExpr::FieldAccess {
location: module_span.union(label_span),
label,
container: Box::new(UntypedExpr::Var {
location: module_span,
name: module,
}),
},
(None, None) => UntypedExpr::Var {
location: label_span,
name: label,
},
};
UntypedExpr::Call {
arguments,
fun: Box::new(fun),
location: span,
}
})
UntypedExpr::Call {
arguments,
fun: Box::new(fun),
location: span,
}
},
)
}
#[cfg(test)]

View File

@ -1,29 +1,72 @@
use chumsky::prelude::*;
use crate::{
ast::{CallArg, Span, UntypedPattern},
ast::{CallArg, Namespace, Span, UntypedPattern},
parser::{error::ParseError, token::Token},
};
pub fn parser(
pattern: Recursive<'_, Token, UntypedPattern, ParseError>,
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
select! {Token::UpName { name } => name}
.then(args(pattern))
.map_with_span(
|(name, (arguments, spread_location, is_record)), location| {
UntypedPattern::Constructor {
is_record,
location,
name,
arguments,
module: None,
constructor: (),
spread_location,
tipo: (),
}
},
)
choice((
select! { Token::Name { name } => name }
.then(just(Token::Dot).ignore_then(select! {Token::UpName { name } => name}))
.then(
just(Token::Dot).ignore_then(
select! {Token::UpName { name } => name}.then(args(pattern.clone())),
),
)
.map_with_span(
|((module, namespace), (name, (arguments, spread_location, is_record))), span| {
UntypedPattern::Constructor {
is_record,
location: span,
name,
arguments,
module: Some(Namespace::Type(Some(module), namespace)),
constructor: (),
spread_location,
tipo: (),
}
},
),
select! { Token::UpName { name } => name }
.then(
just(Token::Dot).ignore_then(
select! {Token::UpName { name } => name}.then(args(pattern.clone())),
),
)
.map_with_span(
|(namespace, (name, (arguments, spread_location, is_record))), span| {
UntypedPattern::Constructor {
is_record,
location: span,
name,
arguments,
module: Some(Namespace::Type(None, namespace)),
constructor: (),
spread_location,
tipo: (),
}
},
),
select! {Token::UpName { name } => name}
.then(args(pattern))
.map_with_span(
|(name, (arguments, spread_location, is_record)), location| {
UntypedPattern::Constructor {
is_record,
location,
name,
arguments,
module: None,
constructor: (),
spread_location,
tipo: (),
}
},
),
))
}
pub(crate) fn args(
@ -102,4 +145,9 @@ mod tests {
fn constructor_module_select() {
assert_pattern!("module.Foo");
}
#[test]
fn constructor_type_select() {
assert_pattern!("Foo.Bar");
}
}

View File

@ -27,9 +27,9 @@ pub use var::parser as var;
pub fn parser() -> impl Parser<Token, UntypedPattern, Error = ParseError> {
recursive(|pattern| {
choice((
var(pattern.clone()),
pair(pattern.clone()),
constructor(pattern.clone()),
var(pattern.clone()),
discard(),
int(),
bytearray(),

View File

@ -8,7 +8,9 @@ Constructor {
name: "Foo",
arguments: [],
module: Some(
"module",
Module(
"module",
),
),
constructor: (),
spread_location: None,

View File

@ -0,0 +1,19 @@
---
source: crates/aiken-lang/src/parser/pattern/constructor.rs
description: "Code:\n\nFoo.Bar"
---
Constructor {
is_record: false,
location: 0..7,
name: "Bar",
arguments: [],
module: Some(
Type(
None,
"Foo",
),
),
constructor: (),
spread_location: None,
tipo: (),
}

View File

@ -9,31 +9,33 @@ use crate::{
pub fn parser(
expression: Recursive<'_, Token, UntypedPattern, ParseError>,
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
select! { Token::Name {name} => name }
.then(
just(Token::Dot)
.ignore_then(
select! {Token::UpName { name } => name}.then(constructor::args(expression)),
)
.or_not(),
)
.map_with_span(|(name, opt_pattern), span| {
if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern {
UntypedPattern::Constructor {
is_record,
location: span,
name: c_name,
arguments,
module: Some(name),
constructor: (),
spread_location,
tipo: (),
}
} else {
UntypedPattern::Var {
location: span,
name,
}
select! {
Token::Name {name} => name,
}
.then(
just(Token::Dot)
.ignore_then(
select! { Token::UpName { name } => name }.then(constructor::args(expression)),
)
.or_not(),
)
.map_with_span(|(name, opt_pattern), span| {
if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern {
UntypedPattern::Constructor {
is_record,
location: span,
name: c_name,
arguments,
module: Some(crate::ast::Namespace::Module(name)),
constructor: (),
spread_location,
tipo: (),
}
})
} else {
UntypedPattern::Var {
location: span,
name,
}
}
})
}

View File

@ -11,15 +11,23 @@ use crate::{
};
use std::collections::HashMap;
const DEFAULT_MODULE_NAME: &str = "my_module";
const DEFAULT_PACKAGE: &str = "test/project";
fn parse(source_code: &str) -> UntypedModule {
parse_as(source_code, DEFAULT_MODULE_NAME)
}
fn parse_as(source_code: &str, name: &str) -> UntypedModule {
let kind = ModuleKind::Lib;
let (ast, _) = parser::module(source_code, kind).expect("Failed to parse module");
let (mut ast, _) = parser::module(source_code, kind).expect("Failed to parse module");
ast.name = name.to_string();
ast
}
fn check_module(
ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
extra: Vec<UntypedModule>,
kind: ModuleKind,
tracing: Tracing,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
@ -31,27 +39,32 @@ fn check_module(
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
for (package, module) in extra {
for module in extra {
let mut warnings = vec![];
if module.name == DEFAULT_MODULE_NAME {
panic!("passed extra modules with default name! Use 'parse_as' to define tests instead of 'parse'.");
}
let typed_module = module
.infer(
&id_gen,
kind,
&package,
DEFAULT_PACKAGE,
&module_types,
Tracing::All(TraceLevel::Verbose),
&mut warnings,
None,
)
.expect("extra dependency did not compile");
module_types.insert(package.clone(), typed_module.type_info.clone());
module_types.insert(typed_module.name.clone(), typed_module.type_info.clone());
}
let result = ast.infer(
&id_gen,
kind,
"test/project",
DEFAULT_PACKAGE,
&module_types,
tracing,
&mut warnings,
@ -76,7 +89,7 @@ fn check_with_verbosity(
fn check_with_deps(
ast: UntypedModule,
extra: Vec<(String, UntypedModule)>,
extra: Vec<UntypedModule>,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, extra, ModuleKind::Lib, Tracing::verbose())
}
@ -2254,6 +2267,524 @@ fn allow_expect_into_opaque_type_constructor_without_typecasting_in_module() {
assert!(check(parse(source_code)).is_ok());
}
#[test]
fn use_imported_type_as_namespace_for_patterns() {
let dependency = r#"
pub type Credential {
VerificationKey(ByteArray)
Script(ByteArray)
}
"#;
let source_code = r#"
use cardano/address.{Credential, Script, VerificationKey}
pub fn compare(left: Credential, right: Credential) -> Ordering {
when left is {
Script(left) ->
when right is {
Script(right) -> Equal
_ -> Less
}
VerificationKey(left) ->
when right is {
Script(_) -> Greater
VerificationKey(right) -> Equal
}
}
}
"#;
let result = check_with_deps(
parse(source_code),
vec![(parse_as(dependency, "cardano/address"))],
);
assert!(matches!(result, Ok(..)), "{result:#?}");
}
#[test]
fn use_type_as_namespace_for_patterns() {
let dependency = r#"
pub type Foo {
A { a: Int }
B
}
"#;
let source_code = r#"
use thing.{Foo}
fn bar(foo: Foo) {
when foo is {
Foo.A { a } -> a > 10
Foo.B -> False
}
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]);
assert!(matches!(result, Ok(..)), "{result:#?}");
}
#[test]
fn use_nested_type_as_namespace_for_patterns() {
let dependency = r#"
pub type Foo {
A { a: Int }
B
}
"#;
let source_code = r#"
use foo.{Foo}
fn bar(x: Foo) {
when x is {
foo.Foo.A { a } -> a > 10
foo.Foo.B -> False
}
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
assert!(matches!(result, Ok(..)), "{result:#?}");
}
#[test]
fn use_opaque_type_as_namespace_for_patterns_fails() {
let dependency = r#"
pub opaque type Foo {
A { a: Int }
B
}
"#;
let source_code = r#"
use thing.{Foo}
fn bar(foo: Foo) {
when foo is {
Foo.A { a } -> a > 10
Foo.B -> False
}
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]);
assert!(
matches!(
&result,
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "A" && warnings.is_empty(),
),
"{result:#?}"
);
}
#[test]
fn use_wrong_type_as_namespace_for_patterns_fails() {
let dependency = r#"
pub type Foo {
A { a: Int }
B
}
pub type Bar {
C
D
}
"#;
let source_code = r#"
use thing.{Foo}
fn bar(foo: Foo) {
when foo is {
Foo.A { .. } -> True
Foo.D -> False
}
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]);
assert!(
matches!(
&result,
Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "D" && warnings.is_empty(),
),
"{result:#?}"
);
}
#[test]
fn use_type_as_namespace_for_constructors() {
let dependency = r#"
pub type Foo {
I { i: Int }
B(Bool)
}
"#;
let source_code = r#"
use foo.{Foo}
test my_test() {
and {
Foo.I { 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 { i: Int }
B(Bool)
}
"#;
let source_code = r#"
use foo
test my_test() {
trace foo.Foo.I { i: 42 }
trace foo.Foo.B(False)
Void
}
"#;
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_from_multi_level_module() {
let dependency = r#"
pub type Foo {
I { i: Int }
B(Bool)
}
"#;
let source_code = r#"
use foo/bar
test my_test() {
trace bar.Foo.I { i: 42 }
trace bar.Foo.B(True)
Void
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo/bar"))]);
assert!(matches!(result, Ok(..)), "{result:#?}");
}
#[test]
fn use_type_as_nested_namespace_for_constructors_from_module_alias() {
let dependency = r#"
pub type Foo {
I { i: Int }
B(Bool)
}
"#;
let source_code = r#"
use foo as bar
test my_test() {
trace bar.Foo.I { i: 42 }
trace bar.Foo.B(True)
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 { i: Int }
B(Bool)
}
"#;
let source_code = r#"
use foo.{Foo}
test my_test() {
Foo.I(42) == Foo.A
}
"#;
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 { i: Int }
B(Bool)
}
pub type Bar {
S { 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 { i: Int }
B(Bool)
}
pub type Bar {
S { 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 { i: Int }
B(Bool)
}
"#;
let source_code = r#"
use foo
test my_test() {
trace foo.Foo.I { 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 { 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 { 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]
fn use_non_imported_module_as_namespace() {
let dependency = r#"
pub type Foo {
I(Int)
B(Bool)
}
"#;
let source_code = r#"
test my_test() {
trace foo.Foo.I(14)
Void
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
assert!(
matches!(
&result,
Err((warnings, Error::UnknownModule { name, .. })) if name == "foo" && warnings.is_empty(),
),
"{result:#?}"
);
}
#[test]
fn invalid_type_field_access_chain() {
let dependency = r#"
pub type Foo {
I(Int)
B(Bool)
}
"#;
let source_code = r#"
use foo.{Foo}
test my_test() {
trace Foo.I.Int(42)
Void
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
assert!(
matches!(
&result,
Err((warnings, Error::InvalidFieldAccess { .. })) if warnings.is_empty(),
),
"{result:#?}"
);
}
#[test]
fn invalid_type_field_access_chain_2() {
let dependency = r#"
pub 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 { .. })) if warnings.is_empty(),
),
"{result:#?}"
);
}
#[test]
fn forbid_importing_or_using_opaque_constructors() {
let dependency = r#"
@ -2264,7 +2795,7 @@ fn forbid_importing_or_using_opaque_constructors() {
"#;
let source_code = r#"
use foo/thing.{Thing, Foo}
use thing.{Thing, Foo}
fn bar(thing: Thing) {
expect Foo(a) = thing
@ -2273,15 +2804,12 @@ fn forbid_importing_or_using_opaque_constructors() {
"#;
assert!(matches!(
check_with_deps(
parse(source_code),
vec![("foo/thing".to_string(), parse(dependency))],
),
check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))],),
Err((_, Error::UnknownModuleField { .. })),
));
let source_code = r#"
use foo/thing.{Thing}
use thing.{Thing}
fn bar(thing: Thing) {
expect Foo(a) = thing
@ -2290,10 +2818,7 @@ fn forbid_importing_or_using_opaque_constructors() {
"#;
assert!(matches!(
check_with_deps(
parse(source_code),
vec![("foo/thing".to_string(), parse(dependency))],
),
check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))],),
Err((_, Error::UnknownTypeConstructor { .. })),
));
}

View File

@ -1449,3 +1449,26 @@ fn capture_right_hand_side_assign() {
"#
);
}
#[test]
fn types_as_namespace() {
assert_format!(
r#"
use foo.{ Foo }
fn predicate(val) {
when val is {
Foo.I(n) -> n >= 14
foo.Foo.B(bytes) -> bytes == "aiken"
}
}
test my_test() {
and {
predicate(foo.Foo.I(42)),
predicate(Foo.b("aiken"))
}
}
"#
);
}

View File

@ -0,0 +1,19 @@
---
source: crates/aiken-lang/src/tests/format.rs
description: "Code:\n\nuse foo.{ Foo }\n\nfn predicate(val) {\n when val is {\n Foo.I(n) -> n >= 14\n foo.Foo.B(bytes) -> bytes == \"aiken\"\n }\n}\n\ntest my_test() {\n and {\n predicate(foo.Foo.I(42)),\n predicate(Foo.b(\"aiken\"))\n }\n}\n"
---
use foo.{Foo}
fn predicate(val) {
when val is {
Foo.I(n) -> n >= 14
foo.Foo.B(bytes) -> bytes == "aiken"
}
}
test my_test() {
and {
predicate(foo.Foo.I(42)),
predicate(Foo.b("aiken")),
}
}

View File

@ -8,9 +8,9 @@ use super::{
use crate::{
ast::{
self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction,
TypedPattern, TypedValidator, UnqualifiedImport, UntypedArg, UntypedDefinition,
UntypedFunction, Use, Validator, PIPE_VARIABLE,
Namespace, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition,
TypedFunction, TypedPattern, TypedValidator, UnqualifiedImport, UntypedArg,
UntypedDefinition, UntypedFunction, Use, Validator, PIPE_VARIABLE,
},
tipo::{fields::FieldMap, TypeAliasAnnotation},
IdGenerator,
@ -91,6 +91,19 @@ pub struct Environment<'a> {
}
impl<'a> Environment<'a> {
#[allow(clippy::result_large_err)]
pub fn err_unknown_module(&self, name: String, location: Span) -> Error {
Error::UnknownModule {
name,
location,
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
}
}
#[allow(clippy::result_large_err)]
pub fn find_module(&self, fragments: &[String], location: Span) -> Result<&'a TypeInfo, Error> {
let mut name = fragments.join("/");
@ -118,11 +131,7 @@ impl<'a> Environment<'a> {
.collect(),
}
} else {
Error::UnknownModule {
location,
name,
known_modules: self.importable_modules.keys().cloned().collect(),
}
self.err_unknown_module(name, location)
}
})
}
@ -372,13 +381,69 @@ impl<'a> Environment<'a> {
}
}
/// Get an imported module's actual name in the current module, from its fully qualified module
/// name.
#[allow(clippy::result_large_err)]
pub fn local_module_name(&self, name: &str, location: Span) -> Result<String, Error> {
self.imported_modules
.iter()
.filter_map(|(k, module)| {
if module.1.name == name {
Some(k.to_string())
} else {
None
}
})
.next()
.ok_or_else(|| self.err_unknown_module(name.to_string(), location))
}
#[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
.importable_modules
.get(module_name)
.ok_or_else(|| self.err_unknown_module(module_name.to_string(), module_location))?;
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)]
pub fn get_type_constructor_mut(
&mut self,
name: &str,
location: Span,
) -> Result<&mut TypeConstructor, Error> {
let types = self.module_types.keys().map(|t| t.to_string()).collect();
let types = self.known_type_names();
let constructor = self
.module_types
@ -407,22 +472,14 @@ impl<'a> Environment<'a> {
.ok_or_else(|| Error::UnknownType {
location,
name: name.to_string(),
types: self.module_types.keys().map(|t| t.to_string()).collect(),
types: self.known_type_names(),
}),
Some(m) => {
let (_, module) =
self.imported_modules
.get(m)
.ok_or_else(|| Error::UnknownModule {
location,
name: name.to_string(),
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?;
let (_, module) = self
.imported_modules
.get(m)
.ok_or_else(|| self.err_unknown_module(name.to_string(), location))?;
self.unused_modules.remove(m);
@ -443,7 +500,7 @@ impl<'a> Environment<'a> {
#[allow(clippy::result_large_err)]
pub fn get_value_constructor(
&mut self,
module: Option<&String>,
module: Option<&Namespace>,
name: &str,
location: Span,
) -> Result<&ValueConstructor, Error> {
@ -457,19 +514,54 @@ impl<'a> Environment<'a> {
constructors: self.local_constructor_names(),
}),
Some(m) => {
let (_, module) =
self.imported_modules
.get(m)
.ok_or_else(|| Error::UnknownModule {
name: m.to_string(),
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
location,
})?;
Some(Namespace::Type(Some(module_name), t)) => {
let module_location = location.map(|start, _| (start, start + module_name.len()));
// Lookup the module using the declared name (which may have been rebind with
// 'as'), to obtain its _full unambiguous name_.
let (_, module) = self.imported_modules.get(module_name).ok_or_else(|| {
self.err_unknown_module(module_name.to_string(), module_location)
})?;
let type_location = Span::create(module_location.end + 1, t.len());
let parent_type = module.types.get(t).ok_or_else(|| Error::UnknownType {
location: type_location,
name: t.to_string(),
types: self.known_type_names(),
})?;
self.unused_modules.remove(&parent_type.module);
self.get_fully_qualified_value_constructor(
(parent_type.module.as_str(), module_location),
(t, type_location),
(name, location.map(|_, end| (type_location.end + 1, end))),
)
}
Some(Namespace::Type(None, t)) => {
let type_location = location.map(|start, _| (start, start + t.len()));
let parent_type = self.module_types.get(t).ok_or_else(|| Error::UnknownType {
location: type_location,
name: t.to_string(),
types: self.known_type_names(),
})?;
self.unused_modules.remove(&parent_type.module);
self.get_fully_qualified_value_constructor(
(parent_type.module.as_str(), type_location),
(t, type_location),
(name, location.map(|start, end| (start + t.len() + 1, end))),
)
}
Some(Namespace::Module(m)) => {
let (_, module) = self.imported_modules.get(m).ok_or_else(|| {
self.err_unknown_module(m.to_string(), Span::create(location.start, m.len()))
})?;
self.unused_modules.remove(m);
@ -728,10 +820,28 @@ impl<'a> Environment<'a> {
}
}
/// Get a list of known type names, for suggestions in errors.
pub fn known_type_names(&self) -> Vec<String> {
self.module_types
.keys()
.filter_map(|t| {
// Avoid leaking special internal types in error hints.
if t.starts_with("__") {
None
} else {
Some(t.to_string())
}
})
.collect()
}
pub fn local_value_names(&self) -> Vec<String> {
self.scope
.keys()
.filter(|&t| PIPE_VARIABLE != t)
// Avoid leaking internal functions in error hints.
.filter(|&t| !crate::builtins::INTERNAL_FUNCTIONS.contains(t.as_str()))
.filter(|&t| !t.starts_with("__") && !TypeConstructor::might_be(t))
.map(|t| t.to_string())
.collect()
}
@ -739,7 +849,9 @@ impl<'a> Environment<'a> {
pub fn local_constructor_names(&self) -> Vec<String> {
self.scope
.keys()
.filter(|&t| t.chars().next().unwrap_or_default().is_uppercase())
// Avoid leaking internal constructors in error hints.
.filter(|&t| !t.starts_with("__"))
.filter(|&t| TypeConstructor::might_be(t))
.map(|t| t.to_string())
.collect()
}
@ -1816,7 +1928,7 @@ impl<'a> Environment<'a> {
.get(name)
.ok_or_else(|| Error::UnknownType {
name: name.to_string(),
types: self.module_types.keys().map(|t| t.to_string()).collect(),
types: self.known_type_names(),
location,
})?
.iter()
@ -1840,15 +1952,7 @@ impl<'a> Environment<'a> {
let module = self
.importable_modules
.get(full_module_name)
.ok_or_else(|| Error::UnknownModule {
location,
name: name.to_string(),
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?;
.ok_or_else(|| self.err_unknown_module(name.to_string(), location))?;
self.unused_modules.remove(full_module_name);

View File

@ -1,6 +1,9 @@
use super::Type;
use crate::{
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedFunction, UntypedPattern},
ast::{
Annotation, BinOp, CallArg, LogicalOpChainKind, Namespace, Span, UntypedFunction,
UntypedPattern,
},
error::ExtraData,
expr::{self, AssignmentPattern, UntypedAssignmentKind, UntypedExpr},
format::Formatter,
@ -395,7 +398,7 @@ From there, you can define 'increment', a function that takes a single argument
expected: usize,
given: Vec<CallArg<UntypedPattern>>,
name: String,
module: Option<String>,
module: Option<Namespace>,
is_record: bool,
},
@ -718,7 +721,7 @@ Perhaps, try the following:
label: String,
name: String,
args: Vec<CallArg<UntypedPattern>>,
module: Option<String>,
module: Option<Namespace>,
spread_location: Option<Span>,
},
@ -805,7 +808,7 @@ Perhaps, try the following:
},
#[error(
"I looked for '{}' in '{}' but couldn't find it.\n",
"I looked for '{}' in module '{}' but couldn't find it.\n",
name.if_supports_color(Stdout, |s| s.purple()),
module_name.if_supports_color(Stdout, |s| s.purple())
)]
@ -819,7 +822,7 @@ Perhaps, try the following:
)
))]
UnknownModuleType {
#[label("unknown import")]
#[label("not exported?")]
location: Span,
name: String,
module_name: String,
@ -1076,7 +1079,7 @@ The best thing to do from here is to remove it."#))]
available_purposes: Vec<String>,
},
#[error("I could not find an appropriate handler in the validator definition\n")]
#[error("I could not find an appropriate handler in the validator definition.\n")]
#[diagnostic(code("unknown::handler"))]
#[diagnostic(help(
"When referring to a validator handler via record access, you must refer to one of the declared handlers{}{}",
@ -1092,7 +1095,7 @@ The best thing to do from here is to remove it."#))]
available_handlers: Vec<String>,
},
#[error("I caught an extraneous fallback handler in an already exhaustive validator\n")]
#[error("I caught an extraneous fallback handler in an already exhaustive validator.\n")]
#[diagnostic(code("extraneous::fallback"))]
#[diagnostic(help(
"Validator handlers must be exhaustive and either cover all purposes, or provide a fallback handler. Here, you have successfully covered all script purposes with your handler, but left an extraneous fallback branch. I cannot let that happen, but removing it for you would probably be deemed rude. So please, remove the fallback."
@ -1101,6 +1104,16 @@ The best thing to do from here is to remove it."#))]
#[label("redundant fallback handler")]
fallback: Span,
},
#[error("I was stopped by a suspicious field access chain.\n")]
#[diagnostic(code("invalid::field_access"))]
#[diagnostic(help(
"It seems like you've got things mixed up a little here? You can only access fields exported by modules or, by types within those modules. Double-check the culprit field access chain, there's likely something wrong about it."
))]
InvalidFieldAccess {
#[label("invalid field access")]
location: Span,
},
}
impl ExtraData for Error {
@ -1163,7 +1176,8 @@ impl ExtraData for Error {
| Error::UnknownValidatorHandler { .. }
| Error::UnexpectedValidatorFallback { .. }
| Error::IncorrectBenchmarkArity { .. }
| Error::MustInferFirst { .. } => None,
| Error::MustInferFirst { .. }
| Error::InvalidFieldAccess { .. } => None,
Error::UnknownType { name, .. }
| Error::UnknownTypeConstructor { name, .. }
@ -1274,7 +1288,7 @@ fn suggest_pattern(
expected: usize,
name: &str,
given: &[CallArg<UntypedPattern>],
module: &Option<String>,
module: &Option<Namespace>,
is_record: bool,
) -> Option<String> {
if expected > given.len() {
@ -1309,7 +1323,7 @@ fn suggest_generic(name: &str, expected: usize) -> String {
fn suggest_constructor_pattern(
name: &str,
args: &[CallArg<UntypedPattern>],
module: &Option<String>,
module: &Option<Namespace>,
spread_location: Option<Span>,
) -> String {
let fixed_args = args
@ -1526,14 +1540,15 @@ fn suggest_import_constructor() -> String {
aiken/pet.ak ==> foo.ak
{keyword_pub} {keyword_type} {type_Pet} {{ {keyword_use} aiken/pet.{{{type_Pet}, {variant_Dog}}}
{variant_Cat}
{variant_Dog} {keyword_fn} foo(pet : {type_Pet}) {{
}} {keyword_when} pet {keyword_is} {{
pet.{variant_Cat} -> // ...
{variant_Dog} {keyword_fn} foo(pet: {type_Pet}) {{
{variant_Fox} {keyword_when} pet {keyword_is} {{
}} pet.{variant_Cat} -> // ...
{variant_Dog} -> // ...
{type_Pet}.{variant_Fox} -> // ...
}}
}}
You must import its constructors explicitly to use them, or prefix them with the module's name.
You must import its constructors explicitly to use them, or prefix them with the module or type's name.
"#
, keyword_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
, keyword_is = "is".if_supports_color(Stdout, |s| s.yellow())
@ -1550,6 +1565,9 @@ fn suggest_import_constructor() -> String {
, variant_Dog = "Dog"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
, variant_Fox = "Fox"
.if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold())
}
}

View File

@ -11,11 +11,11 @@ use super::{
use crate::{
ast::{
self, Annotation, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point,
ByteArrayFormatPreference, CallArg, Curve, Function, IfBranch, LogicalOpChainKind, Pattern,
RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg,
TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, TypedValidator, UnOp,
UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction, UntypedIfBranch,
UntypedPattern, UntypedRecordUpdateArg,
ByteArrayFormatPreference, CallArg, Curve, Function, IfBranch, LogicalOpChainKind,
Namespace, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg,
TypedCallArg, TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg,
TypedValidator, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction,
UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg,
},
builtins::{from_default_function, BUILTIN},
expr::{FnStyle, TypedExpr, UntypedExpr},
@ -404,7 +404,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
module_alias,
label,
..
} => (Some(module_alias), label),
} => (Some(Namespace::Module(module_alias.to_string())), label),
TypedExpr::Var { name, .. } => (None, name),
@ -413,7 +413,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Ok(self
.environment
.get_value_constructor(module, name, location)?
.get_value_constructor(module.as_ref(), name, location)?
.field_map())
}
@ -792,12 +792,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
args: Vec<UntypedRecordUpdateArg>,
location: Span,
) -> Result<TypedExpr, Error> {
let (module, name): (Option<String>, String) = match self.infer(constructor.clone())? {
let (module, name): (Option<Namespace>, String) = match self.infer(constructor.clone())? {
TypedExpr::ModuleSelect {
module_alias,
label,
..
} => (Some(module_alias), label),
} => (Some(Namespace::Module(module_alias)), label),
TypedExpr::Var { name, .. } => (None, name),
@ -1068,6 +1068,69 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
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.map(|start, end| (start + type_name.len() + 1, end)),
),
);
}
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()
{
if TypeConstructor::might_be(module_name) {
return Err(Error::InvalidFieldAccess {
location: access_location,
});
}
// Lookup the module using the declared name (which may have been rebind with
// 'as'), to obtain its _full unambiguous name_.
let (_, module) = self
.environment
.imported_modules
.get(module_name)
.ok_or_else(|| {
self.environment
.err_unknown_module(module_name.to_string(), *module_location)
})?;
return self.infer_inner_type_constructor_access(
(module.name.as_str(), *module_location),
(
type_name,
type_location.map(|start, end| (start + module_name.len() + 1, end)),
),
(
&label,
access_location.map(|start, end| {
(start + module_name.len() + type_name.len() + 2, end)
}),
),
);
}
}
_ => (),
};
// 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.
// TODO: Remove this cloning
@ -1075,7 +1138,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Ok(record_access) => Ok(record_access),
Err(err) => match container {
UntypedExpr::Var { name, location } => {
UntypedExpr::Var { name, location } if !TypeConstructor::might_be(&name) => {
let module_access =
self.infer_module_access(&name, label, &location, access_location);
@ -1106,15 +1169,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.environment
.imported_modules
.get(module_alias)
.ok_or_else(|| Error::UnknownModule {
name: module_alias.to_string(),
location: *module_location,
known_modules: self
.environment
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
.ok_or_else(|| {
self.environment
.err_unknown_module(module_alias.to_string(), *module_location)
})?;
let constructor =
@ -1162,6 +1219,58 @@ 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> {
self.environment.increment_usage(type_name);
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.environment.unused_modules.remove(module_name);
self.infer_module_access(
&self
.environment
.local_module_name(module_name, module_location)?,
label.to_string(),
&type_location,
label_location,
)
}
#[allow(clippy::result_large_err)]
fn infer_record_access(
&mut self,
@ -1169,9 +1278,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
label: String,
location: Span,
) -> Result<TypedExpr, Error> {
// Infer the type of the (presumed) record
// Infer the type of the (presumed) record.
let record = self.infer(record)?;
self.infer_known_record_access(record, label, location)
}
@ -2563,15 +2671,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.environment
.imported_modules
.get(module_name)
.ok_or_else(|| Error::UnknownModule {
location: *location,
name: module_name.to_string(),
known_modules: self
.environment
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
.ok_or_else(|| {
self.environment
.err_unknown_module(module_name.to_string(), *location)
})?;
module

View File

@ -17,7 +17,12 @@ use crate::{
tipo::{expr::infer_function, Span, Type, TypeVar},
IdGenerator,
};
use std::{borrow::Borrow, collections::HashMap, ops::Deref, rc::Rc};
use std::{
borrow::Borrow,
collections::{BTreeSet, HashMap},
ops::Deref,
rc::Rc,
};
impl UntypedModule {
#[allow(clippy::too_many_arguments)]
@ -116,8 +121,14 @@ impl UntypedModule {
.module_types
.retain(|_, info| info.public && info.module == module_name);
let own_types = environment.module_types.keys().collect::<BTreeSet<_>>();
environment.module_values.retain(|_, info| info.public);
environment
.module_types_constructors
.retain(|k, _| own_types.contains(k));
environment
.accessors
.retain(|_, accessors| accessors.public);

View File

@ -6,7 +6,7 @@ use super::{
hydrator::Hydrator,
PatternConstructor, Type, ValueConstructorVariant,
};
use crate::ast::{CallArg, Pattern, Span, TypedPattern, UntypedPattern};
use crate::ast::{CallArg, Namespace, Pattern, Span, TypedPattern, UntypedPattern};
use itertools::Itertools;
use std::{
collections::{HashMap, HashSet},
@ -570,7 +570,12 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
Ok(Pattern::Constructor {
location,
module,
// NOTE:
// Type namespaces are completely erased during type-check.
module: match module {
None | Some(Namespace::Type(..)) => None,
Some(Namespace::Module(m)) => Some(m),
},
name,
arguments: pattern_args,
constructor,
@ -601,7 +606,12 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
Ok(Pattern::Constructor {
location,
module,
// NOTE:
// Type namespaces are completely erased during type-check.
module: match module {
None | Some(Namespace::Type(..)) => None,
Some(Namespace::Module(m)) => Some(m),
},
name,
arguments: vec![],
constructor,

View File

@ -731,7 +731,7 @@ impl Warning {
impl Debug for Warning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
default_miette_handler(0)
default_miette_handler(1)
.debug(
&DisplayWarning {
title: &self.to_string(),