roc-lang / roc

A fast, friendly, functional language.
https://roc-lang.org
Universal Permissive License v1.0
4.46k stars 313 forks source link

Roc does not detect type mismatch error when performing math on values defined in separate file #6682

Open imclerran opened 6 months ago

imclerran commented 6 months ago

Crash message:

This expectation crashed while running:

10│  expect foo 1u8 == 1 + externalDerivedValue
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The crash reported this message:

Hit an erroneous type when creating a layout for `4.IdentId(21)`
1 failed and 0 passed in 333 ms.

OR:

thread 'main' panicked at crates/compiler/alias_analysis/src/lib.rs:612:38:
no entry found for key

(Min repro for both below)

Expected behavior

Preferred behavior:

Alternative acceptable behavior:

Description of error case

To reproduce, the following criteria must be met: 1) The constant must be defined in another file 2) The constant must be derived from another constant The failure mode will vary depending on whether the constant is defined by a literal, or based on another another constant. 3) The constant must be operated on with another value typed as Int * 4) A value of set type must be passed into the Int * 5) The constant must exceed on its own, the maximum value of the type held by the Int *

Note that point 5 means that if you do instStar + constant, where intStar holds 1u8, and constant == 255, Roc will crash with an integer overflow error. However, if constant == 256, it will trigger the Hit an erroneous type when creating a layout for '4.IdentId(21)' error described here.

Minimum reproduction

# MinRepro.roc
interface MinRepro
    exposes []
    imports [
        External.{ 
            # externalBaseValue,
            externalDerivedValue,
        }
    ]

# internalBaseValue = 256 # use in foo to to compile but produce mathematically incorrect result (expect fails)
# internalDerivedValue = 1 * internalBaseConst # use in foo to get *correct/expected* type mismatch error 

foo : Int * -> U16
foo = \n -> Num.toU16 (n + externalDerivedValue) # replacing with externalBaseValue will produce rust panic

expect 
    res = foo 1u8 # if using internalBaseValue, res == 1  (should == 257)
    res == 1 + externalDerivedValue
# External.roc
interface External
    exposes [
        externalBaseValue,
        externalDerivedValue,
    ]
    imports []

externalBaseValue = 256
externalDerivedValue = 1 * externalBaseValue
imclerran commented 6 months ago

4 ways to define a value == 3 failure modes + 1 success

Note that behavior varies wildly depending on how and where the value is defined. I have identified 4 different distinct failure modes:

1) Failure mode 1 ❌