feat: add as type casting
This commit is contained in:
@@ -6,6 +6,7 @@ pub trait Phase: Debug + PartialEq + Eq {
|
||||
type ReturnType: Debug + PartialEq + Eq;
|
||||
type ParamType: Debug + PartialEq + Eq;
|
||||
type ExprType: Debug + PartialEq + Eq;
|
||||
type CastType: Debug + PartialEq + Eq;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -15,6 +16,7 @@ impl Phase for Untyped {
|
||||
type ReturnType = Option<Type>;
|
||||
type ParamType = FunctionParam;
|
||||
type ExprType = ();
|
||||
type CastType = Type;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -24,6 +26,7 @@ impl Phase for Typed {
|
||||
type ReturnType = Ty;
|
||||
type ParamType = (String, Ty);
|
||||
type ExprType = Ty;
|
||||
type CastType = Ty;
|
||||
}
|
||||
|
||||
pub type TypedModule = Module<Typed>;
|
||||
@@ -154,6 +157,10 @@ pub enum ExprKind<P: Phase = Untyped> {
|
||||
lval: Box<Expr<P>>,
|
||||
rval: Box<Expr<P>>,
|
||||
},
|
||||
Cast {
|
||||
expr: Box<Expr<P>>,
|
||||
ty: P::CastType,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -65,6 +65,7 @@ impl<'src> Lexer<'src> {
|
||||
match &self.source[start..self.cursor] {
|
||||
"fn" => TokenKind::Fn,
|
||||
"if" => TokenKind::If,
|
||||
"as" => TokenKind::As,
|
||||
"else" => TokenKind::Else,
|
||||
"return" => TokenKind::Return,
|
||||
"let" => TokenKind::Let,
|
||||
@@ -248,7 +249,7 @@ mod test {
|
||||
#[test]
|
||||
fn identifiers() {
|
||||
assert_eq!(
|
||||
tokenize("HELLO _hello _0@ fn if else return let while break continue"),
|
||||
tokenize("HELLO _hello _0@ fn if else return let while break continue as"),
|
||||
vec![
|
||||
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
||||
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
||||
@@ -262,6 +263,7 @@ mod test {
|
||||
Token::new(TokenKind::While, "while", Span::new(39, 44)),
|
||||
Token::new(TokenKind::Break, "break", Span::new(45, 50)),
|
||||
Token::new(TokenKind::Continue, "continue", Span::new(51, 59)),
|
||||
Token::new(TokenKind::As, "as", Span::new(60, 62)),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -559,6 +559,28 @@ impl<'src> Parser<'src> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if peek_token.kind == TokenKind::As {
|
||||
let left_bp = 25; // high precedence, tighter than multiply
|
||||
if left_bp < min_bp {
|
||||
break;
|
||||
}
|
||||
self.advance(); // consume `as`
|
||||
|
||||
let ty = self.parse_type()?;
|
||||
let span = lhs.span.join(ty.span);
|
||||
|
||||
lhs = Expr {
|
||||
kind: ExprKind::Cast {
|
||||
expr: Box::new(lhs),
|
||||
ty,
|
||||
},
|
||||
ty: (),
|
||||
span,
|
||||
};
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
|
||||
break; // Not an infix operator
|
||||
};
|
||||
@@ -1229,4 +1251,26 @@ mod test {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cast_expr() {
|
||||
assert_eq!(
|
||||
parse("5 as f32;", Parser::parse_expr),
|
||||
Success(Expr {
|
||||
kind: ExprKind::Cast {
|
||||
expr: Box::new(Expr {
|
||||
kind: ExprKind::Integer { value: 5 },
|
||||
ty: (),
|
||||
span: Span::new(0, 1)
|
||||
}),
|
||||
ty: Type {
|
||||
kind: TypeKind::F32,
|
||||
span: Span::new(5, 8)
|
||||
}
|
||||
},
|
||||
ty: (),
|
||||
span: Span::new(0, 8)
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,30 @@ impl Ty {
|
||||
pub fn is_numeric(&self) -> bool {
|
||||
self.is_integer() || self.is_float()
|
||||
}
|
||||
|
||||
/// Returns `true` if the type is signed (including floats).
|
||||
pub fn is_signed(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64 | Ty::F32 | Ty::F64
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the type is unsigned or a boolean.
|
||||
pub fn is_unsigned(&self) -> bool {
|
||||
matches!(self, Ty::U8 | Ty::U16 | Ty::U32 | Ty::U64 | Ty::Bool)
|
||||
}
|
||||
|
||||
/// Returns the exact bit width of the type.
|
||||
pub fn bit_width(&self) -> usize {
|
||||
match self {
|
||||
Ty::I8 | Ty::U8 | Ty::Bool => 8,
|
||||
Ty::I16 | Ty::U16 => 16,
|
||||
Ty::I32 | Ty::U32 | Ty::F32 => 32,
|
||||
Ty::I64 | Ty::U64 | Ty::F64 => 64,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TypeKind> for Ty {
|
||||
@@ -93,6 +117,7 @@ pub struct Sema {
|
||||
deferred_binary: Vec<(Span, Ty)>,
|
||||
deferred_int_literals: Vec<(Span, Ty)>,
|
||||
deferred_float_literals: Vec<(Span, Ty)>,
|
||||
deferred_casts: Vec<(Span, Ty, Ty)>,
|
||||
loop_depth: usize,
|
||||
}
|
||||
|
||||
@@ -108,6 +133,7 @@ impl Sema {
|
||||
deferred_binary: Vec::new(),
|
||||
deferred_int_literals: Vec::new(),
|
||||
deferred_float_literals: Vec::new(),
|
||||
deferred_casts: Vec::new(),
|
||||
loop_depth: 0,
|
||||
}
|
||||
}
|
||||
@@ -621,6 +647,22 @@ impl Sema {
|
||||
span: expr.span,
|
||||
}
|
||||
}
|
||||
ExprKind::Cast { expr: inner, ty } => {
|
||||
let typed_inner = self.analyze_expr(inner);
|
||||
let target_ty = Ty::from(&ty.kind);
|
||||
|
||||
self.deferred_casts
|
||||
.push((expr.span, typed_inner.ty.clone(), target_ty.clone()));
|
||||
|
||||
TypedExpr {
|
||||
kind: TypedExprKind::Cast {
|
||||
expr: Box::new(typed_inner),
|
||||
ty: target_ty.clone(),
|
||||
},
|
||||
ty: target_ty,
|
||||
span: expr.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,6 +771,10 @@ impl Sema {
|
||||
lval: Box::new(self.apply_subst_expr(*lval)),
|
||||
rval: Box::new(self.apply_subst_expr(*rval)),
|
||||
},
|
||||
TypedExprKind::Cast { expr, ty } => TypedExprKind::Cast {
|
||||
expr: Box::new(self.apply_subst_expr(*expr)),
|
||||
ty: self.apply_subst(&ty),
|
||||
},
|
||||
};
|
||||
|
||||
TypedExpr { kind, ty, span }
|
||||
@@ -859,6 +905,29 @@ impl Sema {
|
||||
.push(SemanticError::new("expected float type for literal", span));
|
||||
}
|
||||
}
|
||||
|
||||
for (span, from_ty, to_ty) in std::mem::take(&mut self.deferred_casts) {
|
||||
let from = self.apply_subst(&from_ty);
|
||||
let to = self.apply_subst(&to_ty);
|
||||
|
||||
let final_from = if let Ty::Var(_) = from {
|
||||
let default = Ty::I32; // Unconstrained fallback
|
||||
let _ = self.unify(&from, &default);
|
||||
default
|
||||
} else {
|
||||
from
|
||||
};
|
||||
|
||||
let is_valid = (final_from.is_numeric() || final_from == Ty::Bool)
|
||||
&& (to.is_numeric() || to == Ty::Bool);
|
||||
|
||||
if !is_valid {
|
||||
self.errors.push(SemanticError::new(
|
||||
format!("cannot cast from `{:?}` to `{:?}`", final_from, to),
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,4 +1147,10 @@ mod test {
|
||||
.any(|e| e.message.contains("`break` outside of a loop"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_cast() {
|
||||
let src = "fn test() { let a: f32 = 10.0; let b = a as i32; }";
|
||||
assert!(analyze(src).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ pub enum TokenKind {
|
||||
// Keywords
|
||||
Fn,
|
||||
If,
|
||||
As,
|
||||
Else,
|
||||
Return,
|
||||
Let,
|
||||
@@ -120,6 +121,7 @@ impl Display for TokenKind {
|
||||
TokenKind::BooleanLit => "a boolean",
|
||||
TokenKind::FloatLit => "a float",
|
||||
TokenKind::Fn => "`fn`",
|
||||
TokenKind::As => "`as`",
|
||||
TokenKind::If => "`if`",
|
||||
TokenKind::Else => "`else`",
|
||||
TokenKind::Return => "`return`",
|
||||
|
||||
Reference in New Issue
Block a user