diff --git a/crates/cli/tests/cli_tests.rs b/crates/cli/tests/cli_tests.rs index 434b6ef4d87..e77f92de5db 100644 --- a/crates/cli/tests/cli_tests.rs +++ b/crates/cli/tests/cli_tests.rs @@ -212,31 +212,34 @@ mod cli_tests { ); } - // TODO check this out, there's more that's going wrong than a segfault - //#[test] - /*#[cfg_attr( - any(target_os = "windows", target_os = "linux", target_os = "macos"), - ignore = "Segfault, likely broken because of alias analysis: https://github.com/roc-lang/roc/issues/6544" - )]*/ - /* + #[test] + #[cfg_attr(windows, ignore)] fn false_interpreter() { let cli_build = ExecCli::new( - CMD_BUILD, - file_from_root("crates/cli/tests/test-projects/false-interpreter", "main.roc") - ) - .arg(BUILD_HOST_FLAG) - .arg(SUPPRESS_BUILD_HOST_WARNING_FLAG); + CMD_BUILD, + file_from_root( + "crates/cli/tests/test-projects/false-interpreter", + "main.roc", + ), + ) + .arg(BUILD_HOST_FLAG) + .arg(SUPPRESS_BUILD_HOST_WARNING_FLAG); - let sqrt_false_path_buf = file_from_root("crates/cli/tests/test-projects/false-interpreter/examples", "sqrt.false"); + let sqrt_false_path_buf = file_from_root( + "crates/cli/tests/test-projects/false-interpreter/examples", + "sqrt.false", + ); - let app_args = ["--", - sqrt_false_path_buf - .as_path() - .to_str() - .unwrap()]; + let app_args = [sqrt_false_path_buf.as_path().to_str().unwrap()]; - cli_build.full_check_build_and_run("1414", TEST_LEGACY_LINKER, ALLOW_VALGRIND, None, Some(&app_args)); - }*/ + cli_build.full_check_build_and_run( + "1414", + TEST_LEGACY_LINKER, + ALLOW_VALGRIND, + None, + Some(&app_args), + ); + } #[test] #[cfg_attr(windows, ignore)] @@ -1302,6 +1305,7 @@ mod cli_tests { } #[test] + #[ignore = "flaky currently due to 7022"] fn known_type_error() { let cli_check = ExecCli::new( CMD_CHECK, diff --git a/crates/cli/tests/test-projects/false-interpreter/Context.roc b/crates/cli/tests/test-projects/false-interpreter/Context.roc index b1eec7541b9..99a4af99c8f 100644 --- a/crates/cli/tests/test-projects/false-interpreter/Context.roc +++ b/crates/cli/tests/test-projects/false-interpreter/Context.roc @@ -1,4 +1,4 @@ -module [Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope] +module [Context, Data, with!, getChar!, Option, pushStack, popStack, toStr, inWhileScope] import pf.File import Variable exposing [Variable] @@ -20,13 +20,13 @@ pushStack = \ctx, data -> # I think an open tag union should just work here. # Instead at a call sites, I need to match on the error and then return the same error. # Otherwise it hits unreachable code in ir.rs -popStack : Context -> Result [T Context Data] [EmptyStack] +popStack : Context -> Result (Context, Data) [EmptyStack] popStack = \ctx -> when List.last ctx.stack is Ok val -> poppedCtx = { ctx & stack: List.dropAt ctx.stack (List.len ctx.stack - 1) } - Ok (T poppedCtx val) + Ok (poppedCtx, val) Err ListWasEmpty -> Err EmptyStack @@ -58,30 +58,30 @@ toStr = \{ scopes, stack, state, vars } -> "\n============\nDepth: $(depth)\nState: $(stateStr)\nStack: [$(stackStr)]\nVars: [$(varsStr)]\n============\n" -with : Str, (Context -> Task {} a) -> Task {} a -with = \path, callback -> - File.withOpen path \handle -> +with! : Str, (Context => a) => a +with! = \path, callback! -> + File.withOpen! path \handle -> # I cant define scope here and put it in the list in callback. It breaks alias anaysis. # Instead I have to inline this. # root_scope = { data: Some handle, index: 0, buf: [], whileInfo: None } - callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount } + callback! { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount } # I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is. -getChar : Context -> Task [T U8 Context] [EndOfData, NoScope] -getChar = \ctx -> +getChar! : Context => Result (U8, Context) [EndOfData, NoScope] +getChar! = \ctx -> when List.last ctx.scopes is Ok scope -> - (T val newScope) = getCharScope! scope - Task.ok (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) + (val, newScope) = getCharScope!? scope + Ok (val, { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) Err ListWasEmpty -> - Task.err NoScope + Err NoScope -getCharScope : Scope -> Task [T U8 Scope] [EndOfData, NoScope] -getCharScope = \scope -> +getCharScope! : Scope => Result (U8, Scope) [EndOfData, NoScope] +getCharScope! = \scope -> when List.get scope.buf scope.index is Ok val -> - Task.ok (T val { scope & index: scope.index + 1 }) + Ok (val, { scope & index: scope.index + 1 }) Err OutOfBounds -> when scope.data is @@ -90,13 +90,13 @@ getCharScope = \scope -> when List.first bytes is Ok val -> # This starts at 1 because the first character is already being returned. - Task.ok (T val { scope & buf: bytes, index: 1 }) + Ok (val, { scope & buf: bytes, index: 1 }) Err ListWasEmpty -> - Task.err EndOfData + Err EndOfData None -> - Task.err EndOfData + Err EndOfData inWhileScope : Context -> Bool inWhileScope = \ctx -> diff --git a/crates/cli/tests/test-projects/false-interpreter/Variable.roc b/crates/cli/tests/test-projects/false-interpreter/Variable.roc index 91699632712..f71f8cd577d 100644 --- a/crates/cli/tests/test-projects/false-interpreter/Variable.roc +++ b/crates/cli/tests/test-projects/false-interpreter/Variable.roc @@ -31,4 +31,3 @@ fromUtf8 = \char -> toIndex : Variable -> U64 toIndex = \@Variable char -> Num.intCast (char - 0x61) # "a" -# List.first (Str.toUtf8 "a") diff --git a/crates/cli/tests/test-projects/false-interpreter/main.roc b/crates/cli/tests/test-projects/false-interpreter/main.roc index 9114c518867..062e0f491e8 100644 --- a/crates/cli/tests/test-projects/false-interpreter/main.roc +++ b/crates/cli/tests/test-projects/false-interpreter/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "platform/main.roc" } +app [main!] { pf: platform "platform/main.roc" } import pf.Stdout import pf.Stdin @@ -11,132 +11,114 @@ import Variable exposing [Variable] # It has some extra constraints: # 1) The input files are considered too large to just read in at once. Instead it is read via buffer or line. # 2) The output is also considered too large to generate in memory. It must be printed as we go via buffer or line. -# I think one of the biggest issues with this implementation is that it doesn't return to the platform frequently enough. -# What I mean by that is we build a chain of all Tasks period and return that to the host. -# In something like the elm architecture you return a single step with one Task. -# The huge difference here is when it comes to things like stack overflows. -# In an imperative language, a few of these pieces would be in while loops and it would basically never overflow. -# This implementation is easy to overflow, either make the input long enough or make a false while loop run long enough. -# I assume all of the Task.awaits are the cause of this, but I am not 100% sure. + InterpreterErrors : [BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, MaxInputNumber, NoLambdaOnStack, NoNumberOnStack, NoVariableOnStack, NoScope, OutOfBounds, UnexpectedEndOfData] -main : Str -> Task {} [] -main = \filename -> - interpretFile filename - |> Task.onErr \StringErr e -> Stdout.line "Ran into problem:\n$(e)\n" +main! : Str => {} +main! = \filename -> + when interpretFile! filename is + Ok {} -> + {} + + Err (StringErr e) -> + Stdout.line! "Ran into problem:\n$(e)\n" -interpretFile : Str -> Task {} [StringErr Str] -interpretFile = \filename -> - Context.with filename \ctx -> - result = interpretCtx ctx |> Task.result! +interpretFile! : Str => Result {} [StringErr Str] +interpretFile! = \filename -> + Context.with! filename \ctx -> + result = interpretCtx! ctx when result is Ok _ -> - Task.ok {} + Ok {} Err BadUtf8 -> - Task.err (StringErr "Failed to convert string from Utf8 bytes") + Err (StringErr "Failed to convert string from Utf8 bytes") Err DivByZero -> - Task.err (StringErr "Division by zero") + Err (StringErr "Division by zero") Err EmptyStack -> - Task.err (StringErr "Tried to pop a value off of the stack when it was empty") + Err (StringErr "Tried to pop a value off of the stack when it was empty") Err InvalidBooleanValue -> - Task.err (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") + Err (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") Err (InvalidChar char) -> - Task.err (StringErr "Ran into an invalid character with ascii code: $(char)") + Err (StringErr "Ran into an invalid character with ascii code: $(char)") Err MaxInputNumber -> - Task.err (StringErr "Like the original false compiler, the max input number is 320,000") + Err (StringErr "Like the original false compiler, the max input number is 320,000") Err NoLambdaOnStack -> - Task.err (StringErr "Tried to run a lambda when no lambda was on the stack") + Err (StringErr "Tried to run a lambda when no lambda was on the stack") Err NoNumberOnStack -> - Task.err (StringErr "Tried to run a number when no number was on the stack") + Err (StringErr "Tried to run a number when no number was on the stack") Err NoVariableOnStack -> - Task.err (StringErr "Tried to load a variable when no variable was on the stack") + Err (StringErr "Tried to load a variable when no variable was on the stack") Err NoScope -> - Task.err (StringErr "Tried to run code when not in any scope") + Err (StringErr "Tried to run code when not in any scope") Err OutOfBounds -> - Task.err (StringErr "Tried to load from an offset that was outside of the stack") + Err (StringErr "Tried to load from an offset that was outside of the stack") Err UnexpectedEndOfData -> - Task.err (StringErr "Hit end of data while still parsing something") + Err (StringErr "Hit end of data while still parsing something") -isDigit : U8 -> Bool -isDigit = \char -> - char - >= 0x30 # `0` - && char - <= 0x39 # `0` -isWhitespace : U8 -> Bool -isWhitespace = \char -> - char - == 0xA # new line - || char - == 0xD # carriage return - || char - == 0x20 # space - || char - == 0x9 # tab -interpretCtx : Context -> Task Context InterpreterErrors -interpretCtx = \ctx -> - Task.loop ctx interpretCtxLoop +interpretCtx! : Context => Result Context InterpreterErrors +interpretCtx! = \ctx -> + when interpretCtxLoop! ctx is + Ok (Step next) -> + interpretCtx! next -interpretCtxLoop : Context -> Task [Step Context, Done Context] InterpreterErrors -interpretCtxLoop = \ctx -> + Ok (Done next) -> + Ok next + + Err e -> + Err e + +interpretCtxLoop! : Context => Result [Step Context, Done Context] InterpreterErrors +interpretCtxLoop! = \ctx -> when ctx.state is Executing if Context.inWhileScope ctx -> # Deal with the current while loop potentially looping. last = (List.len ctx.scopes - 1) - when List.get ctx.scopes last is - Ok scope -> - when scope.whileInfo is - Some { state: InCond, body, cond } -> - # Just ran condition. Check the top of stack to see if body should run. - when popNumber ctx is - Ok (T popCtx n) -> - if n == 0 then - newScope = { scope & whileInfo: None } - - Task.ok (Step { popCtx & scopes: List.set ctx.scopes last newScope }) - else - newScope = { scope & whileInfo: Some { state: InBody, body, cond } } + scope = List.get ctx.scopes last |> Result.mapErr? \_ -> NoScope + when scope.whileInfo is + Some { state: InCond, body, cond } -> + # Just ran condition. Check the top of stack to see if body should run. + (popCtx, n) = popNumber? ctx + if n == 0 then + newScope = { scope & whileInfo: None } - Task.ok (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) - - Err e -> - Task.err e + Ok (Step { popCtx & scopes: List.set ctx.scopes last newScope }) + else + newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - Some { state: InBody, body, cond } -> - # Just rand the body. Run the condition again. - newScope = { scope & whileInfo: Some { state: InCond, body, cond } } + Ok (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) - Task.ok (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) + Some { state: InBody, body, cond } -> + # Just rand the body. Run the condition again. + newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - None -> - Task.err NoScope + Ok (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) - Err OutOfBounds -> - Task.err NoScope + None -> + Err NoScope Executing -> # Stdout.line! (Context.toStr ctx) - result = Context.getChar ctx |> Task.result! + result = Context.getChar! ctx when result is - Ok (T val newCtx) -> - execCtx = stepExecCtx! newCtx val - Task.ok (Step execCtx) + Ok (val, newCtx) -> + execCtx = stepExecCtx!? newCtx val + Ok (Step execCtx) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> # Computation complete for this scope. @@ -145,437 +127,357 @@ interpretCtxLoop = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.ok (Done dropCtx) + Ok (Done dropCtx) else - Task.ok (Step dropCtx) + Ok (Step dropCtx) InComment -> - result = Context.getChar ctx |> Task.result! - when result is - Ok (T val newCtx) -> - if val == 0x7D then - # `}` end of comment - Task.ok (Step { newCtx & state: Executing }) - else - Task.ok (Step { newCtx & state: InComment }) - - Err NoScope -> - Task.err NoScope - - Err EndOfData -> - Task.err UnexpectedEndOfData + (val, newCtx) = Context.getChar! ctx |> Result.mapErr? endUnexpected + if val == 0x7D then + # `}` end of comment + Ok (Step { newCtx & state: Executing }) + else + Ok (Step { newCtx & state: InComment }) InNumber accum -> - result = Context.getChar ctx |> Task.result! - when result is - Ok (T val newCtx) -> - if isDigit val then - # still in the number - # i32 multiplication is kinda broken because it implicitly seems to want to upcast to i64. - # so like should be (i32, i32) -> i32, but seems to be (i32, i32) -> i64 - # so this is make i64 mul by 10 then convert back to i32. - nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - - Task.ok (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) - else - # outside of number now, this needs to be executed. - pushCtx = Context.pushStack newCtx (Number accum) - - execCtx = stepExecCtx! { pushCtx & state: Executing } val - Task.ok (Step execCtx) - - Err NoScope -> - Task.err NoScope + (val, newCtx) = Context.getChar! ctx |> Result.mapErr? endUnexpected + if isDigit val then + # still in the number + # i32 multiplication is kinda broken because it implicitly seems to want to upcast to i64. + # so like should be (i32, i32) -> i32, but seems to be (i32, i32) -> i64 + # so this is make i64 mul by 10 then convert back to i32. + nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) + + Ok (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) + else + # outside of number now, this needs to be executed. + pushCtx = Context.pushStack newCtx (Number accum) - Err EndOfData -> - Task.err UnexpectedEndOfData + execCtx = stepExecCtx!? { pushCtx & state: Executing } val + Ok (Step execCtx) InString bytes -> - result = Context.getChar ctx |> Task.result! - when result is - Ok (T val newCtx) -> - if val == 0x22 then - # `"` end of string - when Str.fromUtf8 bytes is - Ok str -> - Stdout.raw! str - Task.ok (Step { newCtx & state: Executing }) - - Err _ -> - Task.err BadUtf8 - else - Task.ok (Step { newCtx & state: InString (List.append bytes val) }) - - Err NoScope -> - Task.err NoScope - - Err EndOfData -> - Task.err UnexpectedEndOfData + (val, newCtx) = Context.getChar! ctx |> Result.mapErr? endUnexpected + if val == 0x22 then + # `"` end of string + when Str.fromUtf8 bytes is + Ok str -> + Stdout.raw! str + Ok (Step { newCtx & state: Executing }) + + Err _ -> + Err BadUtf8 + else + Ok (Step { newCtx & state: InString (List.append bytes val) }) InLambda depth bytes -> - result = Context.getChar ctx |> Task.result! - when result is - Ok (T val newCtx) -> - if val == 0x5B then - # start of a nested lambda `[` - Task.ok (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) - else if val == 0x5D then - # `]` end of current lambda - if depth == 0 then - # end of all lambdas - Task.ok (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) - else - # end of nested lambda - Task.ok (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) - else - Task.ok (Step { newCtx & state: InLambda depth (List.append bytes val) }) + (val, newCtx) = Context.getChar! ctx |> Result.mapErr? endUnexpected + if val == 0x5B then + # start of a nested lambda `[` + Ok (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) + else if val == 0x5D then + # `]` end of current lambda + if depth == 0 then + # end of all lambdas + Ok (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) + else + # end of nested lambda + Ok (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) + else + Ok (Step { newCtx & state: InLambda depth (List.append bytes val) }) - Err NoScope -> - Task.err NoScope + InSpecialChar -> + val = Context.getChar! { ctx & state: Executing } |> Result.mapErr? endUnexpected + when val is + (0xB8, newCtx) -> + (popCtx, index) = popNumber? newCtx + # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. + size = List.len popCtx.stack - 1 + offset = Num.intCast size - index - Err EndOfData -> - Task.err UnexpectedEndOfData + if offset >= 0 then + stackVal = List.get? popCtx.stack (Num.intCast offset) + Ok (Step (Context.pushStack popCtx stackVal)) + else + Err OutOfBounds - InSpecialChar -> - result = Context.getChar { ctx & state: Executing } |> Task.result! - when result is - Ok (T 0xB8 newCtx) -> - result2 = - (T popCtx index) = popNumber? newCtx - # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. - size = List.len popCtx.stack - 1 - offset = Num.intCast size - index - - if offset >= 0 then - stackVal = List.get? popCtx.stack (Num.intCast offset) - Ok (Context.pushStack popCtx stackVal) - else - Err OutOfBounds - - when result2 is - Ok a -> Task.ok (Step a) - Err e -> Task.err e - - Ok (T 0x9F newCtx) -> + (0x9F, newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.ok (Step newCtx) + Ok (Step newCtx) - Ok (T x _) -> + (x, _) -> data = Num.toStr (Num.intCast x) - Task.err (InvalidChar data) - - Err NoScope -> - Task.err NoScope - - Err EndOfData -> - Task.err UnexpectedEndOfData + Err (InvalidChar data) LoadChar -> - result = Context.getChar { ctx & state: Executing } |> Task.result! - when result is - Ok (T x newCtx) -> - Task.ok (Step (Context.pushStack newCtx (Number (Num.intCast x)))) - - Err NoScope -> - Task.err NoScope - - Err EndOfData -> - Task.err UnexpectedEndOfData + (x, newCtx) = Context.getChar! { ctx & state: Executing } |> Result.mapErr? endUnexpected + Ok (Step (Context.pushStack newCtx (Number (Num.intCast x)))) # If it weren't for reading stdin or writing to stdout, this could return a result. -stepExecCtx : Context, U8 -> Task Context InterpreterErrors -stepExecCtx = \ctx, char -> +stepExecCtx! : Context, U8 => Result Context InterpreterErrors +stepExecCtx! = \ctx, char -> when char is 0x21 -> # `!` execute lambda - Task.fromResult - ( - (T popCtx bytes) = popLambda? ctx - Ok { popCtx & scopes: List.append popCtx.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } - ) + (popCtx, bytes) = popLambda? ctx + Ok { popCtx & scopes: List.append popCtx.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } 0x3F -> # `?` if - Task.fromResult - ( - (T popCtx1 bytes) = popLambda? ctx - (T popCtx2 n1) = popNumber? popCtx1 - if n1 == 0 then - Ok popCtx2 - else - Ok { popCtx2 & scopes: List.append popCtx2.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } - ) + (popCtx1, bytes) = popLambda? ctx + (popCtx2, n1) = popNumber? popCtx1 + if n1 == 0 then + Ok popCtx2 + else + Ok { popCtx2 & scopes: List.append popCtx2.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } 0x23 -> # `#` while - Task.fromResult - ( - (T popCtx1 body) = popLambda? ctx - (T popCtx2 cond) = popLambda? popCtx1 - last = (List.len popCtx2.scopes - 1) + (popCtx1, body) = popLambda? ctx + (popCtx2, cond) = popLambda? popCtx1 + last = (List.len popCtx2.scopes - 1) - when List.get popCtx2.scopes last is - Ok scope -> - # set the current scope to be in a while loop. - scopes = List.set popCtx2.scopes last { scope & whileInfo: Some { cond: cond, body: body, state: InCond } } + scope = List.get popCtx2.scopes last |> Result.mapErr? \_ -> NoScope + # set the current scope to be in a while loop. + scopes = List.set popCtx2.scopes last { scope & whileInfo: Some { cond: cond, body: body, state: InCond } } - # push a scope to execute the condition. - Ok { popCtx2 & scopes: List.append scopes { data: None, buf: cond, index: 0, whileInfo: None } } - - Err OutOfBounds -> - Err NoScope - ) + # push a scope to execute the condition. + Ok { popCtx2 & scopes: List.append scopes { data: None, buf: cond, index: 0, whileInfo: None } } 0x24 -> # `$` dup # Switching this to List.last and changing the error to ListWasEmpty leads to a compiler bug. # Complains about the types eq not matching. when List.get ctx.stack (List.len ctx.stack - 1) is - Ok dupItem -> Task.ok (Context.pushStack ctx dupItem) - Err OutOfBounds -> Task.err EmptyStack + Ok dupItem -> Ok (Context.pushStack ctx dupItem) + Err OutOfBounds -> Err EmptyStack 0x25 -> # `%` drop when Context.popStack ctx is # Dropping with an empty stack, all results here are fine - Ok (T popCtx _) -> Task.ok popCtx - Err _ -> Task.ok ctx + Ok (popCtx, _) -> Ok popCtx + Err _ -> Ok ctx 0x5C -> # `\` swap - result2 = - (T popCtx1 n1) = Context.popStack? ctx - (T popCtx2 n2) = Context.popStack? popCtx1 - Ok (Context.pushStack (Context.pushStack popCtx2 n1) n2) - - when result2 is - Ok a -> - Task.ok a - - # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack - Err EmptyStack -> - Task.err EmptyStack + (popCtx1, n1) = Context.popStack? ctx + (popCtx2, n2) = Context.popStack? popCtx1 + Ok (Context.pushStack (Context.pushStack popCtx2 n1) n2) 0x40 -> # `@` rot result2 = - (T popCtx1 n1) = Context.popStack? ctx - (T popCtx2 n2) = Context.popStack? popCtx1 - (T popCtx3 n3) = Context.popStack? popCtx2 + (popCtx1, n1) = Context.popStack? ctx + (popCtx2, n2) = Context.popStack? popCtx1 + (popCtx3, n3) = Context.popStack? popCtx2 Ok (Context.pushStack (Context.pushStack (Context.pushStack popCtx3 n2) n1) n3) when result2 is Ok a -> - Task.ok a + Ok a # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> - Task.err EmptyStack + Err EmptyStack 0xC3 -> # `ø` pick or `ß` flush # these are actually 2 bytes, 0xC3 0xB8 or 0xC3 0x9F # requires special parsing - Task.ok { ctx & state: InSpecialChar } + Ok { ctx & state: InSpecialChar } 0x4F -> # `O` also treat this as pick for easier script writing - Task.fromResult - ( - (T popCtx index) = popNumber? ctx - # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. - size = List.len popCtx.stack - 1 - offset = Num.intCast size - index - - if offset >= 0 then - stackVal = List.get? popCtx.stack (Num.intCast offset) - Ok (Context.pushStack popCtx stackVal) - else - Err OutOfBounds - ) + (popCtx, index) = popNumber? ctx + # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. + size = List.len popCtx.stack - 1 + offset = Num.intCast size - index + + if offset >= 0 then + stackVal = List.get? popCtx.stack (Num.intCast offset) + Ok (Context.pushStack popCtx stackVal) + else + Err OutOfBounds 0x42 -> # `B` also treat this as flush for easier script writing # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.ok ctx + Ok ctx 0x27 -> # `'` load next char - Task.ok { ctx & state: LoadChar } + Ok { ctx & state: LoadChar } 0x2B -> # `+` add - Task.fromResult (binaryOp ctx Num.addWrap) + binaryOp ctx Num.addWrap 0x2D -> # `-` sub - Task.fromResult (binaryOp ctx Num.subWrap) + binaryOp ctx Num.subWrap 0x2A -> # `*` mul - Task.fromResult (binaryOp ctx Num.mulWrap) + binaryOp ctx Num.mulWrap 0x2F -> # `/` div # Due to possible division by zero error, this must be handled specially. - Task.fromResult - ( - (T popCtx1 numR) = popNumber? ctx - (T popCtx2 numL) = popNumber? popCtx1 - res = Num.divTruncChecked? numL numR - Ok (Context.pushStack popCtx2 (Number res)) - ) + (popCtx1, numR) = popNumber? ctx + (popCtx2, numL) = popNumber? popCtx1 + res = Num.divTruncChecked? numL numR + Ok (Context.pushStack popCtx2 (Number res)) 0x26 -> # `&` bitwise and - Task.fromResult (binaryOp ctx Num.bitwiseAnd) + binaryOp ctx Num.bitwiseAnd 0x7C -> # `|` bitwise or - Task.fromResult (binaryOp ctx Num.bitwiseOr) + binaryOp ctx Num.bitwiseOr 0x3D -> # `=` equals - Task.fromResult - ( - binaryOp ctx \a, b -> - if a == b then - -1 - else - 0 - ) + binaryOp ctx \a, b -> + if a == b then + -1 + else + 0 0x3E -> # `>` greater than - Task.fromResult - ( - binaryOp ctx \a, b -> - if a > b then - -1 - else - 0 - ) + binaryOp ctx \a, b -> + if a > b then + -1 + else + 0 0x5F -> # `_` negate - Task.fromResult (unaryOp ctx Num.neg) + unaryOp ctx Num.neg 0x7E -> # `~` bitwise not - Task.fromResult (unaryOp ctx (\x -> Num.bitwiseXor x -1)) # xor with -1 should be bitwise not + unaryOp ctx (\x -> Num.bitwiseXor x -1) # xor with -1 should be bitwise not 0x2C -> # `,` write char - when popNumber ctx is - Ok (T popCtx num) -> - when Str.fromUtf8 [Num.intCast num] is - Ok str -> - Stdout.raw! str - Task.ok popCtx - - Err _ -> - Task.err BadUtf8 - - Err e -> - Task.err e + (popCtx, num) = popNumber? ctx + str = Str.fromUtf8 [Num.intCast num] |> Result.mapErr? \_ -> BadUtf8 + Stdout.raw! str + Ok popCtx 0x2E -> # `.` write int - when popNumber ctx is - Ok (T popCtx num) -> - Stdout.raw! (Num.toStr (Num.intCast num)) - Task.ok popCtx - - Err e -> - Task.err e + (popCtx, num) = popNumber? ctx + Stdout.raw! (Num.toStr (Num.intCast num)) + Ok popCtx 0x5E -> # `^` read char as int in = Stdin.char! {} if in == 255 then # max char sent on EOF. Change to -1 - Task.ok (Context.pushStack ctx (Number -1)) + Ok (Context.pushStack ctx (Number -1)) else - Task.ok (Context.pushStack ctx (Number (Num.intCast in))) + Ok (Context.pushStack ctx (Number (Num.intCast in))) 0x3A -> # `:` store to variable - Task.fromResult - ( - (T popCtx1 var) = popVariable? ctx - # The Result.mapErr on the next line maps from EmptyStack in Context.roc to the full InterpreterErrors union here. - (T popCtx2 n1) = Result.mapErr? (Context.popStack popCtx1) (\EmptyStack -> EmptyStack) - Ok { popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1 } - ) + (popCtx1, var) = popVariable? ctx + (popCtx2, n1) = Context.popStack? popCtx1 + Ok { popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1 } 0x3B -> # `;` load from variable - Task.fromResult - ( - (T popCtx var) = popVariable? ctx - elem = List.get? popCtx.vars (Variable.toIndex var) - Ok (Context.pushStack popCtx elem) - ) + (popCtx, var) = popVariable? ctx + elem = List.get? popCtx.vars (Variable.toIndex var) + Ok (Context.pushStack popCtx elem) 0x22 -> # `"` string start - Task.ok { ctx & state: InString [] } + Ok { ctx & state: InString [] } 0x5B -> # `"` string start - Task.ok { ctx & state: InLambda 0 [] } + Ok { ctx & state: InLambda 0 [] } 0x7B -> # `{` comment start - Task.ok { ctx & state: InComment } + Ok { ctx & state: InComment } x if isDigit x -> # number start - Task.ok { ctx & state: InNumber (Num.intCast (x - 0x30)) } + Ok { ctx & state: InNumber (Num.intCast (x - 0x30)) } x if isWhitespace x -> - Task.ok ctx + Ok ctx x -> when Variable.fromUtf8 x is # letters are variable names Ok var -> - Task.ok (Context.pushStack ctx (Var var)) + Ok (Context.pushStack ctx (Var var)) Err _ -> data = Num.toStr (Num.intCast x) - Task.err (InvalidChar data) + Err (InvalidChar data) unaryOp : Context, (I32 -> I32) -> Result Context InterpreterErrors unaryOp = \ctx, op -> - (T popCtx num) = popNumber? ctx + (popCtx, num) = popNumber? ctx Ok (Context.pushStack popCtx (Number (op num))) binaryOp : Context, (I32, I32 -> I32) -> Result Context InterpreterErrors binaryOp = \ctx, op -> - (T popCtx1 numR) = popNumber? ctx - (T popCtx2 numL) = popNumber? popCtx1 + (popCtx1, numR) = popNumber? ctx + (popCtx2, numL) = popNumber? popCtx1 Ok (Context.pushStack popCtx2 (Number (op numL numR))) -popNumber : Context -> Result [T Context I32] InterpreterErrors +popNumber : Context -> Result (Context, I32) InterpreterErrors popNumber = \ctx -> - when Context.popStack ctx is - Ok (T popCtx (Number num)) -> Ok (T popCtx num) - Ok _ -> Err (NoNumberOnStack) - Err EmptyStack -> Err EmptyStack + when Context.popStack? ctx is + (popCtx, Number num) -> Ok (popCtx, num) + _ -> Err NoNumberOnStack -popLambda : Context -> Result [T Context (List U8)] InterpreterErrors +popLambda : Context -> Result (Context, List U8) InterpreterErrors popLambda = \ctx -> - when Context.popStack ctx is - Ok (T popCtx (Lambda bytes)) -> Ok (T popCtx bytes) - Ok _ -> Err NoLambdaOnStack - Err EmptyStack -> Err EmptyStack + when Context.popStack? ctx is + (popCtx, Lambda bytes) -> Ok (popCtx, bytes) + _ -> Err NoLambdaOnStack -popVariable : Context -> Result [T Context Variable] InterpreterErrors +popVariable : Context -> Result (Context, Variable) InterpreterErrors popVariable = \ctx -> - when Context.popStack ctx is - Ok (T popCtx (Var var)) -> Ok (T popCtx var) - Ok _ -> Err NoVariableOnStack - Err EmptyStack -> Err EmptyStack + when Context.popStack? ctx is + (popCtx, Var var) -> Ok (popCtx, var) + _ -> Err NoVariableOnStack + +isDigit : U8 -> Bool +isDigit = \char -> + char + >= 0x30 # `0` + && char + <= 0x39 # `0` + +isWhitespace : U8 -> Bool +isWhitespace = \char -> + char + == 0xA # new line + || char + == 0xD # carriage return + || char + == 0x20 # space + || char + == 0x9 # tab + +endUnexpected = \err -> + when err is + NoScope -> + NoScope + + EndOfData -> + UnexpectedEndOfData + diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Cargo.toml b/crates/cli/tests/test-projects/false-interpreter/platform/Cargo.toml index 2c436a3a69e..da04287438b 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Cargo.toml +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Cargo.toml @@ -18,6 +18,6 @@ path = "src/main.rs" [dependencies] libc = "0.2" -roc_std = { path = "../../../../roc_std/" } +roc_std = { path = "../../../../../roc_std/" } [workspace] diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/File.roc b/crates/cli/tests/test-projects/false-interpreter/platform/File.roc index f17ecce8e7d..ccb0cffe128 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/File.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/File.roc @@ -1,34 +1,30 @@ -module [line, withOpen, chunk, Handle] +module [line!, withOpen!, chunk!, Handle] -import pf.PlatformTasks +import pf.Host Handle := U64 -line : Handle -> Task Str * -line = \@Handle handle -> - PlatformTasks.getFileLine handle - |> Task.mapErr \_ -> crash "unreachable File.line" - -chunk : Handle -> Task (List U8) * -chunk = \@Handle handle -> - PlatformTasks.getFileBytes handle - |> Task.mapErr \_ -> crash "unreachable File.chunk" - -open : Str -> Task Handle * -open = \path -> - PlatformTasks.openFile path - |> Task.mapErr \_ -> crash "unreachable File.open" - |> Task.map @Handle - -close : Handle -> Task.Task {} * -close = \@Handle handle -> - PlatformTasks.closeFile handle - |> Task.mapErr \_ -> crash "unreachable File.close" - -withOpen : Str, (Handle -> Task {} a) -> Task {} a -withOpen = \path, callback -> +line! : Handle => Str +line! = \@Handle handle -> + Host.getFileLine! handle + +chunk! : Handle => List U8 +chunk! = \@Handle handle -> + Host.getFileBytes! handle + +open! : Str => Handle +open! = \path -> + Host.openFile! path + |> @Handle + +close! : Handle => {} +close! = \@Handle handle -> + Host.closeFile! handle + +withOpen! : Str, (Handle => a) => a +withOpen! = \path, callback! -> handle = open! path - result = callback handle |> Task.result! + result = callback! handle close! handle - Task.fromResult result + result diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc new file mode 100644 index 00000000000..c1662d998c5 --- /dev/null +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc @@ -0,0 +1,19 @@ +hosted Host + exposes [openFile!, closeFile!, getFileLine!, getFileBytes!, putLine!, putRaw!, getLine!, getChar!] + imports [] + +openFile! : Str => U64 + +closeFile! : U64 => {} + +getFileLine! : U64 => Str + +getFileBytes! : U64 => List U8 + +putLine! : Str => {} + +putRaw! : Str => {} + +getLine! : {} => Str + +getChar! : {} => U8 diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/PlatformTasks.roc b/crates/cli/tests/test-projects/false-interpreter/platform/PlatformTasks.roc deleted file mode 100644 index ada0ab7fb96..00000000000 --- a/crates/cli/tests/test-projects/false-interpreter/platform/PlatformTasks.roc +++ /dev/null @@ -1,21 +0,0 @@ -hosted PlatformTasks - exposes [openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar] - imports [] - -openFile : Str -> Task U64 {} - -closeFile : U64 -> Task {} {} - -withFileOpen : Str, (U64 -> Task ok err) -> Task {} {} - -getFileLine : U64 -> Task Str {} - -getFileBytes : U64 -> Task (List U8) {} - -putLine : Str -> Task {} {} - -putRaw : Str -> Task {} {} - -getLine : Task Str {} - -getChar : Task U8 {} diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc index c139724399d..fc33e16ad6d 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc @@ -1,16 +1,11 @@ -module [ - line, - char, -] +module [line!, char!] -import pf.PlatformTasks +import pf.Host -line : {} -> Task Str * -line = \{} -> - PlatformTasks.getLine - |> Task.mapErr \_ -> crash "unreachable Stdin.line" +line! : {} => Str +line! = \{} -> + Host.getLine! {} -char : {} -> Task U8 * -char = \{} -> - PlatformTasks.getChar - |> Task.mapErr \_ -> crash "unreachable Stdin.char" +char! : {} => U8 +char! = \{} -> + Host.getChar! {} diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc index 723dcd606e6..9e56cc6ae11 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc @@ -1,13 +1,11 @@ -module [line, raw] +module [line!, raw!] -import pf.PlatformTasks +import pf.Host -line : Str -> Task {} * -line = \text -> - PlatformTasks.putLine text - |> Task.mapErr \_ -> crash "unreachable Stdout.line" +line! : Str => {} +line! = \text -> + Host.putLine! text -raw : Str -> Task {} * -raw = \text -> - PlatformTasks.putRaw text - |> Task.mapErr \_ -> crash "unreachable Stdout.raw" +raw! : Str => {} +raw! = \text -> + Host.putRaw! text diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/host.c b/crates/cli/tests/test-projects/false-interpreter/platform/host.c index 0378c69589c..5d660e2f031 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/host.c +++ b/crates/cli/tests/test-projects/false-interpreter/platform/host.c @@ -1,5 +1,3 @@ -#include - extern int rust_main(); int main() { diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/main.roc b/crates/cli/tests/test-projects/false-interpreter/platform/main.roc index 1fe4a8baee3..62d643ed6de 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/main.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/main.roc @@ -1,9 +1,9 @@ platform "false-interpreter" - requires {} { main : Str -> Task {} [] } + requires {} { main! : Str => {} } exposes [] packages {} imports [] - provides [mainForHost] + provides [mainForHost!] -mainForHost : Str -> Task {} [] -mainForHost = \file -> main file +mainForHost! : Str => {} +mainForHost! = \file -> main! file diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs b/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs index 693e2d0815f..616dd27a397 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs +++ b/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs @@ -1,9 +1,8 @@ #![allow(non_snake_case)] use core::ffi::c_void; -use core::mem::MaybeUninit; use libc; -use roc_std::{RocList, RocResult, RocStr}; +use roc_std::{RocList, RocStr}; use std::collections::HashMap; use std::env; use std::fs::File; @@ -21,19 +20,7 @@ fn file_handles() -> &'static Mutex>> { extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] - fn roc_main(output: *mut u8, args: &RocStr); - - #[link_name = "roc__mainForHost_1_exposed_size"] - fn roc_main_size() -> i64; - - #[link_name = "roc__mainForHost_0_caller"] - fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); - - #[link_name = "roc__mainForHost_0_size"] - fn size_Fx() -> i64; - - #[link_name = "roc__mainForHost_0_result_size"] - fn size_Fx_result() -> i64; + fn roc_main(void: *const c_void, args: *mut RocStr); } #[no_mangle] @@ -114,88 +101,57 @@ pub extern "C" fn rust_main() -> i32 { let arg = env::args() .nth(1) .expect("Please pass a .false file as a command-line argument to the false interpreter!"); - let arg = RocStr::from(arg.as_str()); - - let size = unsafe { roc_main_size() } as usize; - - unsafe { - let buffer = roc_alloc(size, 1) as *mut u8; + let mut arg = RocStr::from(arg.as_str()); - roc_main(buffer, &arg); + unsafe { roc_main(std::ptr::null(), &mut arg) }; + std::mem::forget(arg); - // arg has been passed to roc now, and it assumes ownership. - // so we must not touch its refcount now - std::mem::forget(arg); - - let result = call_the_closure(buffer); - - roc_dealloc(buffer as _, 1); - - result - }; + // This really shouldn't need to be freed, but valgrid is picky about possibly lost. + *file_handles().lock().unwrap() = HashMap::default(); // Exit code 0 } -unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { - let size = size_Fx_result() as usize; - let buffer = roc_alloc(size, 1) as *mut u8; - - call_Fx( - // This flags pointer will never get dereferenced - MaybeUninit::uninit().as_ptr(), - closure_data_ptr as *const u8, - buffer as *mut u8, - ); - - roc_dealloc(buffer as _, 1); - 0 -} - #[no_mangle] -pub extern "C" fn roc_fx_getLine() -> RocResult { +pub extern "C" fn roc_fx_getLine() -> RocStr { let stdin = std::io::stdin(); let line1 = stdin.lock().lines().next().unwrap().unwrap(); - RocResult::ok(RocStr::from(line1.as_str())) + RocStr::from(line1.as_str()) } #[no_mangle] -pub extern "C" fn roc_fx_getChar() -> RocResult { +pub extern "C" fn roc_fx_getChar() -> u8 { let mut buffer = [0]; if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) { if ioerr.kind() == std::io::ErrorKind::UnexpectedEof { - RocResult::ok(u8::MAX) + u8::MAX } else { panic!("Got an unexpected error while reading char from stdin"); } } else { - RocResult::ok(buffer[0]) + buffer[0] } } #[no_mangle] -pub extern "C" fn roc_fx_putLine(line: &RocStr) -> RocResult<(), ()> { +pub extern "C" fn roc_fx_putLine(line: &RocStr) { let string = line.as_str(); println!("{}", string); let _ = std::io::stdout().lock().flush(); - - RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_putRaw(line: &RocStr) -> RocResult<(), ()> { +pub extern "C" fn roc_fx_putRaw(line: &RocStr) { let string = line.as_str(); print!("{}", string); let _ = std::io::stdout().lock().flush(); - - RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult { +pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocStr { let mut br_map = file_handles().lock().unwrap(); let br = br_map.get_mut(&br_id).unwrap(); let mut line1 = String::default(); @@ -203,11 +159,11 @@ pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult { br.read_line(&mut line1) .expect("Failed to read line from file"); - RocResult::ok(RocStr::from(line1.as_str())) + RocStr::from(line1.as_str()) } #[no_mangle] -pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult, ()> { +pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocList { let mut br_map = file_handles().lock().unwrap(); let br = br_map.get_mut(&br_id).unwrap(); let mut buffer = [0; 0x10 /* This is intentionally small to ensure correct implementation */]; @@ -216,18 +172,16 @@ pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult, ()> .read(&mut buffer[..]) .expect("Failed to read bytes from file"); - RocResult::ok(RocList::from_slice(&buffer[..count])) + RocList::from_slice(&buffer[..count]) } #[no_mangle] -pub extern "C" fn roc_fx_closeFile(br_id: u64) -> RocResult<(), ()> { +pub extern "C" fn roc_fx_closeFile(br_id: u64) { file_handles().lock().unwrap().remove(&br_id); - - RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { +pub extern "C" fn roc_fx_openFile(name: &RocStr) -> u64 { let string = name.as_str(); match File::open(string) { Ok(f) => { @@ -236,7 +190,7 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { file_handles().lock().unwrap().insert(br_id, br); - RocResult::ok(br_id) + br_id } Err(_) => { panic!( @@ -246,17 +200,3 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { } } } - -#[no_mangle] -pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) -> RocResult<(), ()> { - // TODO: figure out accepting a closure in an fx and passing data to it. - // let f = File::open(name.as_str()).expect("Unable to open file"); - // let mut br = BufReader::new(f); - - // unsafe { - // let closure_data_ptr = buffer.offset(8); - // call_the_closure(closure_data_ptr); - // } - - RocResult::ok(()) -} diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 2728fcbc07d..7490318f17f 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -873,7 +873,7 @@ fn specialize_union<'a, 'i>( // We know the tag, we can specialize the decrement for the tag. UnionFieldLayouts::Found { field_layouts, tag } => { - match environment.union_children.get(symbol) { + match environment.union_children.get(&(*symbol, tag)) { None => keep_original_decrement!(), Some(children) => { // TODO perhaps this allocation can be avoided. @@ -884,13 +884,11 @@ fn specialize_union<'a, 'i>( let mut index_symbols = MutMap::default(); for (index, _layout) in field_layouts.iter().enumerate() { - for (child, t, _i) in children_clone + for (child, _i) in children_clone .iter() .rev() - .filter(|(_child, _t, i)| *i == index as u64) + .filter(|(_child, i)| *i == index as u64) { - debug_assert_eq!(tag, *t); - let removed = incremented_children.pop(child); index_symbols.entry(index).or_insert((*child, removed)); @@ -1392,7 +1390,7 @@ struct DropSpecializationEnvironment<'a> { struct_children: MutMap>, // Keeps track of which parent symbol is indexed by which child symbol for unions - union_children: MutMap>, + union_children: MutMap<(Parent, Tag), Vec<'a, (Child, Index)>>, // Keeps track of which parent symbol is indexed by which child symbol for boxes box_children: MutMap>, @@ -1469,9 +1467,9 @@ impl<'a> DropSpecializationEnvironment<'a> { fn add_union_child(&mut self, parent: Parent, child: Child, tag: u16, index: Index) { self.union_children - .entry(parent) + .entry((parent, tag)) .or_insert_with(|| Vec::new_in(self.arena)) - .push((child, tag, index)); + .push((child, index)); } fn add_list_child(&mut self, parent: Parent, child: Child, index: u64) { @@ -1494,9 +1492,12 @@ impl<'a> DropSpecializationEnvironment<'a> { res.extend(children.iter().rev().map(|(child, _)| child)); } - if let Some(children) = self.union_children.get(parent) { - res.extend(children.iter().rev().map(|(child, _, _)| child)); - } + let children = self + .union_children + .iter() + .filter(|(k, _v)| k.0 == *parent) + .flat_map(|(_k, v)| v.iter().rev()); + res.extend(children.map(|(child, _)| child)); if let Some(children) = self.box_children.get(parent) { res.extend(children.iter().rev());