diff --git a/packages/tests/src/core/Example_test.res b/packages/tests/src/core/Example_test.res index d0bbf7fb..87f315c0 100644 --- a/packages/tests/src/core/Example_test.res +++ b/packages/tests/src/core/Example_test.res @@ -77,6 +77,6 @@ test("Compiled serialize code snapshot", t => { t->U.assertCompiledCode( ~schema=filmSchema, ~op=#Serialize, - `i=>{let v0=i["tags"],v4,v5=i["rating"],v6,v7=i["deprecatedAgeRestriction"],v8;if(v0!==void 0){let v1=e[0](v0);v4=v1}try{if(v5!=="G"){e[1](v5)}v6=v5}catch(e0){try{if(v5!=="PG"){e[2](v5)}v6=v5}catch(e1){try{if(v5!=="PG13"){e[3](v5)}v6=v5}catch(e2){try{if(v5!=="R"){e[4](v5)}v6=v5}catch(e3){e[5]([e0,e1,e2,e3,])}}}}if(v7!==void 0){v8=e[6](v7)}return {"Id":i["id"],"Title":i["title"],"Tags":v4,"Rating":v6,"Age":v8,}}`, + `i=>{let v0=i["tags"],v3=i["rating"],v4,v5=i["deprecatedAgeRestriction"],v6;try{if(v3!=="G"){e[0](v3)}v4=v3}catch(e0){try{if(v3!=="PG"){e[1](v3)}v4=v3}catch(e1){try{if(v3!=="PG13"){e[2](v3)}v4=v3}catch(e2){try{if(v3!=="R"){e[3](v3)}v4=v3}catch(e3){e[4]([e0,e1,e2,e3,])}}}}if(v5!==void 0){v6=e[5](v5)}return {"Id":i["id"],"Title":i["title"],"Tags":v0,"Rating":v4,"Age":v6,}}`, ) }) diff --git a/packages/tests/src/core/S_Option_getOrWith_test.res b/packages/tests/src/core/S_Option_getOrWith_test.res index 5e0fac07..34557827 100644 --- a/packages/tests/src/core/S_Option_getOrWith_test.res +++ b/packages/tests/src/core/S_Option_getOrWith_test.res @@ -83,10 +83,5 @@ test("Compiled async parse code snapshot", t => { test("Compiled serialize code snapshot", t => { let schema = S.bool->S.option->S.Option.getOrWith(() => false) - t->U.assertCompiledCode( - ~schema, - ~op=#Serialize, - // TODO: It can be simplified to noop - `i=>{let v0;if(i!==void 0){v0=e[0](i)}return v0}`, - ) + t->U.assertCompiledCodeIsNoop(~schema, ~op=#Serialize) }) diff --git a/packages/tests/src/core/S_Option_getOr_test.res b/packages/tests/src/core/S_Option_getOr_test.res index a521ca7f..2494a228 100644 --- a/packages/tests/src/core/S_Option_getOr_test.res +++ b/packages/tests/src/core/S_Option_getOr_test.res @@ -107,9 +107,5 @@ test("Compiled async parse code snapshot", t => { test("Compiled serialize code snapshot", t => { let schema = S.bool->S.option->S.Option.getOr(false) - t->U.assertCompiledCode( - ~schema, - ~op=#Serialize, - `i=>{let v0;if(i!==void 0){v0=e[0](i)}return v0}`, - ) + t->U.assertCompiledCodeIsNoop(~schema, ~op=#Serialize) }) diff --git a/packages/tests/src/core/S_name_test.res b/packages/tests/src/core/S_name_test.res index 0283dab7..00645ee4 100644 --- a/packages/tests/src/core/S_name_test.res +++ b/packages/tests/src/core/S_name_test.res @@ -85,4 +85,12 @@ test("Name of renamed schema", t => { `Failed parsing at root. Reason: Expected Ethers.BigInt, received "smth"`, (), ) + t->U.assertErrorResult( + %raw(`"smth"`)->S.serializeToUnknownWith(S.null(S.never)->S.setName("Ethers.BigInt")), + { + path: S.Path.empty, + operation: SerializeToUnknown, + code: InvalidType({expected: S.never->S.toUnknown, received: "smth"->Obj.magic}), + }, + ) }) diff --git a/packages/tests/src/core/S_null_test.res b/packages/tests/src/core/S_null_test.res index 023f3d53..a38d85aa 100644 --- a/packages/tests/src/core/S_null_test.res +++ b/packages/tests/src/core/S_null_test.res @@ -62,7 +62,7 @@ module Common = { t->Assert.is(schema->S.reverse->S.reverse, schema->S.toUnknown, ()) }) - let serializeCode = `let v0;if(i!==void 0){v0=e[0](i)}else{v0=null}return v0` + let serializeCode = `let v0;if(i!==void 0){v0=i}else{v0=null}return v0` test("Compiled serialize code snapshot", t => { let schema = factory() t->U.assertCompiledCode(~schema, ~op=#Serialize, `i=>{${serializeCode}}`) @@ -73,7 +73,7 @@ module Common = { t->U.assertCompiledCode( ~schema=schema->S.reverse, ~op=#Parse, - `i=>{if(i!==void 0&&(typeof i!=="string")){e[1](i)}${serializeCode}}`, + `i=>{if(i!==void 0&&(typeof i!=="string")){e[0](i)}${serializeCode}}`, ) }) @@ -156,6 +156,6 @@ test("Serializes Some(None) to null for null nested in option", t => { t->U.assertCompiledCode( ~schema, ~op=#Serialize, - `i=>{let v2;if(i!==void 0){let v0=e[0](i),v1;if(v0!==void 0){v1=e[1](v0)}else{v1=null}v2=v1}return v2}`, + `i=>{let v2;if(i!==void 0){let v0=e[0](i),v1;if(v0!==void 0){v1=v0}else{v1=null}v2=v1}return v2}`, ) }) diff --git a/packages/tests/src/core/S_nullable_test.res b/packages/tests/src/core/S_nullable_test.res index a3fa0dc1..533b5506 100644 --- a/packages/tests/src/core/S_nullable_test.res +++ b/packages/tests/src/core/S_nullable_test.res @@ -60,7 +60,7 @@ module NullCommon = { t->U.assertCompiledCode( ~schema, ~op=#Serialize, - `i=>{let v2;if(i!==void 0){let v0=e[0](i),v1;if(v0!==void 0){v1=e[1](v0)}else{v1=null}v2=v1}return v2}`, + `i=>{let v2;if(i!==void 0){let v0=e[0](i),v1;if(v0!==void 0){v1=v0}else{v1=null}v2=v1}return v2}`, ) }) diff --git a/packages/tests/src/core/S_option_test.res b/packages/tests/src/core/S_option_test.res index f498dcde..8876c7ec 100644 --- a/packages/tests/src/core/S_option_test.res +++ b/packages/tests/src/core/S_option_test.res @@ -62,10 +62,9 @@ module Common = { ) }) - test("Reverse to S.option", t => { + test("Reverse to self", t => { let schema = factory() - t->Assert.not(schema->S.reverse, schema->S.toUnknown, ()) // not self - t->U.assertEqualSchemas(schema->S.reverse, schema->S.toUnknown) + t->Assert.is(schema->S.reverse, schema->S.toUnknown, ()) }) test("Succesfully uses reversed schema for parsing back to initial value", t => { diff --git a/src/S_Core.bs.mjs b/src/S_Core.bs.mjs index f1724ced..e31aab69 100644 --- a/src/S_Core.bs.mjs +++ b/src/S_Core.bs.mjs @@ -654,11 +654,7 @@ function isAsyncParse(schema) { } function reverse(schema) { - var reversed = schema.r(); - reversed.r = (function () { - return schema; - }); - return reversed; + return schema.r(); } function parseAnyOrRaiseWith(any, schema) { @@ -861,7 +857,7 @@ function initialAssertOrRaise(unknown) { function initialSerializeToUnknownOrRaise(unknown) { var schema = this; - var reversed = reverse(schema); + var reversed = schema.r(); var operation = build(reversed.b, reversed, "SerializeToUnknown", serializeFinalizer); schema.serializeOrThrow = operation; return operation(unknown); @@ -875,7 +871,7 @@ function initialSerializeOrRaise(unknown) { _0: schema }, "SerializeToJson", ""); } - var reversed = reverse(schema); + var reversed = schema.r(); var operation = build(reversed.b, reversed, "SerializeToJson", serializeFinalizer); schema.serializeToJsonOrThrow = operation; return operation(unknown); @@ -922,7 +918,14 @@ function makeSchema(name, tagged, metadataMap, builder, maybeTypeFilter, reverse return { t: tagged, n: name, - r: reverse, + r: (function () { + var original = this; + var reversed = reverse.call(original); + reversed.r = (function () { + return original; + }); + return reversed; + }), b: builder, f: maybeTypeFilter, i: 0, @@ -1068,7 +1071,9 @@ function recursive(fn) { function setName(schema, name) { return makeSchema((function () { return name; - }), schema.t, schema.m, schema.b, schema.f, schema.r); + }), schema.t, schema.m, schema.b, schema.f, (function () { + return schema.r(); + })); } function internalRefine(schema, refiner) { @@ -1210,21 +1215,30 @@ function $$default(schema) { } function builder(b, input, selfSchema, path) { - var isNull = (selfSchema.t.TAG === "Null"); + var isNullInput = selfSchema.t.TAG === "Null"; + var reversed = selfSchema.r(); + var isNullOutput = reversed.t.TAG === "Null"; var childSchema = selfSchema.t._0; var bb = scope(b); - var itemOutput = childSchema.b(bb, input, childSchema, path); + var itemInput; + if (!isNullOutput && (b.g.o === "SerializeToJson" || b.g.o === "SerializeToUnknown")) { + var value = Caml_option.valFromOption; + itemInput = val(bb, "e[" + (bb.g.e.push(value) - 1) + "](" + $$var(b, input) + ")"); + } else { + itemInput = input; + } + var itemOutput = childSchema.b(bb, itemInput, childSchema, path); var itemCode = allocateScope(bb); - var isTransformed = isNull || itemOutput !== input; + var inputLiteral = isNullInput ? "null" : "void 0"; + var ouputLiteral = isNullOutput ? "null" : "void 0"; + var isTransformed = inputLiteral !== ouputLiteral || itemOutput !== input; var output = isTransformed ? ({ s: b, a: itemOutput.a }) : input; if (itemCode !== "" || isTransformed) { - b.c = b.c + ("if(" + $$var(b, input) + "!==" + ( - isNull ? "null" : "void 0" - ) + "){" + itemCode + set(b, output, itemOutput) + "}" + ( - isNull || output.a ? "else{" + set(b, output, val(b, "void 0")) + "}" : "" + b.c = b.c + ("if(" + $$var(b, input) + "!==" + inputLiteral + "){" + itemCode + set(b, output, itemOutput) + "}" + ( + inputLiteral !== ouputLiteral || output.a ? "else{" + set(b, output, val(b, ouputLiteral)) + "}" : "" )); } return output; @@ -1240,34 +1254,11 @@ function maybeTypeFilter(schema, inlinedNoneValue) { } -function reverse$1() { - var original = this; - var isNull = (original.t.TAG === "Null"); - var originalChild = original.t._0; - var child = originalChild.r(); - return makeReverseSchema(containerName, { - TAG: "Option", - _0: child - }, empty, (function (b, input, param, path) { - var output = allocateVal(b); - var inputVar = $$var(b, input); - var bb = scope(b); - var value = Caml_option.valFromOption; - var input$1 = map(bb, "e[" + (bb.g.e.push(value) - 1) + "]", input); - var itemOutput = child.b(bb, input$1, child, path); - var itemCode = allocateScope(bb); - b.c = b.c + ("if(" + inputVar + "!==void 0){" + itemCode + set(b, output, itemOutput) + "}" + ( - isNull ? "else{" + setInlined(b, output, "null") + "}" : "" - )); - return output; - }), maybeTypeFilter(child, "void 0")); -} - function factory(schema) { return makeSchema(containerName, { TAG: "Option", _0: schema - }, empty, builder, maybeTypeFilter(schema, "void 0"), reverse$1); + }, empty, builder, maybeTypeFilter(schema, "void 0"), onlyChild(factory, schema)); } function getWithDefault(schema, $$default) { @@ -1279,7 +1270,16 @@ function getWithDefault(schema, $$default) { return val(b, inputVar + "===void 0?" + tmp + ":" + inputVar); })); }), schema.f, (function () { - return schema.r(); + var reversed = schema.r(); + var child = reversed.t; + if (typeof child !== "object") { + return reversed; + } + if (child.TAG !== "Option") { + return reversed; + } + var child$1 = child._0; + return makeReverseSchema(child$1.n, child$1.t, child$1.m, child$1.b, child$1.f); })); } @@ -1301,7 +1301,9 @@ function factory$1(schema) { return makeSchema(containerName, { TAG: "Null", _0: schema - }, empty, builder, maybeTypeFilter(schema, "null"), reverse$1); + }, empty, builder, maybeTypeFilter(schema, "null"), (function () { + return factory(schema.r()); + })); } function nullable(schema) { @@ -1477,7 +1479,7 @@ function name() { }).join(", ") + "})"; } -function reverse$2() { +function reverse$1() { var original = this; return makeReverseSchema(primitiveName, "Unknown", empty, (function (b, input, param, path) { var inputVar = $$var(b, input); @@ -1621,7 +1623,7 @@ function factory$3(definer) { definition: definition }, n: name, - r: reverse$2, + r: reverse$1, b: builder$2, f: typeFilter$1, i: 0, @@ -1693,7 +1695,7 @@ function tuple(definer) { definition: definition }, empty, builder$2, (function (b, inputVar) { return typeFilter(b, inputVar) + ("||" + inputVar + ".length!==" + length); - }), reverse$2); + }), reverse$1); } function variant(schema, definer) { diff --git a/src/S_Core.res b/src/S_Core.res index a149f299..50fe3da7 100644 --- a/src/S_Core.res +++ b/src/S_Core.res @@ -354,6 +354,9 @@ let unsafeGetErrorPayload = variant => (variant->Obj.magic)["_1"] @inline let isLiteralSchema = schema => schema.tagged->unsafeGetVarianTag === "Literal" +@inline +let isNullSchema = schema => schema.tagged->unsafeGetVarianTag === "Null" + type globalConfig = { @as("r") mutable recCounter: int, @@ -1096,9 +1099,7 @@ let isAsyncParse = schema => { } let reverse = schema => { - let reversed = schema.reverse() - reversed.reverse = () => schema->toUnknown - reversed + schema.reverse() } @inline @@ -1264,7 +1265,7 @@ let initialAssertOrRaise = unknown => { let initialSerializeToUnknownOrRaise = unknown => { let schema = %raw(`this`) - let reversed = schema->reverse + let reversed = schema.reverse() let operation = reversed.builder->Builder.build( ~schema=reversed, @@ -1286,7 +1287,7 @@ let initialSerializeOrRaise = unknown => { ), ) } - let reversed = schema->reverse + let reversed = schema.reverse() let operation = reversed.builder->Builder.build( ~schema=reversed, @@ -1336,7 +1337,12 @@ let makeSchema = (~name, ~tagged, ~metadataMap, ~builder, ~maybeTypeFilter, ~rev jsParse, jsParseAsync, jsSerialize, - reverse, + reverse: () => { + let original = %raw(`this`) + let reversed = (reverse->Obj.magic)["call"](original) + reversed.reverse = () => original + reversed + }, } let makeReverseSchema = (~name, ~tagged, ~metadataMap, ~builder, ~maybeTypeFilter) => { @@ -1530,7 +1536,7 @@ let setName = (schema, name) => { ~tagged=schema.tagged, ~maybeTypeFilter=schema.maybeTypeFilter, ~metadataMap=schema.metadataMap, - ~reverse=schema.reverse, // FIXME: test + ~reverse=() => schema.reverse(), // FIXME: test better ) } @@ -1760,24 +1766,41 @@ module Option = { let default = schema => schema->Metadata.get(~id=defaultMetadataId) let builder = Builder.make((b, ~input, ~selfSchema, ~path) => { - let isNull = %raw(`selfSchema.t.TAG === "Null"`) + let isNullInput = selfSchema->isNullSchema + let reversed = selfSchema.reverse() + let isNullOutput = reversed->isNullSchema let childSchema = selfSchema->classify->unsafeGetVariantPayload let bb = b->B.scope - let itemOutput = bb->B.parse(~schema=childSchema, ~input, ~path) + // TODO: Improve the logic + let itemInput = if ( + !isNullOutput && + (b.global.operation === SerializeToJson || b.global.operation === SerializeToUnknown) + ) { + bb->B.val(`${bb->B.embed(%raw("Caml_option.valFromOption"))}(${b->B.Val.var(input)})`) + } else { + input + } + + let itemOutput = bb->B.parse(~schema=childSchema, ~input=itemInput, ~path) let itemCode = bb->B.allocateScope - let isTransformed = isNull || itemOutput !== input + let inputLiteral = isNullInput ? "null" : "void 0" + let ouputLiteral = isNullOutput ? "null" : "void 0" + + let isTransformed = inputLiteral !== ouputLiteral || itemOutput !== input let output = isTransformed ? {_scope: b, isAsync: itemOutput.isAsync} : input if itemCode !== "" || isTransformed { b.code = b.code ++ - `if(${b->B.Val.var(input)}!==${isNull ? "null" : "void 0"}){${itemCode}${b->B.Val.set( + `if(${b->B.Val.var(input)}!==${inputLiteral}){${itemCode}${b->B.Val.set( output, itemOutput, - )}}${isNull || output.isAsync ? `else{${b->B.Val.set(output, b->B.val(`void 0`))}}` : ""}` + )}}${inputLiteral !== ouputLiteral || output.isAsync + ? `else{${b->B.Val.set(output, b->B.val(ouputLiteral))}}` + : ""}` } output @@ -1795,42 +1818,7 @@ module Option = { } } - let reverse = () => { - let original = %raw(`this`) - let isNull = %raw(`original.t.TAG === "Null"`) - let originalChild = original->classify->unsafeGetVariantPayload - let child = originalChild.reverse() - - makeReverseSchema( - ~name=containerName, - ~tagged=Option(child), - ~metadataMap=Metadata.Map.empty, - ~builder=Builder.make((b, ~input, ~selfSchema as _, ~path) => { - let output = b->B.allocateVal - let inputVar = b->B.Val.var(input) - - let bb = b->B.scope - let itemOutput = bb->B.parse( - ~schema=child, - // FIXME: Apply only for option child - ~input=bb->B.Val.map(bb->B.embed(%raw("Caml_option.valFromOption")), input), - ~path, - ) - let itemCode = bb->B.allocateScope - - b.code = - b.code ++ - `if(${inputVar}!==void 0){${itemCode}${b->B.Val.set(output, itemOutput)}}${isNull - ? `else{${b->B.Val.setInlined(output, `null`)}}` - : ""}` - - output - }), - ~maybeTypeFilter=maybeTypeFilter(~schema=child, ~inlinedNoneValue="void 0"), - ) - } - - let factory = schema => { + let rec factory = schema => { let schema = schema->toUnknown makeSchema( ~name=containerName, @@ -1838,7 +1826,7 @@ module Option = { ~tagged=Option(schema), ~builder, ~maybeTypeFilter=maybeTypeFilter(~schema, ~inlinedNoneValue="void 0"), - ~reverse, + ~reverse=Reverse.onlyChild(~factory, ~schema), ) } @@ -1863,7 +1851,22 @@ module Option = { ) }), ~maybeTypeFilter=schema.maybeTypeFilter, - ~reverse=() => schema.reverse(), + ~reverse=() => { + let reversed = schema.reverse() + switch reversed->classify { + | Option(child) => + // Copy to prevent mutating of primitive's reverse function + // TODO: Can be improved to copy only for primitives + makeReverseSchema( + ~name=child.name, + ~tagged=child.tagged, + ~metadataMap=child.metadataMap, + ~builder=child.builder, + ~maybeTypeFilter=child.maybeTypeFilter, + ) + | _ => reversed + } + }, ) } @@ -1882,7 +1885,9 @@ module Null = { ~tagged=Null(schema), ~builder=Option.builder, ~maybeTypeFilter=Option.maybeTypeFilter(~schema, ~inlinedNoneValue="null"), - ~reverse=Option.reverse, + ~reverse=() => { + Option.factory(schema.reverse()) + }, ) } } @@ -3027,7 +3032,6 @@ module Union = { ~maybeTypeFilter=None, ~metadataMap=Metadata.Map.empty, ) - // FIXME: Return self if all reversed schemas are self }, ) }