@@ -42,7 +42,7 @@ fn infix_precedence(kind: &TokenKind) -> Option<Precedence> {
4242 TokenKind :: QuestionQuestion => Some ( Precedence :: NullCoalesce ) ,
4343 TokenKind :: Plus | TokenKind :: Minus => Some ( Precedence :: Addition ) ,
4444 TokenKind :: Star | TokenKind :: Slash | TokenKind :: Percent => Some ( Precedence :: Multiplication ) ,
45- TokenKind :: As => Some ( Precedence :: Cast ) ,
45+ TokenKind :: As | TokenKind :: Is => Some ( Precedence :: Cast ) ,
4646 TokenKind :: Dot
4747 | TokenKind :: QuestionDot
4848 | TokenKind :: LeftParen
@@ -316,6 +316,34 @@ impl Parser {
316316 Ok ( left)
317317 }
318318
319+ /// Like `parse_expr` but does not cross newline boundaries.
320+ /// Used for single-expression `when` arm bodies to prevent the Pratt loop
321+ /// from consuming the `is` keyword of the next arm as an infix type-check.
322+ fn parse_expr_line ( & mut self ) -> Result < Expr , ParseError > {
323+ let mut left = self . parse_prefix ( ) ?;
324+
325+ loop {
326+ if matches ! (
327+ self . peek( ) ,
328+ TokenKind :: Newline | TokenKind :: Semicolon | TokenKind :: Eof | TokenKind :: RightBrace
329+ ) {
330+ break ;
331+ }
332+
333+ let Some ( prec) = infix_precedence ( self . peek ( ) ) else {
334+ break ;
335+ } ;
336+
337+ if prec <= Precedence :: Lowest {
338+ break ;
339+ }
340+
341+ left = self . parse_infix ( left, prec) ?;
342+ }
343+
344+ Ok ( left)
345+ }
346+
319347 // --- Prefix parsing (atoms + unary) ---
320348
321349 fn parse_prefix ( & mut self ) -> Result < Expr , ParseError > {
@@ -616,6 +644,18 @@ impl Parser {
616644 } )
617645 }
618646
647+ // Type check: expr is Type
648+ TokenKind :: Is => {
649+ let target_type = self . parse_type_expr ( ) ?;
650+ Ok ( Expr {
651+ kind : ExprKind :: TypeCheck {
652+ expr : Box :: new ( left) ,
653+ target_type,
654+ } ,
655+ span,
656+ } )
657+ }
658+
619659 // Call: callee(arg1, name: arg2, ...)
620660 TokenKind :: LeftParen => {
621661 self . skip_newlines ( ) ;
@@ -1251,7 +1291,7 @@ impl Parser {
12511291 let body = if self . peek ( ) == & TokenKind :: LeftBrace {
12521292 WhenBody :: Block ( self . parse_block ( ) ?)
12531293 } else {
1254- WhenBody :: Expr ( self . parse_expr ( ) ?)
1294+ WhenBody :: Expr ( self . parse_expr_line ( ) ?)
12551295 } ;
12561296
12571297 self . consume_stmt_terminator ( ) ;
@@ -2164,4 +2204,74 @@ mod tests {
21642204 panic ! ( "expected func decl" ) ;
21652205 }
21662206 }
2207+
2208+ #[ test]
2209+ fn parse_is_expression_simple ( ) {
2210+ let tokens = Lexer :: new ( "let x = obj is Player" ) . tokenize ( ) . unwrap ( ) ;
2211+ let mut parser = Parser :: new ( tokens) ;
2212+ let stmts = parser. parse_program ( ) . unwrap ( ) ;
2213+ if let StmtKind :: Let { initializer, .. } = & stmts[ 0 ] . kind {
2214+ assert ! ( matches!(
2215+ & initializer. kind,
2216+ ExprKind :: TypeCheck {
2217+ target_type: TypeExpr :: Simple ( name) ,
2218+ ..
2219+ } if name == "Player"
2220+ ) ) ;
2221+ } else {
2222+ panic ! ( "expected let stmt" ) ;
2223+ }
2224+ }
2225+
2226+ #[ test]
2227+ fn parse_is_expression_qualified ( ) {
2228+ let tokens = Lexer :: new ( "let x = obj is enemy::Enemy" )
2229+ . tokenize ( )
2230+ . unwrap ( ) ;
2231+ let mut parser = Parser :: new ( tokens) ;
2232+ let stmts = parser. parse_program ( ) . unwrap ( ) ;
2233+ if let StmtKind :: Let { initializer, .. } = & stmts[ 0 ] . kind {
2234+ assert ! ( matches!(
2235+ & initializer. kind,
2236+ ExprKind :: TypeCheck {
2237+ target_type: TypeExpr :: Qualified { namespace, name } ,
2238+ ..
2239+ } if namespace == "enemy" && name == "Enemy"
2240+ ) ) ;
2241+ } else {
2242+ panic ! ( "expected let stmt" ) ;
2243+ }
2244+ }
2245+
2246+ #[ test]
2247+ fn when_type_match_two_arms_no_infix_consumed ( ) {
2248+ let src = "when result {\n is Success(v) => print(v)\n is Error(e) => print(e)\n }" ;
2249+ let tokens = Lexer :: new ( src) . tokenize ( ) . unwrap ( ) ;
2250+ let mut parser = Parser :: new ( tokens) ;
2251+ let stmts = parser. parse_program ( ) . unwrap ( ) ;
2252+ if let StmtKind :: When { arms, .. } = & stmts[ 0 ] . kind {
2253+ assert_eq ! ( arms. len( ) , 2 ) ;
2254+ // Second arm must be a TypeMatch, not consumed by first arm's body expression
2255+ assert ! (
2256+ matches!( arms[ 1 ] . pattern, WhenPattern :: TypeMatch { .. } ) ,
2257+ "second arm pattern was {:?}, expected TypeMatch" ,
2258+ arms[ 1 ] . pattern
2259+ ) ;
2260+ } else {
2261+ panic ! ( "expected when stmt" ) ;
2262+ }
2263+ }
2264+
2265+ #[ test]
2266+ fn type_check_expr_is_still_valid ( ) {
2267+ // `obj is Player` must still parse as a TypeCheck expression (not broken by the fix)
2268+ let tokens = Lexer :: new ( "let x = obj is Player" ) . tokenize ( ) . unwrap ( ) ;
2269+ let mut parser = Parser :: new ( tokens) ;
2270+ let stmts = parser. parse_program ( ) . unwrap ( ) ;
2271+ if let StmtKind :: Let { initializer, .. } = & stmts[ 0 ] . kind {
2272+ assert ! ( matches!( initializer. kind, ExprKind :: TypeCheck { .. } ) ) ;
2273+ } else {
2274+ panic ! ( "expected let stmt" ) ;
2275+ }
2276+ }
21672277}
0 commit comments