diff --git a/lib/check.ml b/lib/check.ml index ade1447..576cb6c 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -2038,6 +2038,17 @@ and named_call ctx ~want loc name args = | "embed" -> (match args with | [ p ] | [ p; _ ] -> + (* The spelling is settled before the file is opened, so a program that + asks for a type embed cannot read a file as is told that, rather than + being told the file is missing and left to discover the other half + after fixing it. *) + (match args with + | [ _; { Ast.e = Ast.Var "string"; _ } ] | [ _ ] -> () + | [ _; t ] -> + fail t.Ast.loc + "embed's second argument is the type to read the file as, and \ + `string` is the only one — (embed \"p\") is the [u8]" + | _ -> ()); let data = read_embed_file (embed_path loc p) p.Ast.loc in let as_string () = mk loc Types.String (Tast.Str data) in (* A [Str] node typed [u8] rather than a [Bytes] prim over one. [Bytes] @@ -2055,10 +2066,6 @@ and named_call ctx ~want loc name args = (match args with | [ _; { Ast.e = Ast.Var "string"; _ } ] -> expect loc ~want (as_string ()) - | [ _; t ] -> - fail t.Ast.loc - "embed's second argument is the type to read the file as, and \ - `string` is the only one — (embed \"p\") is the [u8]" | _ -> (match want with | Some Types.String -> as_string () diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 18ddf93..363673b 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -360,6 +360,25 @@ let () = "(defn main [] i32 (restart-case 0 (use-value [v i32] v))\n\ \ (invoke-restart 'use-value (println \"\")) 0)" "a restart argument must be a value"; + (* An embed reads the bytes before any value exists, so the path has to be + a literal - Odin's rule and for Odin's reason (check_load_directive + refuses anything that is not Addressing_Constant). This is the refusal + that keeps the result genuinely free at run time. *) + refuses_src "an embedded path that is computed" + "(defn main [] i32 (let [p \"x\"] (len (embed p))))" + "must be a literal string"; + (* A file that is not there is a compile error naming it, not an empty + embed: an asset silently missing is the class of quiet wrongness this + whole feature exists to remove. *) + refuses_src "an embedded file that does not exist" + "(defn main [] i32 (len (embed \"no-such-asset.bin\")))" + "cannot embed"; + (* One extra argument, and `string` is the only thing it can be. Two + spellings, not one form that changes type with its context. *) + refuses_src "embed asked for a type it cannot read a file as" + "(defn main [] i32 (len (embed \"no-such-asset.bin\" i32)))" + "`string` is the only one"; + (* Allocators, spec-memory.md. The tier on its own, with no container above it, so that a failure here is not read as a Vec bug. What is asserted is the capability set differing per allocator, the context