diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 5dcd625c..bfdadd58 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -2349,3 +2349,52 @@ fn partial_eq_callback_return() { Err((_, Error::CouldNotUnify { .. })) )); } + +#[test] +fn pair_access_on_call() { + let source_code = r#" + use aiken/builtin + + pub fn list_at(xs: List, index: Int) -> a { + if index == 0 { + builtin.head_list(xs) + } else { + list_at(builtin.tail_list(xs), index - 1) + } + } + + fn foo() { + [list_at([Pair(1, 2)], 0).2nd, ..[1, 2]] + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn pair_index_out_of_bound() { + let source_code = r#" + pub fn foo() { + Pair(1, 2).3rd + } + "#; + + assert!(matches!( + dbg!(check_validator(parse(source_code))), + Err((_, Error::PairIndexOutOfBound { .. })) + )) +} + +#[test] +fn not_indexable() { + let source_code = r#" + pub fn foo() { + "foo".1st + } + "#; + + assert!(matches!( + dbg!(check_validator(parse(source_code))), + Err((_, Error::NotIndexable { .. })) + )) +} diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index c9f41e88..a056385b 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -508,18 +508,16 @@ If you really meant to return that last expression, try to replace it with the f name: String, }, - #[error( - "I tripped over an attempt to access tuple elements on something else than a tuple.\n" - )] + #[error("I tripped over an attempt to access elements on something that isn't indexable.\n")] #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))] - #[diagnostic(code("illegal::tuple_index"))] + #[diagnostic(code("illegal::indexable"))] #[diagnostic(help( - r#"Because you used a tuple-index on an element, I assumed it had to be a tuple but instead I found something of type: + r#"Because you used an ordinal index on an element, I assumed it had to be a tuple or a pair but instead I found something of type: ╰─▶ {type_info}"#, type_info = tipo.to_pretty(0).if_supports_color(Stdout, |s| s.red()) ))] - NotATuple { + NotIndexable { #[label] location: Span, tipo: Rc, @@ -675,12 +673,25 @@ You can help me by providing a type-annotation for 'x', as such: #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))] #[diagnostic(code("invalid::tuple_index"))] TupleIndexOutOfBound { - #[label] + #[label("out of bounds")] location: Span, index: usize, size: usize, }, + #[error( + "I discovered an attempt to access the {} element of a {}.\n", + Ordinal(*index + 1).to_string().if_supports_color(Stdout, |s| s.purple()), + "Pair".if_supports_color(Stdout, |s| s.bright_blue()).if_supports_color(Stdout, |s| s.bold()), + )] + #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#pairs"))] + #[diagnostic(code("invalid::pair_index"))] + PairIndexOutOfBound { + #[label("out of bounds")] + location: Span, + index: usize, + }, + #[error( "I tripped over the following labeled argument: {}.\n", label.if_supports_color(Stdout, |s| s.purple()) @@ -1035,7 +1046,7 @@ impl ExtraData for Error { | Error::MissingVarInAlternativePattern { .. } | Error::MultiValidatorEqualArgs { .. } | Error::NonLocalClauseGuardVariable { .. } - | Error::NotATuple { .. } + | Error::NotIndexable { .. } | Error::NotExhaustivePatternMatch { .. } | Error::NotFn { .. } | Error::PositionalArgumentAfterLabeled { .. } @@ -1045,6 +1056,7 @@ impl ExtraData for Error { | Error::RecursiveType { .. } | Error::RedundantMatchClause { .. } | Error::TupleIndexOutOfBound { .. } + | Error::PairIndexOutOfBound { .. } | Error::UnexpectedLabeledArg { .. } | Error::UnexpectedLabeledArgInPattern { .. } | Error::UnknownLabels { .. } diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 3d4b930d..3579a2ed 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -2065,13 +2065,13 @@ impl<'a, 'b> ExprTyper<'a, 'b> { fn infer_tuple_index( &mut self, - tuple: UntypedExpr, + tuple_or_pair: UntypedExpr, index: usize, location: Span, ) -> Result { - let tuple = self.infer(tuple)?; + let tuple_or_pair = self.infer(tuple_or_pair)?; - let tipo = match *collapse_links(tuple.tipo()) { + let tipo = match *collapse_links(tuple_or_pair.tipo()) { Type::Tuple { ref elems, alias: _, @@ -2087,9 +2087,22 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(elems[index].clone()) } } - _ => Err(Error::NotATuple { + Type::Pair { + ref fst, + ref snd, + alias: _, + } => { + if index == 0 { + Ok(fst.clone()) + } else if index == 1 { + Ok(snd.clone()) + } else { + Err(Error::PairIndexOutOfBound { location, index }) + } + } + _ => Err(Error::NotIndexable { location, - tipo: tuple.tipo(), + tipo: tuple_or_pair.tipo(), }), }?; @@ -2097,7 +2110,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, tipo, index, - tuple: Box::new(tuple), + tuple: Box::new(tuple_or_pair), }) } diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index be24a142..29396da8 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -2118,19 +2118,19 @@ fn acceptance_test_23_to_list() { let src = r#" pub type Map = List> - + pub opaque type AssocList { inner: Map, } - + pub fn new() -> AssocList { AssocList { inner: [] } } - + pub fn to_list(m: AssocList) -> Map { m.inner } - + pub fn insert( in m: AssocList, key k: key, @@ -2138,7 +2138,7 @@ fn acceptance_test_23_to_list() { ) -> AssocList { AssocList { inner: do_insert(m.inner, k, v) } } - + fn do_insert(elems: Map, k: key, v: value) -> Map { when elems is { [] -> @@ -2151,15 +2151,15 @@ fn acceptance_test_23_to_list() { } } } - + fn fixture_1() { new() |> insert("foo", 42) |> insert("bar", 14) } - + test to_list_2() { - to_list(fixture_1()) == [Pair("foo", 42).2nd, Pair("bar", 14)] + to_list(fixture_1()) == [Pair("foo", 42), Pair("bar", 14)] } "#;