diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..fcaee54cc4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: roc-lang diff --git a/.github/workflows/nightly_macos_x86_64.yml b/.github/workflows/nightly_macos_x86_64.yml index 48eb4249c8..8518716fd2 100644 --- a/.github/workflows/nightly_macos_x86_64.yml +++ b/.github/workflows/nightly_macos_x86_64.yml @@ -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 }} diff --git a/.github/workflows/ubuntu_x86_64.yml b/.github/workflows/ubuntu_x86_64.yml index e18b4266c0..cc46b5499e 100644 --- a/.github/workflows/ubuntu_x86_64.yml +++ b/.github/workflows/ubuntu_x86_64.yml @@ -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 diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index fc5c6e2113..201a5c8ca7 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -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 diff --git a/crates/compiler/builtins/roc/Hash.roc b/crates/compiler/builtins/roc/Hash.roc index 59cf8f476c..ea323707b7 100644 --- a/crates/compiler/builtins/roc/Hash.roc +++ b/crates/compiler/builtins/roc/Hash.roc @@ -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 diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index e63d60553a..7c5e789f4c 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -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 = diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index a1202e92ee..81b222553c 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -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, diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 3084daeeaa..0984707718 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -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 diff --git a/crates/compiler/constrain/src/builtins.rs b/crates/compiler/constrain/src/builtins.rs index d26dc58b34..b0fd06a1b0 100644 --- a/crates/compiler/constrain/src/builtins.rs +++ b/crates/compiler/constrain/src/builtins.rs @@ -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::Apply(symbol, args, Region::zero()) + Type::Apply( + symbol, + args.into_iter().map(Loc::at_zero).collect(), + Region::zero(), + ) } #[inline(always)] diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 96435a7319..dcb32d8357 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -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" diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index 3533672f1b..97fe8472f1 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -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, + 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"), diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 475ba772a0..bdfcb24265 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -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, + 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)> { constraints: &Constraints, rank: Rank, pools: &mut Pools, + problems: &mut Vec, + 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)> { 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, + abilities_store: &mut AbilitiesStore, + obligation_cache: &mut ObligationCache, aliases: &mut Aliases, either_type_index: roc_collections::soa::EitherIndex, ) -> 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, + 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, + 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 } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 0113ee380d..60658558cf 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -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 + ); + } } diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index fba4398d9f..7fba003170 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -209,7 +209,7 @@ impl LambdaSet { #[derive(PartialEq, Eq, Clone)] pub struct AliasCommon { pub symbol: Symbol, - pub type_arguments: Vec, + pub type_arguments: Vec>, pub lambda_set_variables: Vec, } @@ -282,7 +282,7 @@ pub enum Type { }, RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, TypeExtension), /// Applying a type to some arguments (e.g. Dict.Dict String Int) - Apply(Symbol, Vec, Region), + Apply(Symbol, Vec>, 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 { .. }) => { 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 { } 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) { .. }) => { 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) { 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(_) => {} diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index d0e3d19feb..f26dab8ea9 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -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. + "### + ); }