Merge branch 'main' into avoid-adjacent-sequence-fusions

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

View File

@ -5,6 +5,7 @@
### Added ### 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**: 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 ### Changed
@ -17,6 +18,7 @@
- **aiken-lang**: Formatter was removing comments from function type annotation args @rvcas - **aiken-lang**: Formatter was removing comments from function type annotation args @rvcas
- **aiken-lang**: Parser wrongly merged two adjacent sequences together, effectively fusioning scopes. @KtorZ - **aiken-lang**: Parser wrongly merged two adjacent sequences together, effectively fusioning scopes. @KtorZ
- **aiken-lang**: Fix hint when suggesting to use named fields, wrongly suggesting args in lexicographical order instead of definition order. @KtorZ
## v1.1.13 - 2025-02-26 ## v1.1.13 - 2025-02-26

View File

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

View File

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

View File

@ -12,6 +12,7 @@ use crate::{
}, },
IdGenerator, IdGenerator,
}; };
use std::{collections::BTreeSet, sync::LazyLock};
use indexmap::IndexMap; use indexmap::IndexMap;
use std::{collections::HashMap, rc::Rc}; use std::{collections::HashMap, rc::Rc};
@ -25,6 +26,16 @@ use uplc::{
pub const PRELUDE: &str = "aiken"; pub const PRELUDE: &str = "aiken";
pub const BUILTIN: &str = "aiken/builtin"; 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 /// Build a prelude that can be injected
/// into a compiler pipeline /// into a compiler pipeline
pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { pub fn prelude(id_gen: &IdGenerator) -> TypeInfo {

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use super::Chain; use super::Chain;
use crate::{ use crate::{
expr::UntypedExpr, ast::well_known,
parser::{token::Token, ParseError}, parser::{token::Token, ParseError},
}; };
use chumsky::prelude::*; use chumsky::prelude::*;
@ -8,27 +8,13 @@ use chumsky::prelude::*;
pub(crate) fn parser() -> impl Parser<Token, Chain, Error = ParseError> { pub(crate) fn parser() -> impl Parser<Token, Chain, Error = ParseError> {
just(Token::Dot) just(Token::Dot)
.ignore_then(choice(( .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::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;

View File

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

View File

@ -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()),

View File

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

View File

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

View File

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

View File

@ -11,15 +11,23 @@ use crate::{
}; };
use std::collections::HashMap; use std::collections::HashMap;
const DEFAULT_MODULE_NAME: &str = "my_module";
const DEFAULT_PACKAGE: &str = "test/project";
fn parse(source_code: &str) -> UntypedModule { 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 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 ast
} }
fn check_module( fn check_module(
ast: UntypedModule, ast: UntypedModule,
extra: Vec<(String, UntypedModule)>, extra: Vec<UntypedModule>,
kind: ModuleKind, kind: ModuleKind,
tracing: Tracing, tracing: Tracing,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> { ) -> 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".to_string(), builtins::prelude(&id_gen));
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&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![]; 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 let typed_module = module
.infer( .infer(
&id_gen, &id_gen,
kind, kind,
&package, DEFAULT_PACKAGE,
&module_types, &module_types,
Tracing::All(TraceLevel::Verbose), Tracing::All(TraceLevel::Verbose),
&mut warnings, &mut warnings,
None, None,
) )
.expect("extra dependency did not compile"); .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( let result = ast.infer(
&id_gen, &id_gen,
kind, kind,
"test/project", DEFAULT_PACKAGE,
&module_types, &module_types,
tracing, tracing,
&mut warnings, &mut warnings,
@ -76,7 +89,7 @@ fn check_with_verbosity(
fn check_with_deps( fn check_with_deps(
ast: UntypedModule, ast: UntypedModule,
extra: Vec<(String, UntypedModule)>, extra: Vec<UntypedModule>,
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> { ) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
check_module(ast, extra, ModuleKind::Lib, Tracing::verbose()) 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()); 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] #[test]
fn forbid_importing_or_using_opaque_constructors() { fn forbid_importing_or_using_opaque_constructors() {
let dependency = r#" let dependency = r#"
@ -2264,7 +2795,7 @@ fn forbid_importing_or_using_opaque_constructors() {
"#; "#;
let source_code = r#" let source_code = r#"
use foo/thing.{Thing, Foo} use thing.{Thing, Foo}
fn bar(thing: Thing) { fn bar(thing: Thing) {
expect Foo(a) = thing expect Foo(a) = thing
@ -2273,15 +2804,12 @@ fn forbid_importing_or_using_opaque_constructors() {
"#; "#;
assert!(matches!( assert!(matches!(
check_with_deps( check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))],),
parse(source_code),
vec![("foo/thing".to_string(), parse(dependency))],
),
Err((_, Error::UnknownModuleField { .. })), Err((_, Error::UnknownModuleField { .. })),
)); ));
let source_code = r#" let source_code = r#"
use foo/thing.{Thing} use thing.{Thing}
fn bar(thing: Thing) { fn bar(thing: Thing) {
expect Foo(a) = thing expect Foo(a) = thing
@ -2290,10 +2818,7 @@ fn forbid_importing_or_using_opaque_constructors() {
"#; "#;
assert!(matches!( assert!(matches!(
check_with_deps( check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))],),
parse(source_code),
vec![("foo/thing".to_string(), parse(dependency))],
),
Err((_, Error::UnknownTypeConstructor { .. })), Err((_, Error::UnknownTypeConstructor { .. })),
)); ));
} }
@ -3701,3 +4226,56 @@ fn unused_record_fields_4() {
} }
); );
} }
#[test]
fn unused_record_fields_5() {
let source_code = r#"
pub type Foo {
b_is_before_a: Bool,
a_is_after_b: Bool,
}
test confusing() {
let Foo(a, b) = Foo(True, True)
a || b
}
"#;
let result = check_validator(parse(source_code));
assert!(result.is_ok());
let (warnings, _) = result.unwrap();
assert_eq!(
warnings[0],
Warning::UnusedRecordFields {
location: Span::create(137, 9),
suggestion: UntypedPattern::Constructor {
is_record: true,
location: Span::create(137, 9),
name: "Foo".to_string(),
arguments: vec![
CallArg {
label: Some("b_is_before_a".to_string()),
location: Span::create(141, 13),
value: Pattern::Var {
location: Span::create(141, 1),
name: "a".to_string()
}
},
CallArg {
label: Some("a_is_after_b".to_string()),
location: Span::create(144, 12),
value: Pattern::Var {
location: Span::create(144, 1),
name: "b".to_string()
}
}
],
module: None,
constructor: (),
spread_location: None,
tipo: ()
}
}
);
}

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::{ use crate::{
ast::{ ast::{
self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction, Namespace, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition,
TypedPattern, TypedValidator, UnqualifiedImport, UntypedArg, UntypedDefinition, TypedFunction, TypedPattern, TypedValidator, UnqualifiedImport, UntypedArg,
UntypedFunction, Use, Validator, PIPE_VARIABLE, UntypedDefinition, UntypedFunction, Use, Validator, PIPE_VARIABLE,
}, },
tipo::{fields::FieldMap, TypeAliasAnnotation}, tipo::{fields::FieldMap, TypeAliasAnnotation},
IdGenerator, IdGenerator,
@ -91,6 +91,19 @@ pub struct Environment<'a> {
} }
impl<'a> 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)] #[allow(clippy::result_large_err)]
pub fn find_module(&self, fragments: &[String], location: Span) -> Result<&'a TypeInfo, Error> { pub fn find_module(&self, fragments: &[String], location: Span) -> Result<&'a TypeInfo, Error> {
let mut name = fragments.join("/"); let mut name = fragments.join("/");
@ -118,11 +131,7 @@ impl<'a> Environment<'a> {
.collect(), .collect(),
} }
} else { } else {
Error::UnknownModule { self.err_unknown_module(name, location)
location,
name,
known_modules: self.importable_modules.keys().cloned().collect(),
}
} }
}) })
} }
@ -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)] #[allow(clippy::result_large_err)]
pub fn get_type_constructor_mut( pub fn get_type_constructor_mut(
&mut self, &mut self,
name: &str, name: &str,
location: Span, location: Span,
) -> Result<&mut TypeConstructor, Error> { ) -> 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 let constructor = self
.module_types .module_types
@ -407,22 +472,14 @@ impl<'a> Environment<'a> {
.ok_or_else(|| Error::UnknownType { .ok_or_else(|| Error::UnknownType {
location, location,
name: name.to_string(), name: name.to_string(),
types: self.module_types.keys().map(|t| t.to_string()).collect(), types: self.known_type_names(),
}), }),
Some(m) => { Some(m) => {
let (_, module) = let (_, module) = self
self.imported_modules .imported_modules
.get(m) .get(m)
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| self.err_unknown_module(name.to_string(), location))?;
location,
name: name.to_string(),
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?;
self.unused_modules.remove(m); self.unused_modules.remove(m);
@ -443,7 +500,7 @@ impl<'a> Environment<'a> {
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
pub fn get_value_constructor( pub fn get_value_constructor(
&mut self, &mut self,
module: Option<&String>, module: Option<&Namespace>,
name: &str, name: &str,
location: Span, location: Span,
) -> Result<&ValueConstructor, Error> { ) -> Result<&ValueConstructor, Error> {
@ -457,19 +514,54 @@ impl<'a> Environment<'a> {
constructors: self.local_constructor_names(), constructors: self.local_constructor_names(),
}), }),
Some(m) => { Some(Namespace::Type(Some(module_name), t)) => {
let (_, module) = let module_location = location.map(|start, _| (start, start + module_name.len()));
self.imported_modules
.get(m) // Lookup the module using the declared name (which may have been rebind with
.ok_or_else(|| Error::UnknownModule { // 'as'), to obtain its _full unambiguous name_.
name: m.to_string(), let (_, module) = self.imported_modules.get(module_name).ok_or_else(|| {
known_modules: self self.err_unknown_module(module_name.to_string(), module_location)
.importable_modules })?;
.keys()
.map(|t| t.to_string()) let type_location = Span::create(module_location.end + 1, t.len());
.collect(),
location, 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); 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> { pub fn local_value_names(&self) -> Vec<String> {
self.scope self.scope
.keys() .keys()
.filter(|&t| PIPE_VARIABLE != t) .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()) .map(|t| t.to_string())
.collect() .collect()
} }
@ -739,7 +849,9 @@ impl<'a> Environment<'a> {
pub fn local_constructor_names(&self) -> Vec<String> { pub fn local_constructor_names(&self) -> Vec<String> {
self.scope self.scope
.keys() .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()) .map(|t| t.to_string())
.collect() .collect()
} }
@ -1816,7 +1928,7 @@ impl<'a> Environment<'a> {
.get(name) .get(name)
.ok_or_else(|| Error::UnknownType { .ok_or_else(|| Error::UnknownType {
name: name.to_string(), name: name.to_string(),
types: self.module_types.keys().map(|t| t.to_string()).collect(), types: self.known_type_names(),
location, location,
})? })?
.iter() .iter()
@ -1840,15 +1952,7 @@ impl<'a> Environment<'a> {
let module = self let module = self
.importable_modules .importable_modules
.get(full_module_name) .get(full_module_name)
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| self.err_unknown_module(name.to_string(), location))?;
location,
name: name.to_string(),
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?;
self.unused_modules.remove(full_module_name); self.unused_modules.remove(full_module_name);

View File

@ -1,6 +1,9 @@
use super::Type; use super::Type;
use crate::{ use crate::{
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedFunction, UntypedPattern}, ast::{
Annotation, BinOp, CallArg, LogicalOpChainKind, Namespace, Span, UntypedFunction,
UntypedPattern,
},
error::ExtraData, error::ExtraData,
expr::{self, AssignmentPattern, UntypedAssignmentKind, UntypedExpr}, expr::{self, AssignmentPattern, UntypedAssignmentKind, UntypedExpr},
format::Formatter, format::Formatter,
@ -395,7 +398,7 @@ From there, you can define 'increment', a function that takes a single argument
expected: usize, expected: usize,
given: Vec<CallArg<UntypedPattern>>, given: Vec<CallArg<UntypedPattern>>,
name: String, name: String,
module: Option<String>, module: Option<Namespace>,
is_record: bool, is_record: bool,
}, },
@ -718,7 +721,7 @@ Perhaps, try the following:
label: String, label: String,
name: String, name: String,
args: Vec<CallArg<UntypedPattern>>, args: Vec<CallArg<UntypedPattern>>,
module: Option<String>, module: Option<Namespace>,
spread_location: Option<Span>, spread_location: Option<Span>,
}, },
@ -805,7 +808,7 @@ Perhaps, try the following:
}, },
#[error( #[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()), name.if_supports_color(Stdout, |s| s.purple()),
module_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 { UnknownModuleType {
#[label("unknown import")] #[label("not exported?")]
location: Span, location: Span,
name: String, name: String,
module_name: String, module_name: String,
@ -1076,7 +1079,7 @@ The best thing to do from here is to remove it."#))]
available_purposes: Vec<String>, 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(code("unknown::handler"))]
#[diagnostic(help( #[diagnostic(help(
"When referring to a validator handler via record access, you must refer to one of the declared handlers{}{}", "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>, 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(code("extraneous::fallback"))]
#[diagnostic(help( #[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." "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")] #[label("redundant fallback handler")]
fallback: Span, 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 { impl ExtraData for Error {
@ -1163,7 +1176,8 @@ impl ExtraData for Error {
| Error::UnknownValidatorHandler { .. } | Error::UnknownValidatorHandler { .. }
| Error::UnexpectedValidatorFallback { .. } | Error::UnexpectedValidatorFallback { .. }
| Error::IncorrectBenchmarkArity { .. } | Error::IncorrectBenchmarkArity { .. }
| Error::MustInferFirst { .. } => None, | Error::MustInferFirst { .. }
| Error::InvalidFieldAccess { .. } => None,
Error::UnknownType { name, .. } Error::UnknownType { name, .. }
| Error::UnknownTypeConstructor { name, .. } | Error::UnknownTypeConstructor { name, .. }
@ -1274,7 +1288,7 @@ fn suggest_pattern(
expected: usize, expected: usize,
name: &str, name: &str,
given: &[CallArg<UntypedPattern>], given: &[CallArg<UntypedPattern>],
module: &Option<String>, module: &Option<Namespace>,
is_record: bool, is_record: bool,
) -> Option<String> { ) -> Option<String> {
if expected > given.len() { if expected > given.len() {
@ -1309,7 +1323,7 @@ fn suggest_generic(name: &str, expected: usize) -> String {
fn suggest_constructor_pattern( fn suggest_constructor_pattern(
name: &str, name: &str,
args: &[CallArg<UntypedPattern>], args: &[CallArg<UntypedPattern>],
module: &Option<String>, module: &Option<Namespace>,
spread_location: Option<Span>, spread_location: Option<Span>,
) -> String { ) -> String {
let fixed_args = args let fixed_args = args
@ -1526,14 +1540,15 @@ fn suggest_import_constructor() -> String {
aiken/pet.ak ==> foo.ak aiken/pet.ak ==> foo.ak
{keyword_pub} {keyword_type} {type_Pet} {{ {keyword_use} aiken/pet.{{{type_Pet}, {variant_Dog}}} {keyword_pub} {keyword_type} {type_Pet} {{ {keyword_use} aiken/pet.{{{type_Pet}, {variant_Dog}}}
{variant_Cat} {variant_Cat}
{variant_Dog} {keyword_fn} foo(pet : {type_Pet}) {{ {variant_Dog} {keyword_fn} foo(pet: {type_Pet}) {{
}} {keyword_when} pet {keyword_is} {{ {variant_Fox} {keyword_when} pet {keyword_is} {{
pet.{variant_Cat} -> // ... }} pet.{variant_Cat} -> // ...
{variant_Dog} -> // ... {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_fn = "fn".if_supports_color(Stdout, |s| s.yellow())
, keyword_is = "is".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" , variant_Dog = "Dog"
.if_supports_color(Stdout, |s| s.bright_blue()) .if_supports_color(Stdout, |s| s.bright_blue())
.if_supports_color(Stdout, |s| s.bold()) .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::{ use crate::{
ast::{ ast::{
self, Annotation, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point, self, Annotation, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point,
ByteArrayFormatPreference, CallArg, Curve, Function, IfBranch, LogicalOpChainKind, Pattern, ByteArrayFormatPreference, CallArg, Curve, Function, IfBranch, LogicalOpChainKind,
RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg, Namespace, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg,
TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, TypedValidator, UnOp, TypedCallArg, TypedClause, TypedIfBranch, TypedPattern, TypedRecordUpdateArg,
UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction, UntypedIfBranch, TypedValidator, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedFunction,
UntypedPattern, UntypedRecordUpdateArg, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg,
}, },
builtins::{from_default_function, BUILTIN}, builtins::{from_default_function, BUILTIN},
expr::{FnStyle, TypedExpr, UntypedExpr}, expr::{FnStyle, TypedExpr, UntypedExpr},
@ -404,7 +404,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
module_alias, module_alias,
label, label,
.. ..
} => (Some(module_alias), label), } => (Some(Namespace::Module(module_alias.to_string())), label),
TypedExpr::Var { name, .. } => (None, name), TypedExpr::Var { name, .. } => (None, name),
@ -413,7 +413,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Ok(self Ok(self
.environment .environment
.get_value_constructor(module, name, location)? .get_value_constructor(module.as_ref(), name, location)?
.field_map()) .field_map())
} }
@ -792,12 +792,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
args: Vec<UntypedRecordUpdateArg>, args: Vec<UntypedRecordUpdateArg>,
location: Span, location: Span,
) -> Result<TypedExpr, Error> { ) -> 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 { TypedExpr::ModuleSelect {
module_alias, module_alias,
label, label,
.. ..
} => (Some(module_alias), label), } => (Some(Namespace::Module(module_alias)), label),
TypedExpr::Var { name, .. } => (None, name), TypedExpr::Var { name, .. } => (None, name),
@ -1068,6 +1068,69 @@ 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.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 // 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 +1138,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);
@ -1106,15 +1169,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.environment .environment
.imported_modules .imported_modules
.get(module_alias) .get(module_alias)
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| {
name: module_alias.to_string(), self.environment
location: *module_location, .err_unknown_module(module_alias.to_string(), *module_location)
known_modules: self
.environment
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?; })?;
let constructor = 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)] #[allow(clippy::result_large_err)]
fn infer_record_access( fn infer_record_access(
&mut self, &mut self,
@ -1169,9 +1278,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)
} }
@ -2563,15 +2671,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.environment .environment
.imported_modules .imported_modules
.get(module_name) .get(module_name)
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| {
location: *location, self.environment
name: module_name.to_string(), .err_unknown_module(module_name.to_string(), *location)
known_modules: self
.environment
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?; })?;
module module

View File

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

View File

@ -6,7 +6,7 @@ use super::{
hydrator::Hydrator, hydrator::Hydrator,
PatternConstructor, Type, ValueConstructorVariant, PatternConstructor, Type, ValueConstructorVariant,
}; };
use crate::ast::{CallArg, Pattern, Span, TypedPattern, UntypedPattern}; use crate::ast::{CallArg, Namespace, Pattern, Span, TypedPattern, UntypedPattern};
use itertools::Itertools; use itertools::Itertools;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@ -467,7 +467,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
let arguments = field_map let arguments = field_map
.fields .fields
.iter() .iter()
.sorted_by(|(a, _), (b, _)| a.cmp(b)) .sorted_by(|(_, (a, _)), (_, (b, _))| a.cmp(b))
.zip(pattern_args.iter()) .zip(pattern_args.iter())
.filter_map(|((field, (_, _)), arg)| { .filter_map(|((field, (_, _)), arg)| {
if arg.value.is_discard() { if arg.value.is_discard() {
@ -570,7 +570,12 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
Ok(Pattern::Constructor { Ok(Pattern::Constructor {
location, 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, name,
arguments: pattern_args, arguments: pattern_args,
constructor, constructor,
@ -601,7 +606,12 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
Ok(Pattern::Constructor { Ok(Pattern::Constructor {
location, 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, name,
arguments: vec![], arguments: vec![],
constructor, constructor,

View File

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