fromIntegral is unchecked: it silently truncates or wraps when the target type is narrower than the source, and the type checker never complains. The codebase converts between Int, Integer, Natural, Word/Word8, and Char code points in several places, and a wrong conversion here is exactly the class of bug behind the array-index off-by-one (#49). Right now nothing distinguishes a provably-safe widening (Int -> Integer) from a lossy narrowing (Integer -> Int); both read as fromIntegral.
Proposal
Adopt the int-cast library:
intCast only type-checks when the target provably contains every value of the source (e.g. Word8 -> Int, Int -> Integer, Natural -> Integer). A lossy conversion becomes a compile error instead of a silent runtime corruption.
- For genuine narrowings (
Integer -> Int, Natural -> Int), use intCastMaybe and handle the overflow case, or keep an explicit, commented conversion where the bound is known.
This turns "did I pick a wide enough target?" from a runtime accident into a compile-time check.
Sites to migrate
Audit every integer coercion in lib/ and exe/; the current ones (12 fromIntegral, plus toEnum/fromEnum) cluster as:
lib/Language/PureScript/PSString.hs: code point / UTF-16 / byte encoding (Char <-> Int <-> Word), the densest cluster.
lib/Language/PureScript/Backend/IR/Types.hs: De Bruijn Index (a Natural) mixed with Int arithmetic (around lines 702, 756, 789).
lib/Language/PureScript/Backend/IR.hs: PatArrayLength length conversion.
lib/Language/PureScript/Backend/Lua.hs: ArrayIndex (Natural -> Lua Integer).
lib/Language/PureScript/Backend/IR/Optimizer.hs: unIndex index used to index a list.
Scope and verification
Pure refactor, no behavior change intended. Each site keeps its current semantics; the win is that the safe ones become statically guaranteed and the lossy ones become explicit. The existing golden and unit suites guard against regressions; narrowings that gain a Maybe need their overflow branch covered.
fromIntegralis unchecked: it silently truncates or wraps when the target type is narrower than the source, and the type checker never complains. The codebase converts betweenInt,Integer,Natural,Word/Word8, andCharcode points in several places, and a wrong conversion here is exactly the class of bug behind the array-index off-by-one (#49). Right now nothing distinguishes a provably-safe widening (Int -> Integer) from a lossy narrowing (Integer -> Int); both read asfromIntegral.Proposal
Adopt the
int-castlibrary:intCastonly type-checks when the target provably contains every value of the source (e.g.Word8 -> Int,Int -> Integer,Natural -> Integer). A lossy conversion becomes a compile error instead of a silent runtime corruption.Integer -> Int,Natural -> Int), useintCastMaybeand handle the overflow case, or keep an explicit, commented conversion where the bound is known.This turns "did I pick a wide enough target?" from a runtime accident into a compile-time check.
Sites to migrate
Audit every integer coercion in
lib/andexe/; the current ones (12fromIntegral, plustoEnum/fromEnum) cluster as:lib/Language/PureScript/PSString.hs: code point / UTF-16 / byte encoding (Char<->Int<->Word), the densest cluster.lib/Language/PureScript/Backend/IR/Types.hs: De BruijnIndex(aNatural) mixed withIntarithmetic (around lines 702, 756, 789).lib/Language/PureScript/Backend/IR.hs:PatArrayLengthlength conversion.lib/Language/PureScript/Backend/Lua.hs:ArrayIndex(Natural-> LuaInteger).lib/Language/PureScript/Backend/IR/Optimizer.hs:unIndex indexused to index a list.Scope and verification
Pure refactor, no behavior change intended. Each site keeps its current semantics; the win is that the safe ones become statically guaranteed and the lossy ones become explicit. The existing golden and unit suites guard against regressions; narrowings that gain a
Maybeneed their overflow branch covered.