feat: add support for let statements

This commit is contained in:
2026-04-21 23:03:03 +02:00
parent 42ba357302
commit 3fe307eff8
8 changed files with 212 additions and 10 deletions
+6
View File
@@ -101,6 +101,12 @@ pub enum StmtKind<P: Phase = Untyped> {
Return {
value: Option<Expr<P>>,
},
Let {
name: String,
name_span: Span,
ty: P::ReturnType,
initializer: Option<Expr<P>>,
},
}
#[derive(Debug, PartialEq, Eq)]
+3 -1
View File
@@ -67,6 +67,7 @@ impl<'src> Lexer<'src> {
"if" => TokenKind::If,
"else" => TokenKind::Else,
"return" => TokenKind::Return,
"let" => TokenKind::Let,
"i8" => TokenKind::I8,
"i16" => TokenKind::I16,
@@ -205,7 +206,7 @@ mod test {
#[test]
fn identifiers() {
assert_eq!(
tokenize("HELLO _hello _0@ fn if else return"),
tokenize("HELLO _hello _0@ fn if else return let"),
vec![
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
@@ -215,6 +216,7 @@ mod test {
Token::new(TokenKind::If, "if", Span::new(20, 22)),
Token::new(TokenKind::Else, "else", Span::new(23, 27)),
Token::new(TokenKind::Return, "return", Span::new(28, 34)),
Token::new(TokenKind::Let, "let", Span::new(35, 38)),
]
)
}
+81 -1
View File
@@ -285,7 +285,9 @@ impl<'src> Parser<'src> {
/// ```ebnf
/// stmt = compound_stmt
/// | if_stmt
/// | return_stmt ;
/// | return_stmt
/// | let_stmt
/// ;
/// ```
pub fn parse_stmt(&mut self) -> ParseResult<Stmt> {
let peek_token = self.peek_no_eof()?;
@@ -294,6 +296,7 @@ impl<'src> Parser<'src> {
TokenKind::LBrace => self.parse_compound_stmt(),
TokenKind::If => self.parse_if_stmt(),
TokenKind::Return => self.parse_return_stmt(),
TokenKind::Let => self.parse_let_stmt(),
_ => Err(ParseError::new(
format!("expected a statement but found {} instead", peek_token.kind),
@@ -398,6 +401,47 @@ impl<'src> Parser<'src> {
})
}
/// Parses a let statement.
///
/// ```ebnf
/// let_stmt = "let" IDENTIFIER [ ":" type ] [ "=" expr ] ";" ;
/// ```
fn parse_let_stmt(&mut self) -> ParseResult<Stmt> {
let let_token = self.expect(TokenKind::Let)?;
let (name, name_span) = {
let ident_token = self.expect(TokenKind::Identifier)?;
(ident_token.text.to_string(), ident_token.span)
};
let ty = if self.is_peek(TokenKind::Colon) {
self.advance();
Some(self.parse_type()?)
} else {
None
};
let initializer = if self.is_peek(TokenKind::Assign) {
self.advance();
Some(self.parse_expr()?)
} else {
None
};
let semi_token = self.expect(TokenKind::Semicolon)?;
let span = let_token.span.join(semi_token.span);
Ok(Stmt {
kind: StmtKind::Let {
name,
name_span,
ty,
initializer,
},
span,
})
}
// ====== Pratt Parsing Implementation ======
/// Parses an expression.
@@ -795,6 +839,42 @@ mod test {
);
}
#[test]
fn let_stmt() {
assert_eq!(
parse("let a: i32 = 5;", Parser::parse_stmt),
Success(Stmt {
kind: StmtKind::Let {
name: "a".to_string(),
name_span: Span::new(4, 5),
ty: Some(Type {
kind: TypeKind::I32,
span: Span::new(7, 10),
}),
initializer: Some(Expr {
kind: ExprKind::Integer { value: 5 },
ty: (),
span: Span::new(13, 14),
}),
},
span: Span::new(0, 15)
})
);
assert_eq!(
parse("let a;", Parser::parse_stmt),
Success(Stmt {
kind: StmtKind::Let {
name: "a".to_string(),
name_span: Span::new(4, 5),
ty: None,
initializer: None,
},
span: Span::new(0, 6)
})
);
}
#[test]
fn function_decl() {
assert_eq!(
+66
View File
@@ -348,6 +348,48 @@ impl Sema {
}
}
}
StmtKind::Let {
name,
name_span,
ty: type_annotation,
initializer,
} => {
let explicit_ty = type_annotation.as_ref().map(|t| Ty::from(&t.kind));
let (inferred_ty, typed_initializer) = match initializer {
Some(expr) => {
let typed_expr = self.analyze_expr(expr);
if let Some(ref ext_ty) = explicit_ty {
if let Err(err) = self.unify(&typed_expr.ty, ext_ty) {
self.errors.push(SemanticError::new(err, expr.span));
}
}
(typed_expr.ty.clone(), Some(typed_expr))
}
None => {
if let Some(ref ext_ty) = explicit_ty {
(ext_ty.clone(), None)
} else {
self.errors
.push(SemanticError::new("type annotation needed", *name_span));
(self.new_var(), None)
}
}
};
let final_ty = explicit_ty.unwrap_or(inferred_ty);
self.bind(name, final_ty.clone());
TypedStmt {
kind: TypedStmtKind::Let {
name: name.clone(),
name_span: *name_span,
ty: final_ty,
initializer: typed_initializer,
},
span: stmt.span,
}
}
}
}
@@ -532,6 +574,18 @@ impl Sema {
TypedStmtKind::Return { value } => TypedStmtKind::Return {
value: value.map(|e| self.apply_subst_expr(e)),
},
TypedStmtKind::Let {
name,
name_span,
ty,
initializer,
} => TypedStmtKind::Let {
name,
name_span,
ty: self.apply_subst(&ty),
initializer: initializer.map(|e| self.apply_subst_expr(e)),
},
};
TypedStmt { kind, span }
@@ -797,4 +851,16 @@ mod test {
let src = "fn test() { if 12 {} }";
assert!(analyze(src).is_err());
}
#[test]
fn valid_let() {
let src = "fn test() { let a = 5; let b: i32 = a; }";
assert!(analyze(src).is_ok());
}
#[test]
fn let_missing_type() {
let src = "fn test() { let a; }";
assert!(analyze(src).is_err());
}
}
+2
View File
@@ -59,6 +59,7 @@ pub enum TokenKind {
If,
Else,
Return,
Let,
// Types
I8,
@@ -115,6 +116,7 @@ impl Display for TokenKind {
TokenKind::If => "`if`",
TokenKind::Else => "`else`",
TokenKind::Return => "`return`",
TokenKind::Let => "`let`",
TokenKind::I8 => "`i8`",
TokenKind::I16 => "`i16`",
TokenKind::I32 => "`i32`",