mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-13 15:26:24 +00:00
Merge branch 'roc-lang:main' into updating-docs
This commit is contained in:
commit
a99fe32bff
15 changed files with 914 additions and 72 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: roc-lang
|
27
.github/workflows/nightly_macos_x86_64.yml
vendored
27
.github/workflows/nightly_macos_x86_64.yml
vendored
|
@ -11,7 +11,10 @@ env:
|
|||
jobs:
|
||||
test-build-upload:
|
||||
name: build, test, package and upload nightly release
|
||||
runs-on: [macos-12]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-11, macos-12 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -35,23 +38,41 @@ jobs:
|
|||
command: build
|
||||
args: --release --locked
|
||||
|
||||
- name: execute rust tests
|
||||
- name: execute rust tests if macos 12
|
||||
if: endsWith(matrix.os, '12')
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --release --locked -- --skip opaque_wrap_function --skip bool_list_literal
|
||||
|
||||
- name: execute rust tests if macos 11
|
||||
if: endsWith(matrix.os, '11')
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --release --locked -- --skip opaque_wrap_function --skip bool_list_literal --skip platform_switching_swift --skip swift_ui
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
- name: get macos version if 11
|
||||
if: endsWith(matrix.os, '11')
|
||||
run: echo "MACOSVERSION=11" >> $GITHUB_ENV
|
||||
|
||||
- name: get macos version if 12
|
||||
if: endsWith(matrix.os, '12')
|
||||
run: echo "MACOSVERSION=12" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_${MACOSVERSION}_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_TAR_FILENAME }}
|
||||
|
|
3
.github/workflows/ubuntu_x86_64.yml
vendored
3
.github/workflows/ubuntu_x86_64.yml
vendored
|
@ -45,6 +45,9 @@ jobs:
|
|||
- name: run `roc test` on Str builtins
|
||||
run: cargo run --locked --release -- test crates/compiler/builtins/roc/Str.roc && sccache --show-stats
|
||||
|
||||
- name: run `roc test` on Dict builtins
|
||||
run: cargo run --locked --release -- test crates/compiler/builtins/roc/Dict.roc && sccache --show-stats
|
||||
|
||||
#TODO pass --locked into the script here as well, this avoids rebuilding dependencies unnecessarily
|
||||
- name: wasm repl test
|
||||
run: crates/repl_test/test_wasm.sh && sccache --show-stats
|
||||
|
|
|
@ -21,7 +21,9 @@ interface Dict
|
|||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Num.{ Nat },
|
||||
Str,
|
||||
Num.{ Nat, U64, U8 },
|
||||
Hash.{ Hasher },
|
||||
]
|
||||
|
||||
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
|
||||
|
@ -206,3 +208,412 @@ insertIfVacant = \dict, key, value ->
|
|||
dict
|
||||
else
|
||||
Dict.insert dict key value
|
||||
|
||||
# We have decided not to expose the standard roc hashing algorithm.
|
||||
# This is to avoid external dependence and the need for versioning.
|
||||
# The current implementation is a form of [Wyhash final3](https://github.com/wangyi-fudan/wyhash/blob/a5995b98ebfa7bd38bfadc0919326d2e7aabb805/wyhash.h).
|
||||
# It is 64bit and little endian specific currently.
|
||||
# TODO: wyhash is slow for large keys, use something like cityhash if the keys are too long.
|
||||
# TODO: Add a builtin to distinguish big endian systems and change loading orders.
|
||||
# TODO: Switch out Wymum on systems with slow 128bit multiplication.
|
||||
LowLevelHasher := { originalSeed : U64, state : U64 } has [
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
addI8,
|
||||
addI16,
|
||||
addI32,
|
||||
addI64,
|
||||
addI128,
|
||||
complete,
|
||||
},
|
||||
]
|
||||
|
||||
# unsafe primitive that does not perform a bounds check
|
||||
# TODO hide behind an InternalList.roc module
|
||||
listGetUnsafe : List a, Nat -> a
|
||||
|
||||
createLowLevelHasher : { seed ?U64 } -> LowLevelHasher
|
||||
createLowLevelHasher = \{ seed ? 0x526F_6352_616E_643F } ->
|
||||
@LowLevelHasher { originalSeed: seed, state: seed }
|
||||
|
||||
combineState : LowLevelHasher, { a : U64, b : U64, seed : U64, length : U64 } -> LowLevelHasher
|
||||
combineState = \@LowLevelHasher { originalSeed, state }, { a, b, seed, length } ->
|
||||
tmp = wymix (Num.bitwiseXor wyp1 a) (Num.bitwiseXor seed b)
|
||||
hash = wymix (Num.bitwiseXor wyp1 length) tmp
|
||||
|
||||
@LowLevelHasher { originalSeed, state: wymix state hash }
|
||||
|
||||
complete = \@LowLevelHasher { state } -> state
|
||||
|
||||
addI8 = \hasher, i8 ->
|
||||
addU8 hasher (Num.toU8 i8)
|
||||
addI16 = \hasher, i16 ->
|
||||
addU16 hasher (Num.toU16 i16)
|
||||
addI32 = \hasher, i32 ->
|
||||
addU32 hasher (Num.toU32 i32)
|
||||
addI64 = \hasher, i64 ->
|
||||
addU64 hasher (Num.toU64 i64)
|
||||
addI128 = \hasher, i128 ->
|
||||
addU128 hasher (Num.toU128 i128)
|
||||
|
||||
# These implementations hash each value individually with the seed and then mix
|
||||
# the resulting hash with the state. There are other options that may be faster
|
||||
# like using the output of the last hash as the seed to the current hash.
|
||||
# I am simply not sure the tradeoffs here. Theoretically this method is more sound.
|
||||
# Either way, the performance will be similar and we can change this later.
|
||||
addU8 = \@LowLevelHasher { originalSeed, state }, u8 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.toU64 u8
|
||||
a =
|
||||
Num.shiftLeftBy p0 16
|
||||
|> Num.bitwiseOr (Num.shiftLeftBy p0 8)
|
||||
|> Num.bitwiseOr p0
|
||||
b = 0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 1 }
|
||||
|
||||
addU16 = \@LowLevelHasher { originalSeed, state }, u16 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.bitwiseAnd u16 0xFF |> Num.toU64
|
||||
p1 = Num.shiftRightZfBy u16 8 |> Num.toU64
|
||||
a =
|
||||
Num.shiftLeftBy p0 16
|
||||
|> Num.bitwiseOr (Num.shiftLeftBy p1 8)
|
||||
|> Num.bitwiseOr p1
|
||||
b = 0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 2 }
|
||||
|
||||
addU32 = \@LowLevelHasher { originalSeed, state }, u32 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.toU64 u32
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b: a, seed, length: 4 }
|
||||
|
||||
addU64 = \@LowLevelHasher { originalSeed, state }, u64 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF u64
|
||||
p1 = Num.shiftRightZfBy u64 32
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p1
|
||||
b = Num.shiftLeftBy p1 32 |> Num.bitwiseOr p0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 8 }
|
||||
|
||||
addU128 = \@LowLevelHasher { originalSeed, state }, u128 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
lower = u128 |> Num.toU64
|
||||
upper = Num.shiftRightZfBy u128 64 |> Num.toU64
|
||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF lower
|
||||
p1 = Num.shiftRightZfBy lower 32 |> Num.bitwiseAnd 0xFFFF_FFFF
|
||||
p2 = Num.bitwiseAnd 0xFFFF_FFFF upper
|
||||
p3 = Num.shiftRightZfBy upper 32 |> Num.bitwiseAnd 0xFFFF_FFFF
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p2
|
||||
b = Num.shiftLeftBy p3 32 |> Num.bitwiseOr p1
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 16 }
|
||||
|
||||
addBytes : LowLevelHasher, List U8 -> LowLevelHasher
|
||||
addBytes = \@LowLevelHasher { originalSeed, state }, list ->
|
||||
length = List.len list
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
abs =
|
||||
if length <= 16 then
|
||||
if length >= 4 then
|
||||
x = Num.shiftRightZfBy length 3 |> Num.shiftLeftBy 2
|
||||
a = Num.bitwiseOr (wyr4 list 0 |> Num.shiftLeftBy 32) (wyr4 list x)
|
||||
b =
|
||||
(wyr4 list (Num.subWrap length 4) |> Num.shiftLeftBy 32)
|
||||
|> Num.bitwiseOr (wyr4 list (Num.subWrap length 4 |> Num.subWrap x))
|
||||
|
||||
{ a, b, seed }
|
||||
else if length > 0 then
|
||||
{ a: wyr3 list 0 length, b: 0, seed }
|
||||
else
|
||||
{ a: 0, b: 0, seed }
|
||||
else if length <= 48 then
|
||||
hashBytesHelper16 seed list 0 length
|
||||
else
|
||||
hashBytesHelper48 seed seed seed list 0 length
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length: Num.toU64 length }
|
||||
|
||||
hashBytesHelper48 : U64, U64, U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
|
||||
hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
|
||||
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
|
||||
newSee1 = wymix (Num.bitwiseXor (wyr8 list (Num.addWrap index 16)) wyp2) (Num.bitwiseXor (wyr8 list (Num.addWrap index 24)) see1)
|
||||
newSee2 = wymix (Num.bitwiseXor (wyr8 list (Num.addWrap index 32)) wyp3) (Num.bitwiseXor (wyr8 list (Num.addWrap index 40)) see2)
|
||||
newRemaining = Num.subWrap remaining 48
|
||||
newIndex = Num.addWrap index 48
|
||||
|
||||
if newRemaining > 48 then
|
||||
hashBytesHelper48 newSeed newSee1 newSee2 list newIndex newRemaining
|
||||
else if newRemaining > 16 then
|
||||
finalSeed = Num.bitwiseXor newSee2 (Num.bitwiseXor newSee1 newSeed)
|
||||
|
||||
hashBytesHelper16 finalSeed list newIndex newRemaining
|
||||
else
|
||||
finalSeed = Num.bitwiseXor newSee2 (Num.bitwiseXor newSee1 newSeed)
|
||||
|
||||
{ a: wyr8 list (Num.subWrap newRemaining 16 |> Num.addWrap newIndex), b: wyr8 list (Num.subWrap newRemaining 8 |> Num.addWrap newIndex), seed: finalSeed }
|
||||
|
||||
hashBytesHelper16 : U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
|
||||
hashBytesHelper16 = \seed, list, index, remaining ->
|
||||
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
|
||||
newRemaining = Num.subWrap remaining 16
|
||||
newIndex = Num.addWrap index 16
|
||||
|
||||
if newRemaining <= 16 then
|
||||
{ a: wyr8 list (Num.subWrap newRemaining 16 |> Num.addWrap newIndex), b: wyr8 list (Num.subWrap newRemaining 8 |> Num.addWrap newIndex), seed: newSeed }
|
||||
else
|
||||
hashBytesHelper16 newSeed list newIndex newRemaining
|
||||
|
||||
wyp0 : U64
|
||||
wyp0 = 0xa0761d6478bd642f
|
||||
wyp1 : U64
|
||||
wyp1 = 0xe7037ed1a0b428db
|
||||
wyp2 : U64
|
||||
wyp2 = 0x8ebc6af09c88c6e3
|
||||
wyp3 : U64
|
||||
wyp3 = 0x589965cc75374cc3
|
||||
|
||||
wymix : U64, U64 -> U64
|
||||
wymix = \a, b ->
|
||||
{ lower, upper } = wymum a b
|
||||
|
||||
Num.bitwiseXor lower upper
|
||||
|
||||
wymum : U64, U64 -> { lower : U64, upper : U64 }
|
||||
wymum = \a, b ->
|
||||
r = Num.toU128 a * Num.toU128 b
|
||||
lower = Num.toU64 r
|
||||
upper = Num.shiftRightZfBy r 64 |> Num.toU64
|
||||
|
||||
# This is the more robust form.
|
||||
# { lower: Num.bitwiseXor a lower, upper: Num.bitwiseXor b upper }
|
||||
{ lower, upper }
|
||||
|
||||
# Get the next 8 bytes as a U64
|
||||
wyr8 : List U8, Nat -> U64
|
||||
wyr8 = \list, index ->
|
||||
# With seamless slices and Num.fromBytes, this should be possible to make faster and nicer.
|
||||
# It would also deal with the fact that on big endian systems we want to invert the order here.
|
||||
# Without seamless slices, we would need fromBytes to take an index.
|
||||
p1 = listGetUnsafe list index |> Num.toU64
|
||||
p2 = listGetUnsafe list (Num.addWrap index 1) |> Num.toU64
|
||||
p3 = listGetUnsafe list (Num.addWrap index 2) |> Num.toU64
|
||||
p4 = listGetUnsafe list (Num.addWrap index 3) |> Num.toU64
|
||||
p5 = listGetUnsafe list (Num.addWrap index 4) |> Num.toU64
|
||||
p6 = listGetUnsafe list (Num.addWrap index 5) |> Num.toU64
|
||||
p7 = listGetUnsafe list (Num.addWrap index 6) |> Num.toU64
|
||||
p8 = listGetUnsafe list (Num.addWrap index 7) |> Num.toU64
|
||||
a = Num.bitwiseOr p1 (Num.shiftLeftBy p2 8)
|
||||
b = Num.bitwiseOr (Num.shiftLeftBy p3 16) (Num.shiftLeftBy p4 24)
|
||||
c = Num.bitwiseOr (Num.shiftLeftBy p5 32) (Num.shiftLeftBy p6 40)
|
||||
d = Num.bitwiseOr (Num.shiftLeftBy p7 48) (Num.shiftLeftBy p8 56)
|
||||
|
||||
Num.bitwiseOr (Num.bitwiseOr a b) (Num.bitwiseOr c d)
|
||||
|
||||
# Get the next 4 bytes as a U64 with some shifting.
|
||||
wyr4 : List U8, Nat -> U64
|
||||
wyr4 = \list, index ->
|
||||
p1 = listGetUnsafe list index |> Num.toU64
|
||||
p2 = listGetUnsafe list (Num.addWrap index 1) |> Num.toU64
|
||||
p3 = listGetUnsafe list (Num.addWrap index 2) |> Num.toU64
|
||||
p4 = listGetUnsafe list (Num.addWrap index 3) |> Num.toU64
|
||||
a = Num.bitwiseOr p1 (Num.shiftLeftBy p2 8)
|
||||
b = Num.bitwiseOr (Num.shiftLeftBy p3 16) (Num.shiftLeftBy p4 24)
|
||||
|
||||
Num.bitwiseOr a b
|
||||
|
||||
# Get the next K bytes with some shifting.
|
||||
# K must be 3 or less.
|
||||
wyr3 : List U8, Nat, Nat -> U64
|
||||
wyr3 = \list, index, k ->
|
||||
# ((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1]
|
||||
p1 = listGetUnsafe list index |> Num.toU64
|
||||
p2 = listGetUnsafe list (Num.shiftRightZfBy k 1 |> Num.addWrap index) |> Num.toU64
|
||||
p3 = listGetUnsafe list (Num.subWrap k 1 |> Num.addWrap index) |> Num.toU64
|
||||
a = Num.bitwiseOr (Num.shiftLeftBy p1 16) (Num.shiftLeftBy p2 8)
|
||||
|
||||
Num.bitwiseOr a p3
|
||||
|
||||
# TODO: would be great to have table driven expects for this.
|
||||
# Would also be great to have some sort of property based hasher
|
||||
# where we can compare `addU*` functions to the `addBytes` function.
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes []
|
||||
|> complete
|
||||
|
||||
hash == 0x1C3F_F8BF_07F9_B0B3
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x42]
|
||||
|> complete
|
||||
|
||||
hash == 0x8F9F_0A1E_E06F_0D52
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU8 0x42
|
||||
|> complete
|
||||
|
||||
hash == 0x8F9F_0A1E_E06F_0D52
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0xFF, 0xFF]
|
||||
|> complete
|
||||
|
||||
hash == 0x86CC_8B71_563F_F084
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU16 0xFFFF
|
||||
|> complete
|
||||
|
||||
hash == 0x86CC_8B71_563F_F084
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x36, 0xA7]
|
||||
|> complete
|
||||
|
||||
hash == 0xD1A5_0F24_2536_84F8
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU16 0xA736
|
||||
|> complete
|
||||
|
||||
hash == 0xD1A5_0F24_2536_84F8
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x00, 0x00, 0x00, 0x00]
|
||||
|> complete
|
||||
|
||||
hash == 0x3762_ACB1_7604_B541
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU32 0x0000_0000
|
||||
|> complete
|
||||
|
||||
hash == 0x3762_ACB1_7604_B541
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0xA9, 0x2F, 0xEE, 0x21]
|
||||
|> complete
|
||||
|
||||
hash == 0x20F3_3FD7_D32E_C7A9
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU32 0x21EE_2FA9
|
||||
|> complete
|
||||
|
||||
hash == 0x20F3_3FD7_D32E_C7A9
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x5D, 0x66, 0xB1, 0x8F, 0x68, 0x44, 0xC7, 0x03, 0xE1, 0xDD, 0x23, 0x34, 0xBB, 0x9A, 0x42, 0xA7]
|
||||
|> complete
|
||||
|
||||
hash == 0xA16F_DDAA_C167_74C7
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU128 0xA742_9ABB_3423_DDE1_03C7_4468_8FB1_665D
|
||||
|> complete
|
||||
|
||||
hash == 0xA16F_DDAA_C167_74C7
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashStrBytes "abcdefghijklmnopqrstuvwxyz"
|
||||
|> complete
|
||||
|
||||
hash == 0xBEE0_A8FD_E990_D285
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashStrBytes "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|> complete
|
||||
|
||||
hash == 0xB3C5_8528_9D82_A6EF
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashStrBytes "1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||
|> complete
|
||||
|
||||
hash == 0xDB6B_7997_7A55_BA03
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes (List.repeat 0x77 100)
|
||||
|> complete
|
||||
|
||||
hash == 0x171F_EEE2_B764_8E5E
|
||||
|
||||
# Note, had to specify u8 in the lists below to avoid ability type resolution error.
|
||||
# Apparently it won't pick the default integer.
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [8u8, 82u8, 3u8, 8u8, 24u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash == 0x999F_B530_3529_F17D
|
||||
|
||||
expect
|
||||
hash1 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered ([0u8, 1u8, 2u8, 3u8, 4u8]) List.walk
|
||||
|> complete
|
||||
|
||||
hash2 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [4u8, 3u8, 2u8, 1u8, 0u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash1 == hash2
|
||||
|
||||
expect
|
||||
hash1 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [0u8, 1u8, 2u8, 3u8, 4u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash2 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [4u8, 3u8, 2u8, 1u8, 0u8, 0u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash1 != hash2
|
||||
|
|
|
@ -17,6 +17,7 @@ interface Hash
|
|||
complete,
|
||||
hashStrBytes,
|
||||
hashList,
|
||||
hashUnordered,
|
||||
] imports [
|
||||
List,
|
||||
Str,
|
||||
|
@ -75,10 +76,33 @@ Hasher has
|
|||
|
||||
## Adds a string into a [Hasher] by hashing its UTF-8 bytes.
|
||||
hashStrBytes = \hasher, s ->
|
||||
Str.walkUtf8WithIndex s hasher \accumHasher, byte, _ ->
|
||||
addU8 accumHasher byte
|
||||
addBytes hasher (Str.toUtf8 s)
|
||||
|
||||
## Adds a list of [Hash]able elements to a [Hasher] by hashing each element.
|
||||
hashList = \hasher, lst ->
|
||||
List.walk lst hasher \accumHasher, elem ->
|
||||
hash accumHasher elem
|
||||
|
||||
## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
|
||||
## The container is iterated using the walk method passed in.
|
||||
## The order of the elements does not affect the final hash.
|
||||
hashUnordered = \hasher, container, walk ->
|
||||
walk
|
||||
container
|
||||
0
|
||||
(\accum, elem ->
|
||||
x =
|
||||
# Note, we intentionally copy the hasher in every iteration.
|
||||
# Having the same base state is required for unordered hashing.
|
||||
hasher
|
||||
|> hash elem
|
||||
|> complete
|
||||
nextAccum = Num.addWrap accum x
|
||||
|
||||
if nextAccum < accum then
|
||||
# we don't want to lose a bit of entropy on overflow, so add it back in.
|
||||
Num.addWrap nextAccum 1
|
||||
else
|
||||
nextAccum
|
||||
)
|
||||
|> \accum -> addU64 hasher accum
|
||||
|
|
|
@ -553,7 +553,7 @@ fn can_annotation_help(
|
|||
references,
|
||||
);
|
||||
|
||||
args.push(arg_ann);
|
||||
args.push(Loc::at(arg.region, arg_ann));
|
||||
}
|
||||
|
||||
match scope.lookup_alias(symbol) {
|
||||
|
@ -573,8 +573,14 @@ fn can_annotation_help(
|
|||
|
||||
let mut type_var_to_arg = Vec::new();
|
||||
|
||||
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(arg_ann);
|
||||
for (alias_arg, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(Loc::at(
|
||||
arg_ann.region,
|
||||
OptAbleType {
|
||||
typ: arg_ann.value,
|
||||
opt_ability: alias_arg.value.opt_bound_ability,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let mut lambda_set_variables =
|
||||
|
|
|
@ -70,6 +70,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
|
|||
// Below, we explicitly handle some exceptions to the pattern where a lowlevel maps
|
||||
// directly to a symbol. If you are unsure if your lowlevel is an exception, assume
|
||||
// that it isn't and just see if that works.
|
||||
#[allow(unreachable_patterns)] // multiple symbols can map to one low-level
|
||||
match lowlevel {
|
||||
$(
|
||||
LowLevel::$lowlevel => Symbol::$symbol,
|
||||
|
@ -144,6 +145,8 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
ListSwap; LIST_SWAP; 3,
|
||||
ListGetCapacity; LIST_CAPACITY; 1,
|
||||
|
||||
ListGetUnsafe; DICT_LIST_GET_UNSAFE; 2,
|
||||
|
||||
NumAdd; NUM_ADD; 2,
|
||||
NumAddWrap; NUM_ADD_WRAP; 2,
|
||||
NumAddChecked; NUM_ADD_CHECKED_LOWLEVEL; 2,
|
||||
|
|
|
@ -829,7 +829,15 @@ fn canonicalize_opaque<'a>(
|
|||
type_arguments: alias
|
||||
.type_variables
|
||||
.iter()
|
||||
.map(|_| Type::Variable(var_store.fresh()))
|
||||
.map(|alias_var| {
|
||||
Loc::at(
|
||||
alias_var.region,
|
||||
OptAbleType {
|
||||
typ: Type::Variable(var_store.fresh()),
|
||||
opt_ability: alias_var.value.opt_bound_ability,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
lambda_set_variables: alias
|
||||
.lambda_set_variables
|
||||
|
|
|
@ -3,7 +3,7 @@ use roc_can::constraint::{Constraint, Constraints};
|
|||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::num::{NumericRange, SingleQuoteBound};
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::Type::{self, *};
|
||||
|
@ -198,7 +198,11 @@ pub fn num_literal(
|
|||
|
||||
#[inline(always)]
|
||||
pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type {
|
||||
Type::Apply(symbol, args, Region::zero())
|
||||
Type::Apply(
|
||||
symbol,
|
||||
args.into_iter().map(Loc::at_zero).collect(),
|
||||
Region::zero(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -1433,6 +1433,8 @@ define_builtins! {
|
|||
15 DICT_WITH_CAPACITY: "withCapacity"
|
||||
16 DICT_CAPACITY: "capacity"
|
||||
17 DICT_UPDATE: "update"
|
||||
|
||||
18 DICT_LIST_GET_UNSAFE: "listGetUnsafe"
|
||||
}
|
||||
9 SET: "Set" => {
|
||||
0 SET_SET: "Set" exposed_type=true // the Set.Set type alias
|
||||
|
@ -1532,6 +1534,7 @@ define_builtins! {
|
|||
14 HASH_COMPLETE: "complete"
|
||||
15 HASH_HASH_STR_BYTES: "hashStrBytes"
|
||||
16 HASH_HASH_LIST: "hashList"
|
||||
17 HASH_HASH_UNORDERED: "hashUnordered"
|
||||
}
|
||||
14 JSON: "Json" => {
|
||||
0 JSON_JSON: "Json"
|
||||
|
|
|
@ -50,7 +50,14 @@ pub struct PendingDerivesTable(
|
|||
);
|
||||
|
||||
impl PendingDerivesTable {
|
||||
pub fn new(subs: &mut Subs, aliases: &mut Aliases, pending_derives: PendingDerives) -> Self {
|
||||
pub fn new(
|
||||
subs: &mut Subs,
|
||||
aliases: &mut Aliases,
|
||||
pending_derives: PendingDerives,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
) -> Self {
|
||||
let mut table = VecMap::with_capacity(pending_derives.len());
|
||||
|
||||
for (opaque, (typ, derives)) in pending_derives.into_iter() {
|
||||
|
@ -66,8 +73,16 @@ impl PendingDerivesTable {
|
|||
let derive_key = RequestedDeriveKey { opaque, ability };
|
||||
|
||||
// Neither rank nor pools should matter here.
|
||||
let opaque_var =
|
||||
type_to_var(subs, Rank::toplevel(), &mut Pools::default(), aliases, &typ);
|
||||
let opaque_var = type_to_var(
|
||||
subs,
|
||||
Rank::toplevel(),
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
&mut Pools::default(),
|
||||
aliases,
|
||||
&typ,
|
||||
);
|
||||
let real_var = match subs.get_content_without_compacting(opaque_var) {
|
||||
Content::Alias(_, _, real_var, AliasKind::Opaque) => real_var,
|
||||
_ => internal_error!("Non-opaque in derives table"),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::ability::{
|
||||
resolve_ability_specialization, type_implementing_specialization, AbilityImplError,
|
||||
CheckedDerives, ObligationCache, PendingDerivesTable, Resolved,
|
||||
|
@ -274,6 +276,9 @@ impl Aliases {
|
|||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
arena: &bumpalo::Bump,
|
||||
symbol: Symbol,
|
||||
alias_variables: AliasVariables,
|
||||
|
@ -375,7 +380,18 @@ impl Aliases {
|
|||
if !can_reuse_old_definition {
|
||||
let mut typ = typ.clone();
|
||||
typ.substitute_variables(&substitutions);
|
||||
let alias_variable = type_to_variable(subs, rank, pools, arena, self, &typ, false);
|
||||
let alias_variable = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
self,
|
||||
&typ,
|
||||
false,
|
||||
);
|
||||
(alias_variable, kind)
|
||||
} else {
|
||||
if !substitutions.is_empty() {
|
||||
|
@ -389,7 +405,18 @@ impl Aliases {
|
|||
// assumption: an alias does not (transitively) syntactically contain itself
|
||||
// (if it did it would have to be a recursive tag union, which we should have fixed up
|
||||
// during canonicalization)
|
||||
let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t, false);
|
||||
let alias_variable = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
self,
|
||||
&t,
|
||||
false,
|
||||
);
|
||||
|
||||
{
|
||||
match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) {
|
||||
|
@ -562,7 +589,14 @@ fn run_in_place(
|
|||
let mut obligation_cache = ObligationCache::default();
|
||||
let mut awaiting_specializations = AwaitingSpecializations::default();
|
||||
|
||||
let pending_derives = PendingDerivesTable::new(subs, aliases, pending_derives);
|
||||
let pending_derives = PendingDerivesTable::new(
|
||||
subs,
|
||||
aliases,
|
||||
pending_derives,
|
||||
problems,
|
||||
abilities_store,
|
||||
&mut obligation_cache,
|
||||
);
|
||||
let CheckedDerives {
|
||||
legal_derives: _,
|
||||
problems: derives_problems,
|
||||
|
@ -687,6 +721,9 @@ fn solve(
|
|||
constraints,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
subs,
|
||||
let_con.def_types,
|
||||
|
@ -747,6 +784,9 @@ fn solve(
|
|||
constraints,
|
||||
next_rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
subs,
|
||||
let_con.def_types,
|
||||
|
@ -858,11 +898,29 @@ fn solve(
|
|||
Eq(roc_can::constraint::Eq(type_index, expectation_index, category_index, region)) => {
|
||||
let category = &constraints.categories[category_index.index()];
|
||||
|
||||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
let actual = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*type_index,
|
||||
);
|
||||
|
||||
let expectation = &constraints.expectations[expectation_index.index()];
|
||||
let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(&mut UEnv::new(subs), actual, expected, Mode::EQ) {
|
||||
Success {
|
||||
|
@ -927,6 +985,9 @@ fn solve(
|
|||
subs,
|
||||
rank,
|
||||
pools,
|
||||
&mut vec![], // don't report any extra errors
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*source_index,
|
||||
);
|
||||
|
@ -962,8 +1023,16 @@ fn solve(
|
|||
let actual = deep_copy_var_in(subs, rank, pools, var, arena);
|
||||
let expectation = &constraints.expectations[expectation_index.index()];
|
||||
|
||||
let expected =
|
||||
type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(&mut UEnv::new(subs), actual, expected, Mode::EQ) {
|
||||
Success {
|
||||
|
@ -1048,11 +1117,29 @@ fn solve(
|
|||
| PatternPresence(type_index, expectation_index, category_index, region) => {
|
||||
let category = &constraints.pattern_categories[category_index.index()];
|
||||
|
||||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
let actual = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*type_index,
|
||||
);
|
||||
|
||||
let expectation = &constraints.pattern_expectations[expectation_index.index()];
|
||||
let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
let mode = match constraint {
|
||||
PatternPresence(..) => Mode::PRESENT,
|
||||
|
@ -1209,8 +1296,17 @@ fn solve(
|
|||
}
|
||||
}
|
||||
IsOpenType(type_index) => {
|
||||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
let actual = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*type_index,
|
||||
);
|
||||
|
||||
open_tag_union(subs, actual);
|
||||
|
||||
|
@ -1231,12 +1327,30 @@ fn solve(
|
|||
let tys = &constraints.types[types.indices()];
|
||||
let pattern_category = &constraints.pattern_categories[pattern_category.index()];
|
||||
|
||||
let actual = type_to_var(subs, rank, pools, aliases, typ);
|
||||
let actual = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
typ,
|
||||
);
|
||||
let tag_ty = Type::TagUnion(
|
||||
vec![(tag_name.clone(), tys.to_vec())],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
let includes = type_to_var(subs, rank, pools, aliases, &tag_ty);
|
||||
let includes = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
&tag_ty,
|
||||
);
|
||||
|
||||
match unify(&mut UEnv::new(subs), actual, includes, Mode::PRESENT) {
|
||||
Success {
|
||||
|
@ -1337,10 +1451,28 @@ fn solve(
|
|||
}
|
||||
};
|
||||
|
||||
let real_var =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, real_var);
|
||||
let real_var = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
real_var,
|
||||
);
|
||||
|
||||
let branches_var = type_to_var(subs, rank, pools, aliases, expected_type);
|
||||
let branches_var = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expected_type,
|
||||
);
|
||||
|
||||
let real_content = subs.get_content_without_compacting(real_var);
|
||||
let branches_content = subs.get_content_without_compacting(branches_var);
|
||||
|
@ -1889,6 +2021,9 @@ impl LocalDefVarsVec<(Symbol, Loc<Variable>)> {
|
|||
constraints: &Constraints,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
aliases: &mut Aliases,
|
||||
subs: &mut Subs,
|
||||
def_types_slice: roc_can::constraint::DefTypes,
|
||||
|
@ -1899,7 +2034,16 @@ impl LocalDefVarsVec<(Symbol, Loc<Variable>)> {
|
|||
let mut local_def_vars = Self::with_length(types_slice.len());
|
||||
|
||||
for (&(symbol, region), typ) in (loc_symbols_slice.iter()).zip(types_slice) {
|
||||
let var = type_to_var(subs, rank, pools, aliases, typ);
|
||||
let var = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
typ,
|
||||
);
|
||||
|
||||
local_def_vars.push((symbol, Loc { value: var, region }));
|
||||
}
|
||||
|
@ -1930,6 +2074,9 @@ fn either_type_index_to_var(
|
|||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
aliases: &mut Aliases,
|
||||
either_type_index: roc_collections::soa::EitherIndex<Type, Variable>,
|
||||
) -> Variable {
|
||||
|
@ -1937,7 +2084,16 @@ fn either_type_index_to_var(
|
|||
Ok(type_index) => {
|
||||
let typ = &constraints.types[type_index.index()];
|
||||
|
||||
type_to_var(subs, rank, pools, aliases, typ)
|
||||
type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
typ,
|
||||
)
|
||||
}
|
||||
Err(var_index) => {
|
||||
// we cheat, and store the variable directly in the index
|
||||
|
@ -1949,6 +2105,9 @@ fn either_type_index_to_var(
|
|||
pub(crate) fn type_to_var(
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
pools: &mut Pools,
|
||||
aliases: &mut Aliases,
|
||||
typ: &Type,
|
||||
|
@ -1958,7 +2117,18 @@ pub(crate) fn type_to_var(
|
|||
} else {
|
||||
let mut arena = take_scratchpad();
|
||||
|
||||
let var = type_to_variable(subs, rank, pools, &arena, aliases, typ, false);
|
||||
let var = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
&arena,
|
||||
aliases,
|
||||
typ,
|
||||
false,
|
||||
);
|
||||
|
||||
arena.reset();
|
||||
put_scratchpad(arena);
|
||||
|
@ -2109,6 +2279,9 @@ fn type_to_variable<'a>(
|
|||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
arena: &'a bumpalo::Bump,
|
||||
aliases: &mut Aliases,
|
||||
typ: &Type,
|
||||
|
@ -2118,6 +2291,7 @@ fn type_to_variable<'a>(
|
|||
use bumpalo::collections::Vec;
|
||||
|
||||
let mut stack = Vec::with_capacity_in(8, arena);
|
||||
let mut bind_to_ability = Vec::new_in(arena);
|
||||
|
||||
macro_rules! helper {
|
||||
($typ:expr, $ambient_function_policy:expr) => {{
|
||||
|
@ -2165,7 +2339,7 @@ fn type_to_variable<'a>(
|
|||
Apply(symbol, arguments, _) => {
|
||||
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
||||
let var = helper!(var_index);
|
||||
let var = helper!(&var_index.value);
|
||||
subs.variables[target_index] = var;
|
||||
}
|
||||
|
||||
|
@ -2356,8 +2530,11 @@ fn type_to_variable<'a>(
|
|||
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
|
||||
|
||||
for (target_index, arg_type) in (new_variables.indices()).zip(type_arguments) {
|
||||
let copy_var = helper!(arg_type);
|
||||
let copy_var = helper!(&arg_type.value.typ);
|
||||
subs.variables[target_index] = copy_var;
|
||||
if let Some(ability) = arg_type.value.opt_ability {
|
||||
bind_to_ability.push((Loc::at(arg_type.region, copy_var), ability));
|
||||
}
|
||||
}
|
||||
|
||||
let it = (new_variables.indices().skip(type_arguments.len()))
|
||||
|
@ -2365,8 +2542,18 @@ fn type_to_variable<'a>(
|
|||
for (target_index, ls) in it {
|
||||
// We MUST do this now, otherwise when linking the ambient function during
|
||||
// instantiation of the real var, there will be nothing to link against.
|
||||
let copy_var =
|
||||
type_to_variable(subs, rank, pools, arena, aliases, &ls.0, true);
|
||||
let copy_var = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
aliases,
|
||||
&ls.0,
|
||||
true,
|
||||
);
|
||||
subs.variables[target_index] = copy_var;
|
||||
}
|
||||
|
||||
|
@ -2381,6 +2568,9 @@ fn type_to_variable<'a>(
|
|||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
*symbol,
|
||||
alias_variables,
|
||||
|
@ -2488,8 +2678,18 @@ fn type_to_variable<'a>(
|
|||
for (target_index, ls) in it {
|
||||
// We MUST do this now, otherwise when linking the ambient function during
|
||||
// instantiation of the real var, there will be nothing to link against.
|
||||
let copy_var =
|
||||
type_to_variable(subs, rank, pools, arena, aliases, &ls.0, true);
|
||||
let copy_var = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
aliases,
|
||||
&ls.0,
|
||||
true,
|
||||
);
|
||||
subs.variables[target_index] = copy_var;
|
||||
}
|
||||
|
||||
|
@ -2501,8 +2701,18 @@ fn type_to_variable<'a>(
|
|||
};
|
||||
|
||||
// cannot use helper! here because this variable may be involved in unification below
|
||||
let alias_variable =
|
||||
type_to_variable(subs, rank, pools, arena, aliases, alias_type, false);
|
||||
let alias_variable = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
aliases,
|
||||
alias_type,
|
||||
false,
|
||||
);
|
||||
// TODO(opaques): I think host-exposed aliases should always be structural
|
||||
// (when does it make sense to give a host an opaque type?)
|
||||
let content = Content::Alias(
|
||||
|
@ -2533,6 +2743,67 @@ fn type_to_variable<'a>(
|
|||
};
|
||||
}
|
||||
|
||||
for (Loc { value: var, region }, ability) in bind_to_ability {
|
||||
match *subs.get_content_unchecked(var) {
|
||||
Content::RigidVar(a) => {
|
||||
subs.set_content(var, Content::RigidAbleVar(a, ability));
|
||||
}
|
||||
Content::RigidAbleVar(_, ab) if ab == ability => {
|
||||
// pass, already bound
|
||||
}
|
||||
_ => {
|
||||
let flex_ability = subs.fresh(Descriptor {
|
||||
content: Content::FlexAbleVar(None, ability),
|
||||
rank,
|
||||
mark: Mark::NONE,
|
||||
copy: OptVariable::NONE,
|
||||
});
|
||||
|
||||
let category = Category::OpaqueArg;
|
||||
match unify(&mut UEnv::new(subs), var, flex_ability, Mode::EQ) {
|
||||
Success {
|
||||
vars: _,
|
||||
must_implement_ability,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
// No introduction needed
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(region, category, flex_ability),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
debug_assert!(lambda_sets_to_specialize
|
||||
.drain()
|
||||
.all(|(_, vals)| vals.is_empty()));
|
||||
}
|
||||
Failure(_vars, actual_type, expected_type, _bad_impls) => {
|
||||
// No introduction needed
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
region,
|
||||
category,
|
||||
actual_type,
|
||||
Expected::NoExpectation(expected_type),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
}
|
||||
BadType(_vars, problem) => {
|
||||
// No introduction needed
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
|
|
@ -7991,4 +7991,22 @@ mod solve_expr {
|
|||
"Bool",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_able_variables_in_type_alias() {
|
||||
infer_queries!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
F a : a | a has Hash
|
||||
|
||||
main : F a -> F a
|
||||
#^^^^{-1}
|
||||
"#
|
||||
),
|
||||
@"main : a -[[main(0)]]-> a | a has Hash"
|
||||
print_only_under_alias: true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ impl LambdaSet {
|
|||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct AliasCommon {
|
||||
pub symbol: Symbol,
|
||||
pub type_arguments: Vec<Type>,
|
||||
pub type_arguments: Vec<Loc<OptAbleType>>,
|
||||
pub lambda_set_variables: Vec<LambdaSet>,
|
||||
}
|
||||
|
||||
|
@ -282,7 +282,7 @@ pub enum Type {
|
|||
},
|
||||
RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, TypeExtension),
|
||||
/// Applying a type to some arguments (e.g. Dict.Dict String Int)
|
||||
Apply(Symbol, Vec<Type>, Region),
|
||||
Apply(Symbol, Vec<Loc<Type>>, Region),
|
||||
Variable(Variable),
|
||||
RangedNumber(NumericRange),
|
||||
/// A type error, which will code gen to a runtime error
|
||||
|
@ -793,7 +793,7 @@ impl Type {
|
|||
..
|
||||
}) => {
|
||||
for value in type_arguments.iter_mut() {
|
||||
stack.push(value);
|
||||
stack.push(&mut value.value.typ);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables.iter_mut() {
|
||||
|
@ -833,7 +833,7 @@ impl Type {
|
|||
stack.push(actual_type);
|
||||
}
|
||||
Apply(_, args, _) => {
|
||||
stack.extend(args);
|
||||
stack.extend(args.iter_mut().map(|t| &mut t.value));
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet {
|
||||
|
@ -915,7 +915,7 @@ impl Type {
|
|||
..
|
||||
}) => {
|
||||
for value in type_arguments.iter_mut() {
|
||||
stack.push(value);
|
||||
stack.push(&mut value.value.typ);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables.iter_mut() {
|
||||
|
@ -954,7 +954,7 @@ impl Type {
|
|||
stack.push(actual_type);
|
||||
}
|
||||
Apply(_, args, _) => {
|
||||
stack.extend(args);
|
||||
stack.extend(args.iter_mut().map(|t| &mut t.value));
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet {
|
||||
|
@ -1021,7 +1021,9 @@ impl Type {
|
|||
..
|
||||
}) => {
|
||||
for ta in type_arguments {
|
||||
ta.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
ta.value
|
||||
.typ
|
||||
.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1042,13 +1044,16 @@ impl Type {
|
|||
} => actual_type.substitute_alias(rep_symbol, rep_args, actual),
|
||||
Apply(symbol, args, region) if *symbol == rep_symbol => {
|
||||
if args.len() == rep_args.len()
|
||||
&& args.iter().zip(rep_args.iter()).all(|(t1, t2)| t1 == t2)
|
||||
&& args
|
||||
.iter()
|
||||
.zip(rep_args.iter())
|
||||
.all(|(t1, t2)| &t1.value == t2)
|
||||
{
|
||||
*self = actual.clone();
|
||||
|
||||
if let Apply(_, args, _) = self {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
arg.value.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
|
@ -1057,7 +1062,7 @@ impl Type {
|
|||
}
|
||||
Apply(_, args, _) => {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
arg.value.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1103,7 +1108,9 @@ impl Type {
|
|||
..
|
||||
}) => {
|
||||
symbol == &rep_symbol
|
||||
|| type_arguments.iter().any(|v| v.contains_symbol(rep_symbol))
|
||||
|| type_arguments
|
||||
.iter()
|
||||
.any(|v| v.value.typ.contains_symbol(rep_symbol))
|
||||
|| lambda_set_variables
|
||||
.iter()
|
||||
.any(|v| v.0.contains_symbol(rep_symbol))
|
||||
|
@ -1117,7 +1124,7 @@ impl Type {
|
|||
name == &rep_symbol || actual.contains_symbol(rep_symbol)
|
||||
}
|
||||
Apply(symbol, _, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.value.contains_symbol(rep_symbol)),
|
||||
RangedNumber(_) => false,
|
||||
UnspecializedLambdaSet {
|
||||
unspecialized: Uls(_, sym, _),
|
||||
|
@ -1174,7 +1181,9 @@ impl Type {
|
|||
..
|
||||
} => actual_type.contains_variable(rep_variable),
|
||||
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
|
||||
Apply(_, args, _) => args
|
||||
.iter()
|
||||
.any(|arg| arg.value.contains_variable(rep_variable)),
|
||||
RangedNumber(_) => false,
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) => false,
|
||||
}
|
||||
|
@ -1259,7 +1268,12 @@ impl Type {
|
|||
.iter()
|
||||
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
|
||||
type_arguments.iter_mut().for_each(|t| {
|
||||
t.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables)
|
||||
t.value.typ.instantiate_aliases(
|
||||
region,
|
||||
aliases,
|
||||
var_store,
|
||||
new_lambda_set_variables,
|
||||
)
|
||||
});
|
||||
}
|
||||
HostExposedAlias {
|
||||
|
@ -1316,8 +1330,14 @@ impl Type {
|
|||
if false {
|
||||
let mut type_var_to_arg = Vec::new();
|
||||
|
||||
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(arg_ann.clone());
|
||||
for (alias_var, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(Loc::at(
|
||||
arg_ann.region,
|
||||
OptAbleType {
|
||||
typ: arg_ann.value.clone(),
|
||||
opt_ability: alias_var.value.opt_bound_ability,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let mut lambda_set_variables =
|
||||
|
@ -1370,17 +1390,17 @@ impl Type {
|
|||
) in alias.type_variables.iter().zip(args.iter())
|
||||
{
|
||||
let mut filler = filler.clone();
|
||||
filler.instantiate_aliases(
|
||||
filler.value.instantiate_aliases(
|
||||
region,
|
||||
aliases,
|
||||
var_store,
|
||||
new_lambda_set_variables,
|
||||
);
|
||||
named_args.push(OptAbleType {
|
||||
typ: filler.clone(),
|
||||
typ: filler.value.clone(),
|
||||
opt_ability: *opt_bound_ability,
|
||||
});
|
||||
substitution.insert(*placeholder, filler);
|
||||
substitution.insert(*placeholder, filler.value);
|
||||
}
|
||||
|
||||
// make sure hidden variables are freshly instantiated
|
||||
|
@ -1435,7 +1455,12 @@ impl Type {
|
|||
} else {
|
||||
// one of the special-cased Apply types.
|
||||
for x in args {
|
||||
x.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
|
||||
x.value.instantiate_aliases(
|
||||
region,
|
||||
aliases,
|
||||
var_store,
|
||||
new_lambda_set_variables,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1552,7 +1577,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
|
|||
..
|
||||
}) => {
|
||||
output.push(*symbol);
|
||||
stack.extend(type_arguments);
|
||||
stack.extend(type_arguments.iter().map(|ta| &ta.value.typ));
|
||||
}
|
||||
Alias {
|
||||
symbol: alias_symbol,
|
||||
|
@ -1572,7 +1597,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
|
|||
}
|
||||
Apply(symbol, args, _) => {
|
||||
output.push(*symbol);
|
||||
stack.extend(args);
|
||||
stack.extend(args.iter().map(|t| &t.value));
|
||||
}
|
||||
Erroneous(Problem::CyclicAlias(alias, _, _)) => {
|
||||
output.push(*alias);
|
||||
|
@ -1679,7 +1704,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
|||
..
|
||||
}) => {
|
||||
for arg in type_arguments {
|
||||
variables_help(arg, accum);
|
||||
variables_help(&arg.value.typ, accum);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables {
|
||||
|
@ -1709,7 +1734,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
|||
RangedNumber(_) => {}
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
variables_help(&x.value, accum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1823,7 +1848,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
|||
..
|
||||
}) => {
|
||||
for arg in type_arguments {
|
||||
variables_help_detailed(arg, accum);
|
||||
variables_help_detailed(&arg.value.typ, accum);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables {
|
||||
|
@ -1857,7 +1882,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
|||
RangedNumber(_) => {}
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help_detailed(x, accum);
|
||||
variables_help_detailed(&x.value, accum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2928,7 +2953,7 @@ fn instantiate_lambda_sets_as_unspecialized(
|
|||
debug_assert!(matches!(lambda_set.0, Type::Variable(_)));
|
||||
lambda_set.0 = new_uls();
|
||||
}
|
||||
stack.extend(type_arguments.iter_mut().rev());
|
||||
stack.extend(type_arguments.iter_mut().rev().map(|ta| &mut ta.value.typ));
|
||||
}
|
||||
Type::Alias {
|
||||
symbol: _,
|
||||
|
@ -2959,7 +2984,7 @@ fn instantiate_lambda_sets_as_unspecialized(
|
|||
stack.extend(type_arguments.iter_mut().rev());
|
||||
}
|
||||
Type::Apply(_sym, args, _region) => {
|
||||
stack.extend(args.iter_mut().rev());
|
||||
stack.extend(args.iter_mut().rev().map(|t| &mut t.value));
|
||||
}
|
||||
Type::Variable(_) => {}
|
||||
Type::RangedNumber(_) => {}
|
||||
|
|
|
@ -11430,4 +11430,31 @@ All branches in an `if` must have the same type!
|
|||
Bool.false
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
expand_ability_from_type_alias_mismatch,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [f] to "./platform"
|
||||
|
||||
F a : a | a has Hash
|
||||
|
||||
f : F ({} -> {})
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
5│ f : F ({} -> {})
|
||||
^^^^^^^^
|
||||
|
||||
I can't generate an implementation of the `Hash` ability for
|
||||
|
||||
{} -> {}
|
||||
|
||||
Note: `Hash` cannot be generated for functions.
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue