Skip to content

Commit 39e7826

Browse files
timfennisclaude
andauthored
fix: improve type annotations PR (#128)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent b2cfc0f commit 39e7826

12 files changed

Lines changed: 69 additions & 51 deletions

ndc_analyser/src/analyser.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,6 @@ impl Analyser {
617617
found_type: StaticType,
618618
span: Span,
619619
) {
620-
eprintln!("E: {:?}, F: {:?}", expected_type, found_type);
621620
match lvalue {
622621
Lvalue::Identifier {
623622
identifier,
@@ -627,11 +626,11 @@ impl Analyser {
627626
} => {
628627
// If there is a type annotation and the given type is not a subtype of the annotated type we emit an error
629628
if let Some(expected_type) = &expected_type
630-
&& found_type.is_incompatible_with(&expected_type)
629+
&& !found_type.is_subtype(expected_type)
631630
{
632631
self.emit(AnalysisError::mismatched_types(
633-
found_type.clone(),
634-
expected_type.clone(),
632+
&found_type,
633+
expected_type,
635634
*span,
636635
));
637636
}
@@ -673,8 +672,9 @@ impl Analyser {
673672
return;
674673
};
675674

676-
// TODO: emit error that foudn_type could not be unpacked and continue as if it were Any
677-
let found_types = found_type.unpack().unwrap();
675+
let found_types = found_type
676+
.unpack()
677+
.unwrap_or_else(|| Box::new(std::iter::repeat(&StaticType::Any)));
678678

679679
for (sub_lvalue, expected_type, found_type) in
680680
izip!(seq.iter_mut(), sub_types, found_types)
@@ -683,7 +683,7 @@ impl Analyser {
683683
sub_lvalue,
684684
Some(expected_type.clone()),
685685
found_type.clone(),
686-
/* todo: figure out how to narrow this span */ span,
686+
span,
687687
);
688688
}
689689
}
@@ -721,7 +721,7 @@ impl AnalysisError {
721721
self.span
722722
}
723723

724-
fn mismatched_types(found: StaticType, expected: StaticType, span: Span) -> Self {
724+
fn mismatched_types(found: &StaticType, expected: &StaticType, span: Span) -> Self {
725725
Self {
726726
text: format!("mismatched types: found {found} but expected {expected}"),
727727
span,

ndc_bin/src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ impl TryFrom<Command> for Action {
125125
Command::Run {
126126
file: Some(file),
127127
options,
128-
} => Self::RunFile { path: file, options },
128+
} => Self::RunFile {
129+
path: file,
130+
options,
131+
},
129132
Command::Run { file: None, .. } => Self::StartRepl,
130133
Command::Lsp { stdio: _ } => Self::RunLsp,
131134
Command::Disassemble { file } => Self::DisassembleFile(file),
@@ -153,10 +156,7 @@ fn main() -> anyhow::Result<()> {
153156
let action: Action = cli.command.unwrap_or_default().try_into()?;
154157

155158
match action {
156-
Action::RunFile {
157-
path,
158-
options,
159-
} => {
159+
Action::RunFile { path, options } => {
160160
let filename = path
161161
.file_name()
162162
.and_then(|name| name.to_str())

ndc_core/src/static_type.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,15 @@ impl StaticType {
139139
args: Vec<Self>,
140140
) -> Result<Self, StaticTypeConstructionError> {
141141
match name {
142-
"Any" => Self::require_no_args(name, args).map(|_| Self::Any),
143-
"Never" => Self::require_no_args(name, args).map(|_| Self::Never),
144-
"Bool" => Self::require_no_args(name, args).map(|_| Self::Bool),
145-
"Number" => Self::require_no_args(name, args).map(|_| Self::Number),
146-
"Float" => Self::require_no_args(name, args).map(|_| Self::Float),
147-
"Int" => Self::require_no_args(name, args).map(|_| Self::Int),
148-
"Rational" => Self::require_no_args(name, args).map(|_| Self::Rational),
149-
"Complex" => Self::require_no_args(name, args).map(|_| Self::Complex),
150-
"String" => Self::require_no_args(name, args).map(|_| Self::String),
142+
"Any" => Self::require_no_args(name, &args).map(|_| Self::Any),
143+
"Never" => Self::require_no_args(name, &args).map(|_| Self::Never),
144+
"Bool" => Self::require_no_args(name, &args).map(|_| Self::Bool),
145+
"Number" => Self::require_no_args(name, &args).map(|_| Self::Number),
146+
"Float" => Self::require_no_args(name, &args).map(|_| Self::Float),
147+
"Int" => Self::require_no_args(name, &args).map(|_| Self::Int),
148+
"Rational" => Self::require_no_args(name, &args).map(|_| Self::Rational),
149+
"Complex" => Self::require_no_args(name, &args).map(|_| Self::Complex),
150+
"String" => Self::require_no_args(name, &args).map(|_| Self::String),
151151
"Option" => {
152152
Self::require_exactly_one_arg(name, args).map(|elem| Self::Option(Box::new(elem)))
153153
}
@@ -184,7 +184,7 @@ impl StaticType {
184184
}
185185
}
186186

187-
fn require_no_args(name: &str, args: Vec<Self>) -> Result<(), StaticTypeConstructionError> {
187+
fn require_no_args(name: &str, args: &[Self]) -> Result<(), StaticTypeConstructionError> {
188188
if args.is_empty() {
189189
Ok(())
190190
} else {
@@ -207,7 +207,7 @@ impl StaticType {
207207
name: &str,
208208
args: Vec<Self>,
209209
) -> Result<[Self; N], StaticTypeConstructionError> {
210-
args.try_into().map_err(|_: Vec<Self>| {
210+
args.try_into().map_err(|_err: Vec<Self>| {
211211
StaticTypeConstructionError::new(
212212
format!("type `{name}` expects exactly {N} generic arguments"),
213213
format!("Use `{name}<...>` with {N} type arguments."),

ndc_lsp/src/features/inlay_hints.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,19 @@ mod tests {
113113
fn inferred_let_binding_gets_type_inlay() {
114114
let info = collect_hints("let value = 1;");
115115
assert!(
116-
info.hints
117-
.iter()
118-
.any(|hint| matches!(&hint.label, InlayHintLabel::String(label) if label == ": Int"))
116+
info.hints.iter().any(
117+
|hint| matches!(&hint.label, InlayHintLabel::String(label) if label == ": Int")
118+
)
119119
);
120120
}
121121

122122
#[test]
123123
fn annotated_let_binding_skips_type_inlay() {
124124
let info = collect_hints("let value: Int = 1;");
125125
assert!(
126-
!info.hints
127-
.iter()
128-
.any(|hint| matches!(&hint.label, InlayHintLabel::String(label) if label == ": Int"))
126+
!info.hints.iter().any(
127+
|hint| matches!(&hint.label, InlayHintLabel::String(label) if label == ": Int")
128+
)
129129
);
130130
assert_eq!(info.variable_types.get("value"), Some(&StaticType::Int));
131131
}

ndc_parser/src/parser.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -441,14 +441,14 @@ impl Parser {
441441

442442
fn delimited_comma_separated<T>(
443443
&mut self,
444-
open: Token,
445-
close: Token,
444+
open: &Token,
445+
close: &Token,
446446
parse_item: fn(&mut Self) -> Result<T, Error>,
447447
allow_empty: bool,
448448
) -> Result<(Vec<T>, Span), Error> {
449-
let open_span = self.require_current_token_matches(&open)?.span;
449+
let open_span = self.require_current_token_matches(open)?.span;
450450

451-
if let Some(close_token) = self.consume_token_if(&[close.clone()]) {
451+
if let Some(close_token) = self.consume_token_if(std::slice::from_ref(close)) {
452452
if allow_empty {
453453
return Ok((Vec::new(), open_span.merge(close_token.span)));
454454
}
@@ -463,14 +463,14 @@ impl Parser {
463463
let mut items = vec![parse_item(self)?];
464464

465465
while self.consume_token_if(&[Token::Comma]).is_some() {
466-
if self.match_token(&[close.clone()]).is_some() {
466+
if self.match_token(std::slice::from_ref(close)).is_some() {
467467
break;
468468
}
469469

470470
items.push(parse_item(self)?);
471471
}
472472

473-
let close_span = self.require_current_token_matches(&close)?.span;
473+
let close_span = self.require_current_token_matches(close)?.span;
474474
Ok((items, open_span.merge(close_span)))
475475
}
476476

@@ -480,8 +480,8 @@ impl Parser {
480480
next: fn(&mut Self) -> Result<ExpressionLocation, Error>,
481481
) -> Result<ExpressionLocation, Error> {
482482
let (values, span) = self.delimited_comma_separated(
483-
Token::LeftParentheses,
484-
Token::RightParentheses,
483+
&Token::LeftParentheses,
484+
&Token::RightParentheses,
485485
next,
486486
true,
487487
)?;
@@ -1343,7 +1343,7 @@ impl Parser {
13431343

13441344
match token {
13451345
Token::Identifier(_) => self.named_or_generic_type(),
1346-
Token::LeftCurlyBracket => self.tuple_type(),
1346+
Token::LeftParentheses => self.tuple_type(),
13471347
_ => Err(Error::with_help(
13481348
format!("expected a type annotation, found `{token}`"),
13491349
*span,
@@ -1362,7 +1362,7 @@ impl Parser {
13621362
};
13631363

13641364
let generic_args = if self.peek_current_token() == Some(&Token::Less) {
1365-
self.delimited_comma_separated(Token::Less, Token::Greater, Self::static_type, false)?
1365+
self.delimited_comma_separated(&Token::Less, &Token::Greater, Self::static_type, false)?
13661366
.0
13671367
} else {
13681368
Vec::new()
@@ -1373,7 +1373,13 @@ impl Parser {
13731373
}
13741374

13751375
pub fn tuple_type(&mut self) -> Result<StaticType, Error> {
1376-
todo!()
1376+
let (types, _span) = self.delimited_comma_separated(
1377+
&Token::LeftParentheses,
1378+
&Token::RightParentheses,
1379+
Self::static_type,
1380+
true,
1381+
)?;
1382+
Ok(StaticType::Tuple(types))
13771383
}
13781384

13791385
fn peek_range_end(&self) -> bool {

tests/programs/004_basic/046_annotated_let_binding.ndc

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,19 @@ while false {
66
let bool_value: Bool = true;
77
let int_value: Int = 3;
88
let float_value: Float = 3.0;
9-
let rational_value: Rational = 3 / 4;
10-
let complex_value: Complex = 1 + 2i;
9+
let rational_value: Number = 3 / 4;
10+
let complex_value: Number = 1 + 2i;
1111
let number_value: Number = 3;
1212
let string_value: String = "hello";
1313

14-
let option_value: Option<Int> = Some(3);
14+
let option_value: Option<Any> = Some(3);
1515
let sequence_value: Sequence<Int> = [1, 2, 3];
1616
let list_value: List<Int> = [1, 2, 3];
1717
let iterator_value: Iterator<Int> = 1..10;
18-
let min_heap_value: MinHeap<Int> = MinHeap();
19-
let max_heap_value: MaxHeap<Int> = MaxHeap();
20-
let deque_value: Deque<Int> = Deque();
18+
let min_heap_value: MinHeap<Any> = MinHeap();
19+
let max_heap_value: MaxHeap<Any> = MaxHeap();
20+
let deque_value: Deque<Any> = Deque();
2121
let map_value: Map<String, Int> = %{"a": 1, "b": 2};
2222
let tuple_named_value: Tuple<Int, String> = (1, "hello");
23-
24-
// Currently unsupported in the parser:
25-
// - tuple type syntax parsed via `tuple_type()` is still unimplemented
26-
// - function type annotations are not yet parsed
23+
let tuple_shorthand_value: (Int, String) = (1, "hello");
24+
let tuple_empty_value: () = ();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// expect-error: mismatched types: found String but expected Int
2+
let x: Int = "hello";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// expect-error: mismatched types: found Bool but expected String
2+
let x: String = true;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// expect-error: mismatched types: found List<Int> but expected List<String>
2+
let x: List<String> = [1, 2, 3];
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// expect-error: mismatched types: found Tuple<Int, Int> but expected Tuple<String, String>
2+
let x: (String, String) = (1, 2);

0 commit comments

Comments
 (0)