Feat: add mutable pointer type *mut T and *mut opaque

- Grammar: update pointer_type to support optional mut keyword;
  LL(1) verified (56 named rules, no conflicts)
- AST: update Type enum with mutable: bool field for Pointer and
  OpaquePointer variants
- Parser: consume optional mut token in parse_type; update all
  existing pointer tests; add 4 new mut pointer tests (85 pass)
- VSCode extension: add *mut capture-group pattern for syntax
  highlighting; update SYNTAX.md with pointer mutability table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 10:37:21 +01:00
parent 53c66a3d03
commit 7cdcad7df3
5 changed files with 97 additions and 29 deletions

View File

@@ -76,10 +76,10 @@ pub enum Type {
Char,
// User-defined named type (e.g. a struct)
Named(String, Span),
// Typed pointer: `*type`
Pointer(Box<Type>),
// Opaque (untyped) pointer: `*opaque`
OpaquePointer,
// Typed pointer: `*type` (immutable) or `*mut type` (mutable)
Pointer { mutable: bool, pointee: Box<Type> },
// Opaque (untyped) pointer: `*opaque` (immutable) or `*mut opaque` (mutable)
OpaquePointer { mutable: bool },
// Fixed-size array: `[type; INT_LIT]`
Array { elem: Box<Type>, size: String },
// Error placeholder for recovery

View File

@@ -223,13 +223,19 @@ impl<'src> Parser<'src> {
// Named type (user-defined struct, etc.)
TokenKind::Ident => Type::Named(tok.text.to_owned(), tok.span),
// Pointer: `*opaque` or `*<type>`
// Pointer: `*[mut] opaque` or `*[mut] <type>`
TokenKind::Star => {
let mutable = if self.current().kind == TokenKind::Mut {
self.advance();
true
} else {
false
};
if self.current().kind == TokenKind::Opaque {
self.advance();
Type::OpaquePointer
Type::OpaquePointer { mutable }
} else {
Type::Pointer(Box::new(self.parse_type()))
Type::Pointer { mutable, pointee: Box::new(self.parse_type()) }
}
}
@@ -1177,12 +1183,34 @@ mod tests {
#[test]
fn type_pointer() {
assert!(matches!(parse_type_str("*i32"), Type::Pointer(_)));
assert!(matches!(
parse_type_str("*i32"),
Type::Pointer { mutable: false, .. }
));
}
#[test]
fn type_mut_pointer() {
assert!(matches!(
parse_type_str("*mut i32"),
Type::Pointer { mutable: true, .. }
));
}
#[test]
fn type_opaque_pointer() {
assert!(matches!(parse_type_str("*opaque"), Type::OpaquePointer));
assert!(matches!(
parse_type_str("*opaque"),
Type::OpaquePointer { mutable: false }
));
}
#[test]
fn type_mut_opaque_pointer() {
assert!(matches!(
parse_type_str("*mut opaque"),
Type::OpaquePointer { mutable: true }
));
}
#[test]
@@ -1194,8 +1222,17 @@ mod tests {
#[test]
fn type_nested_pointer() {
// `**i32` → Pointer(Pointer(I32))
assert!(matches!(parse_type_str("**i32"), Type::Pointer(_)));
// `**i32` → Pointer { Pointer { I32 } }
assert!(matches!(parse_type_str("**i32"), Type::Pointer { mutable: false, .. }));
}
#[test]
fn type_nested_mut_pointer() {
// `*mut *mut i32` → Pointer { mutable: true, Pointer { mutable: true, I32 } }
assert!(matches!(
parse_type_str("*mut *mut i32"),
Type::Pointer { mutable: true, .. }
));
}
// ── Statement tests ───────────────────────────────────────────────────────
@@ -1466,7 +1503,7 @@ mod tests {
let d = top("fn foo(p: *i32) { }");
match &d.kind {
TopLevelDefKind::Func(f) => {
assert!(matches!(f.params[0].ty, Type::Pointer(_)));
assert!(matches!(f.params[0].ty, Type::Pointer { mutable: false, .. }));
}
_ => panic!("expected func def"),
}
@@ -1506,7 +1543,7 @@ mod tests {
let d = top("struct Node { value: i32, next: *Node }");
match &d.kind {
TopLevelDefKind::Struct(s) => {
assert!(matches!(s.fields[1].ty, Type::Pointer(_)));
assert!(matches!(s.fields[1].ty, Type::Pointer { mutable: false, .. }));
}
_ => panic!("expected struct def"),
}