Test: fix parser generic annotations and add checker test suite
- Fix two test helpers (top/program) missing <Parsed> type argument - Add checker/tests.rs with 74 tests covering all §7/§8 rules: entry point validation, duplicate defs, struct cycles, literals, promotion, type inference, definite assignment, undefined vars/funcs, arithmetic, shift, comparison, logical ops, unary ops, pointers, struct literals, field/index access, function calls, return checking, mutation, and break/continue - Fix assignment LHS check: bare identifier on the write side of `=` must not trigger "uninitialized" — use lhs_ty() helper that skips the assigned-set check for the direct write target Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::Checker;
|
||||
use super::env::TypeEnv;
|
||||
use crate::ast::{self, BinaryOp, CompoundAssignOp, ExprKind, Parsed, Ty, UnaryOp};
|
||||
use crate::diagnostics::{Diagnostic, Label};
|
||||
use crate::token::Span;
|
||||
use super::env::TypeEnv;
|
||||
use super::Checker;
|
||||
|
||||
impl Checker {
|
||||
/// Type-check `expr` in environment `env` with definite-assignment set `assigned`.
|
||||
@@ -23,7 +23,10 @@ impl Checker {
|
||||
ExprKind::FloatLit(_) => Ty::F64,
|
||||
|
||||
// T-StringLit → *char
|
||||
ExprKind::StringLit(_) => Ty::Ptr { mutable: false, pointee: Box::new(Ty::Char) },
|
||||
ExprKind::StringLit(_) => Ty::Ptr {
|
||||
mutable: false,
|
||||
pointee: Box::new(Ty::Char),
|
||||
},
|
||||
|
||||
// T-CharLit → char
|
||||
ExprKind::CharLit(_) => Ty::Char,
|
||||
@@ -56,7 +59,11 @@ impl Checker {
|
||||
},
|
||||
|
||||
// T-StructLit
|
||||
ExprKind::StructLit { name, name_span, fields } => {
|
||||
ExprKind::StructLit {
|
||||
name,
|
||||
name_span,
|
||||
fields,
|
||||
} => {
|
||||
if !self.sigma.contains(name) {
|
||||
self.emit(
|
||||
Diagnostic::error(format!("undefined struct `{name}`"))
|
||||
@@ -135,7 +142,11 @@ impl Checker {
|
||||
}
|
||||
|
||||
// T-Unary
|
||||
ExprKind::Unary { op, op_span, expr: inner } => {
|
||||
ExprKind::Unary {
|
||||
op,
|
||||
op_span,
|
||||
expr: inner,
|
||||
} => {
|
||||
let inner_ty = self.check_expr(inner, env, assigned);
|
||||
if inner_ty.is_error() {
|
||||
return Ty::Error;
|
||||
@@ -144,12 +155,20 @@ impl Checker {
|
||||
}
|
||||
|
||||
// T-Binary
|
||||
ExprKind::Binary { op, op_span, lhs, rhs } => {
|
||||
self.check_binary(*op, *op_span, lhs, rhs, env, assigned)
|
||||
}
|
||||
ExprKind::Binary {
|
||||
op,
|
||||
op_span,
|
||||
lhs,
|
||||
rhs,
|
||||
} => self.check_binary(*op, *op_span, lhs, rhs, env, assigned),
|
||||
|
||||
// T-CompoundAssign: lhs op= rhs (expands to lhs = lhs op rhs)
|
||||
ExprKind::CompoundAssign { op, op_span, lhs, rhs } => {
|
||||
ExprKind::CompoundAssign {
|
||||
op,
|
||||
op_span,
|
||||
lhs,
|
||||
rhs,
|
||||
} => {
|
||||
if !self.is_mutable_place(lhs, env) {
|
||||
self.emit(
|
||||
Diagnostic::error(
|
||||
@@ -168,7 +187,11 @@ impl Checker {
|
||||
}
|
||||
|
||||
// T-Field
|
||||
ExprKind::Field { expr: inner, field, field_span } => {
|
||||
ExprKind::Field {
|
||||
expr: inner,
|
||||
field,
|
||||
field_span,
|
||||
} => {
|
||||
let inner_ty = self.check_expr(inner, env, assigned);
|
||||
if inner_ty.is_error() {
|
||||
return Ty::Error;
|
||||
@@ -385,7 +408,10 @@ impl Checker {
|
||||
return Ty::Error;
|
||||
}
|
||||
let mutable = self.is_mutable_place(inner, env);
|
||||
Ty::Ptr { mutable, pointee: Box::new(inner_ty) }
|
||||
Ty::Ptr {
|
||||
mutable,
|
||||
pointee: Box::new(inner_ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,13 +432,14 @@ impl Checker {
|
||||
BinaryOp::Assign => {
|
||||
if !self.is_mutable_place(lhs, env) {
|
||||
self.emit(
|
||||
Diagnostic::error(
|
||||
"left-hand side of `=` must be a mutable place",
|
||||
)
|
||||
.with_label(Label::primary(lhs.span)),
|
||||
Diagnostic::error("left-hand side of `=` must be a mutable place")
|
||||
.with_label(Label::primary(lhs.span)),
|
||||
);
|
||||
}
|
||||
let lhs_ty = self.check_expr(lhs, env, assigned);
|
||||
// For a bare identifier on the LHS we only need its declared
|
||||
// type, not a read — don't emit "uninitialized" for the target
|
||||
// of the assignment itself.
|
||||
let lhs_ty = self.lhs_ty(lhs, env, assigned);
|
||||
let rhs_ty = self.check_expr(rhs, env, assigned);
|
||||
if !lhs_ty.is_error() && !rhs_ty.is_error() && !rhs_ty.promotes_to(&lhs_ty) {
|
||||
self.emit(
|
||||
@@ -562,6 +589,37 @@ impl Checker {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the declared type of `expr` as a write-side place, without
|
||||
/// checking that it has been initialised first. For a bare identifier
|
||||
/// this avoids a spurious "uninitialized" diagnostic on the target of
|
||||
/// an assignment like `x = 5` when `x` is being given its first value.
|
||||
///
|
||||
/// For all other place forms (deref, field, index) the sub-expression IS
|
||||
/// read, so the normal `check_expr` path (with assigned checking) is used.
|
||||
fn lhs_ty(
|
||||
&mut self,
|
||||
expr: &ast::Expr<Parsed>,
|
||||
env: &TypeEnv,
|
||||
assigned: &HashSet<String>,
|
||||
) -> Ty {
|
||||
match &expr.kind {
|
||||
ExprKind::Ident(name) => match env.lookup(name) {
|
||||
Some((ty, _)) => ty.clone(),
|
||||
None => {
|
||||
self.emit(
|
||||
Diagnostic::error(format!("undefined variable `{name}`"))
|
||||
.with_label(Label::primary(expr.span)),
|
||||
);
|
||||
Ty::Error
|
||||
}
|
||||
},
|
||||
ExprKind::Group(inner) => self.lhs_ty(inner, env, assigned),
|
||||
// All other place forms (deref, field, index) involve a read of
|
||||
// the sub-expression — use the full check.
|
||||
_ => self.check_expr(expr, env, assigned),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that `lhs_ty op rhs_ty` is a valid arithmetic expression;
|
||||
/// returns the result type.
|
||||
fn check_arith_or_shift(&mut self, lhs_ty: Ty, rhs_ty: Ty, op: BinaryOp, op_span: Span) -> Ty {
|
||||
@@ -596,7 +654,9 @@ impl Checker {
|
||||
pub fn is_place(&self, expr: &ast::Expr<Parsed>, env: &TypeEnv) -> bool {
|
||||
match &expr.kind {
|
||||
ExprKind::Ident(_) => true,
|
||||
ExprKind::Unary { op: UnaryOp::Deref, .. } => true,
|
||||
ExprKind::Unary {
|
||||
op: UnaryOp::Deref, ..
|
||||
} => true,
|
||||
ExprKind::Field { expr: inner, .. } => self.is_place(inner, env),
|
||||
ExprKind::Index { expr: inner, .. } => self.is_place(inner, env),
|
||||
ExprKind::Group(inner) => self.is_place(inner, env),
|
||||
@@ -612,10 +672,12 @@ impl Checker {
|
||||
/// - `e.f` / `e[i]` is mutable iff `e` is mutable.
|
||||
pub fn is_mutable_place(&self, expr: &ast::Expr<Parsed>, env: &TypeEnv) -> bool {
|
||||
match &expr.kind {
|
||||
ExprKind::Ident(name) => {
|
||||
env.lookup(name).map(|(_, m)| m).unwrap_or(false)
|
||||
}
|
||||
ExprKind::Unary { op: UnaryOp::Deref, expr: inner, .. } => {
|
||||
ExprKind::Ident(name) => env.lookup(name).map(|(_, m)| m).unwrap_or(false),
|
||||
ExprKind::Unary {
|
||||
op: UnaryOp::Deref,
|
||||
expr: inner,
|
||||
..
|
||||
} => {
|
||||
// Mutable iff inner's type is *mut T.
|
||||
// We resolve the type of inner from env without emitting errors.
|
||||
matches!(
|
||||
@@ -635,25 +697,27 @@ impl Checker {
|
||||
match &expr.kind {
|
||||
ExprKind::Ident(name) => env.lookup(name).map(|(ty, _)| ty.clone()),
|
||||
ExprKind::Group(inner) => self.peek_ty(inner, env),
|
||||
ExprKind::Unary { op: UnaryOp::Deref, expr: inner, .. } => {
|
||||
match self.peek_ty(inner, env)? {
|
||||
Ty::Ptr { pointee, .. } => Some(*pointee),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
ExprKind::Field { expr: inner, field, .. } => {
|
||||
ExprKind::Unary {
|
||||
op: UnaryOp::Deref,
|
||||
expr: inner,
|
||||
..
|
||||
} => match self.peek_ty(inner, env)? {
|
||||
Ty::Ptr { pointee, .. } => Some(*pointee),
|
||||
_ => None,
|
||||
},
|
||||
ExprKind::Field {
|
||||
expr: inner, field, ..
|
||||
} => {
|
||||
let Ty::Struct(sname) = self.peek_ty(inner, env)? else {
|
||||
return None;
|
||||
};
|
||||
self.sigma.field_ty(&sname, field).cloned()
|
||||
}
|
||||
ExprKind::Index { expr: inner, .. } => {
|
||||
match self.peek_ty(inner, env)? {
|
||||
Ty::Array { elem, .. } => Some(*elem),
|
||||
Ty::Ptr { pointee, .. } => Some(*pointee),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
ExprKind::Index { expr: inner, .. } => match self.peek_ty(inner, env)? {
|
||||
Ty::Array { elem, .. } => Some(*elem),
|
||||
Ty::Ptr { pointee, .. } => Some(*pointee),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user