roc/test/snapshots/numeric_let_generalize_in_block.md
Richard Feldman b7c9c16f9f
Fix polymorphic numeric let-generalization (issue #8666)
The root cause was that numeric literals with `from_numeral` constraints
were being generalized (let-polymorphism), causing each lookup to create
a fresh instantiation. This meant constraints from later usage (like
List.get expecting U64) didn't propagate back to the original definition,
leaving the value as an unconstrained flex var that defaulted to Dec.

Fix:
1. In generalize.zig: Don't generalize flex vars with `from_numeral`
   constraints at ANY rank (not just top_level)
2. In Check.zig: Don't instantiate during lookup if the var has a
   `from_numeral` constraint - unify directly instead

This aligns with the design that let-generalization should only work for
things that are syntactically lambdas (e.g. `foo = |arg| ...`).

Also reverts the interpreter workaround - the proper fix is in the type
checker, not working around type system bugs in the interpreter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 22:38:11 -05:00

1.9 KiB

META

description=Numeric without let-generalization gives type error (only lambdas get let-generalization)
type=expr

SOURCE

{
    n = 42
    a = I64.to_str(n)
    b = Dec.to_str(n)
    Str.concat(a, b)
}

EXPECTED

TYPE MISMATCH - numeric_let_generalize_in_block.md:4:20:4:21

PROBLEMS

TYPE MISMATCH The first argument being passed to this function has the wrong type: numeric_let_generalize_in_block.md:4:20:4:21:

    b = Dec.to_str(n)
               ^

This argument has the type: I64

But the function needs the first argument to be: Dec

TOKENS

OpenCurly,
LowerIdent,OpAssign,Int,
LowerIdent,OpAssign,UpperIdent,NoSpaceDotLowerIdent,NoSpaceOpenRound,LowerIdent,CloseRound,
LowerIdent,OpAssign,UpperIdent,NoSpaceDotLowerIdent,NoSpaceOpenRound,LowerIdent,CloseRound,
UpperIdent,NoSpaceDotLowerIdent,NoSpaceOpenRound,LowerIdent,Comma,LowerIdent,CloseRound,
CloseCurly,
EndOfFile,

PARSE

(e-block
	(statements
		(s-decl
			(p-ident (raw "n"))
			(e-int (raw "42")))
		(s-decl
			(p-ident (raw "a"))
			(e-apply
				(e-ident (raw "I64.to_str"))
				(e-ident (raw "n"))))
		(s-decl
			(p-ident (raw "b"))
			(e-apply
				(e-ident (raw "Dec.to_str"))
				(e-ident (raw "n"))))
		(e-apply
			(e-ident (raw "Str.concat"))
			(e-ident (raw "a"))
			(e-ident (raw "b")))))

FORMATTED

{
	n = 42
	a = I64.to_str(n)
	b = Dec.to_str(n)
	Str.concat(a, b)
}

CANONICALIZE

(e-block
	(s-let
		(p-assign (ident "n"))
		(e-num (value "42")))
	(s-let
		(p-assign (ident "a"))
		(e-call
			(e-lookup-external
				(builtin))
			(e-lookup-local
				(p-assign (ident "n")))))
	(s-let
		(p-assign (ident "b"))
		(e-call
			(e-lookup-external
				(builtin))
			(e-lookup-local
				(p-assign (ident "n")))))
	(e-call
		(e-lookup-external
			(builtin))
		(e-lookup-local
			(p-assign (ident "a")))
		(e-lookup-local
			(p-assign (ident "b")))))

TYPES

(expr (type "Error"))