Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ndc_analyser/src/analyser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ impl Analyser {
TypeExpr::Tuple { elements, .. } => {
StaticType::Tuple(elements.iter().map(|e| self.lower_type_expr(e)).collect())
}
TypeExpr::Name { name, args, span } => {
TypeExpr::Name {
name, args, span, ..
} => {
let has_args = !args.is_empty();
let args = args.iter().map(|a| self.lower_type_expr(a)).collect();
match self.scope_tree.resolve_type(name).cloned() {
Expand Down
2 changes: 2 additions & 0 deletions ndc_parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,7 @@ impl Parser {
};

return Ok(TypeExpr::Name {
id: NodeId::next(),
name,
args: Vec::new(),
span,
Expand Down Expand Up @@ -1547,6 +1548,7 @@ impl Parser {
};

Ok(TypeExpr::Name {
id: NodeId::next(),
name: ident,
args,
span,
Expand Down
4 changes: 4 additions & 0 deletions ndc_parser/src/type_expr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::expression::NodeId;
use ndc_lexer::Span;
use std::fmt;

Expand All @@ -8,6 +9,9 @@ use std::fmt;
pub enum TypeExpr {
/// A (possibly generic) named type: `Int`, `List<Int>`, `Map<String, Int>`.
Name {
/// Identity of this name, independent of its source location. In
/// `Map<P, Q>`, `Map`, `P` and `Q` each have their own.
id: NodeId,
name: String,
args: Vec<Self>,
span: Span,
Expand Down
88 changes: 88 additions & 0 deletions ndc_parser/tests/type_annotations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![allow(unused_crate_dependencies)]

use std::collections::HashSet;

use ndc_lexer::{Lexer, SourceId};
use ndc_parser::{Expression, ExpressionLocation, NodeId, Parser, TypeExpr};

fn parse_one(source: &str) -> ExpressionLocation {
let tokens = Lexer::new(source, SourceId::SYNTHETIC)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut expressions = Parser::from_tokens(tokens).parse().unwrap();
assert_eq!(expressions.len(), 1);
expressions.pop().unwrap()
}

fn names(annotation: &TypeExpr, found: &mut Vec<(String, NodeId)>) {
match annotation {
TypeExpr::Name { id, name, args, .. } => {
found.push((name.clone(), *id));
for arg in args {
names(arg, found);
}
}
TypeExpr::Tuple { elements, .. } => {
for element in elements {
names(element, found);
}
}
}
}

fn assert_distinct(ids: impl IntoIterator<Item = NodeId>) {
let ids = ids.into_iter().collect::<Vec<_>>();
let unique = ids.iter().collect::<HashSet<_>>();
assert_eq!(unique.len(), ids.len(), "{ids:?}");
}

#[test]
fn every_type_name_has_its_own_id() {
let expression = parse_one("let x: Map<P, (Q, List<P>)> = input");
let Expression::VariableDeclaration {
annotated_type: Some(annotation),
..
} = expression.expression
else {
panic!("expected an annotated declaration");
};

let mut found = vec![];
names(&annotation, &mut found);
assert_eq!(
found
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<_>>(),
["Map", "P", "Q", "List", "P"]
);
// Both `P`s are separate uses, so they get separate ids.
assert_distinct(found.into_iter().map(|(_, id)| id));
}

#[test]
fn cast_targets_before_a_comparison_have_ids() {
// `Int < 5` is not a type argument list, so the parser reads `Int` on its own.
let expression = parse_one("[x as Int < 5, y as Int < 6]");
let Expression::List { values } = expression.expression else {
panic!("expected a list");
};

let ids = values.iter().map(|value| {
let (Expression::Call { arguments, .. } | Expression::OperatorCall { arguments, .. }) =
&value.expression
else {
panic!("expected a comparison, got {value:?}");
};
let Expression::Cast {
annotation: TypeExpr::Name { id, name, .. },
..
} = &arguments[0].expression
else {
panic!("expected a cast to a named type");
};
assert_eq!(name, "Int");
*id
});
assert_distinct(ids);
}
Loading