diff --git a/src/canonicalize/NodeStore.zig b/src/canonicalize/NodeStore.zig index da79023385..93574d6e80 100644 --- a/src/canonicalize/NodeStore.zig +++ b/src/canonicalize/NodeStore.zig @@ -719,8 +719,10 @@ pub fn getExpr(store: *const NodeStore, expr: CIR.Expr.Idx) CIR.Expr { } }; }, .expr_dot_access => { + // Subtract 1 to undo the +1 offset used during storage + // This allows distinguishing empty args {start:0, len:0} from null args const args_span = if (node.data_3 != 0) blk: { - const packed_span = FunctionArgs.fromU32(node.data_3); + const packed_span = FunctionArgs.fromU32(node.data_3 - 1); const data_span = packed_span.toDataSpan(); break :blk CIR.Expr.Span{ .span = data_span }; } else null; @@ -1570,11 +1572,12 @@ pub fn addExpr(store: *NodeStore, expr: CIR.Expr, region: base.Region) Allocator node.data_2 = @bitCast(e.field_name); if (e.args) |args| { // Use PackedDataSpan for efficient storage - FunctionArgs config is good for method call args + // Add 1 to distinguish empty args {start:0, len:0} (becomes 1) from null args (remains 0) std.debug.assert(FunctionArgs.canFit(args.span)); const packed_span = FunctionArgs.fromDataSpanUnchecked(args.span); - node.data_3 = packed_span.toU32(); + node.data_3 = packed_span.toU32() + 1; } else { - node.data_3 = 0; // No args + node.data_3 = 0; // No args (null) } }, .e_runtime_error => |e| { diff --git a/src/parse/Parser.zig b/src/parse/Parser.zig index 87af4c7294..d44c48c6f5 100644 --- a/src/parse/Parser.zig +++ b/src/parse/Parser.zig @@ -1894,7 +1894,7 @@ pub fn parseExprWithBp(self: *Parser, min_bp: u8) Error!AST.Expr.Idx { .Int => { self.advance(); - // Disallow NoSpaceDotInt after Int (ambiguous with floats like 35.123) + // Disallow NoSpaceDotInt after Int (ambiguous with decimal literals like 35.123) // But allow NoSpaceDotLowerIdent for method calls like 35.to_str() if (self.peek() == .NoSpaceDotInt) { return try self.pushMalformed(AST.Expr.Idx, .expr_dot_suffix_not_allowed, self.pos); diff --git a/test/snapshots/eval/method_on_float_literal.md b/test/snapshots/eval/method_on_float_literal.md index b649647b67..d5641c66a0 100644 --- a/test/snapshots/eval/method_on_float_literal.md +++ b/test/snapshots/eval/method_on_float_literal.md @@ -1,58 +1,13 @@ # META ~~~ini description=Method call directly on float literal -type=snippet +type=repl ~~~ # SOURCE ~~~roc -age : Str -age = 12.34.to_str() +» 12.34.foo() ~~~ -# EXPECTED -NIL +# OUTPUT +Crash: Num.Dec does not implement foo # PROBLEMS NIL -# TOKENS -~~~zig -LowerIdent,OpColon,UpperIdent, -LowerIdent,OpAssign,Float,NoSpaceDotLowerIdent,NoSpaceOpenRound,CloseRound, -EndOfFile, -~~~ -# PARSE -~~~clojure -(file - (type-module) - (statements - (s-type-anno (name "age") - (ty (name "Str"))) - (s-decl - (p-ident (raw "age")) - (e-field-access - (e-frac (raw "12.34")) - (e-apply - (e-ident (raw "to_str"))))))) -~~~ -# FORMATTED -~~~roc -NO CHANGE -~~~ -# CANONICALIZE -~~~clojure -(can-ir - (d-let - (p-assign (ident "age")) - (e-dot-access (field "to_str") - (receiver - (e-dec-small (numerator "1234") (denominator-power-of-ten "2") (value "12.34"))) - (args)) - (annotation - (ty-lookup (name "Str") (builtin))))) -~~~ -# TYPES -~~~clojure -(inferred-types - (defs - (patt (type "Str"))) - (expressions - (expr (type "Str")))) -~~~ diff --git a/test/snapshots/eval/method_on_int_literal.md b/test/snapshots/eval/method_on_int_literal.md index f82e1a3acf..4d25eab3d9 100644 --- a/test/snapshots/eval/method_on_int_literal.md +++ b/test/snapshots/eval/method_on_int_literal.md @@ -1,58 +1,13 @@ # META ~~~ini description=Method call directly on integer literal -type=snippet +type=repl ~~~ # SOURCE ~~~roc -age : Str -age = 35.to_str() +» 35.foo() ~~~ -# EXPECTED -NIL +# OUTPUT +Crash: Num.Dec does not implement foo # PROBLEMS NIL -# TOKENS -~~~zig -LowerIdent,OpColon,UpperIdent, -LowerIdent,OpAssign,Int,NoSpaceDotLowerIdent,NoSpaceOpenRound,CloseRound, -EndOfFile, -~~~ -# PARSE -~~~clojure -(file - (type-module) - (statements - (s-type-anno (name "age") - (ty (name "Str"))) - (s-decl - (p-ident (raw "age")) - (e-field-access - (e-int (raw "35")) - (e-apply - (e-ident (raw "to_str"))))))) -~~~ -# FORMATTED -~~~roc -NO CHANGE -~~~ -# CANONICALIZE -~~~clojure -(can-ir - (d-let - (p-assign (ident "age")) - (e-dot-access (field "to_str") - (receiver - (e-num (value "35"))) - (args)) - (annotation - (ty-lookup (name "Str") (builtin))))) -~~~ -# TYPES -~~~clojure -(inferred-types - (defs - (patt (type "Str"))) - (expressions - (expr (type "Str")))) -~~~