diff --git a/compiler/override-registry_test.go b/compiler/override-registry_test.go index 83d9c66b3..d06a072c5 100644 --- a/compiler/override-registry_test.go +++ b/compiler/override-registry_test.go @@ -1009,6 +1009,33 @@ func TestOverrideParityVerifierReportsBlockedGoUse(t *testing.T) { requireDiagnosticCode(t, result.Diagnostics, "goscript/overrides:parity-blocked-use") } +// TestOverrideParityVerifierBlocksJSONV2 checks that a program that uses +// encoding/json/v2, which compiled from Go source fails when it initializes, +// fails to compile instead. +func TestOverrideParityVerifierBlocksJSONV2(t *testing.T) { + moduleDir := writePackageGraphFixture(t, map[string]string{ + "go.mod": "module example.test/jsonv2\n\ngo 1.27\n", + "main.go": strings.Join([]string{ + "package main", + "import \"encoding/json/v2\"", + "func main() { json.Marshal(1) }", + "", + }, "\n"), + }) + comp, err := NewCompiler(&Config{ + Dir: moduleDir, + OutputPath: filepath.Join(t.TempDir(), "out"), + }, nil, nil) + if err != nil { + t.Fatal(err.Error()) + } + result, err := comp.CompilePackages(context.Background(), ".") + if err == nil { + t.Fatalf("expected compile to fail") + } + requireDiagnosticCode(t, result.Diagnostics, "goscript/overrides:parity-blocked-use") +} + func TestOverrideParityVerifierAllowsDeferredMissingExports(t *testing.T) { result, err := compileParityFixture(t, map[string]overrideParityEntry{ "Present": {Status: overrideParityStatusReal}, diff --git a/gs-embed.go b/gs-embed.go index 390613a1c..d53afef42 100644 --- a/gs-embed.go +++ b/gs-embed.go @@ -83,6 +83,9 @@ import "embed" //go:embed gs/encoding/json/jsontext/stubs.gs.ts //go:embed gs/encoding/json/meta.json //go:embed gs/encoding/json/parity.json +//go:embed gs/encoding/json/v2/index.ts +//go:embed gs/encoding/json/v2/meta.json +//go:embed gs/encoding/json/v2/parity.json //go:embed gs/errors/errors.ts //go:embed gs/errors/index.ts //go:embed gs/fmt/fmt.ts diff --git a/gs/encoding/json/meta.json b/gs/encoding/json/meta.json index a9f89412f..b3bde38fa 100644 --- a/gs/encoding/json/meta.json +++ b/gs/encoding/json/meta.json @@ -1,6 +1,7 @@ { "dependencies": [ "encoding/json/jsontext", + "encoding/json/v2", "io" ], "asyncMethods": { diff --git a/gs/encoding/json/v2/index.ts b/gs/encoding/json/v2/index.ts new file mode 100644 index 000000000..fb1aa4b90 --- /dev/null +++ b/gs/encoding/json/v2/index.ts @@ -0,0 +1,17 @@ +// encoding/json/v2 is not supported: its implementation uses the internals of +// encoding/json/jsontext, which the jsontext override does not implement, so +// compiling it from Go source fails when the package initializes. parity.json +// blocks its exports, so a program that uses one fails to compile instead. +// +// Marshaler and Unmarshaler are the exceptions: encoding/json.Marshaler and +// encoding/json.Unmarshaler are aliases of them since Go 1.27, so code that +// uses those types refers to these, which have the same methods. +import type * as $ from '@goscript/builtin/index.js' + +export interface Marshaler { + MarshalJSON(): [$.Slice, $.GoError] +} + +export interface Unmarshaler { + UnmarshalJSON(data: $.Slice): $.GoError +} diff --git a/gs/encoding/json/v2/meta.json b/gs/encoding/json/v2/meta.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/gs/encoding/json/v2/meta.json @@ -0,0 +1 @@ +{} diff --git a/gs/encoding/json/v2/parity.json b/gs/encoding/json/v2/parity.json new file mode 100644 index 000000000..46216c136 --- /dev/null +++ b/gs/encoding/json/v2/parity.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "strict": true, + "symbols": { + "DefaultOptionsV2": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Deterministic": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "ErrUnknownName": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "FormatNilMapAsNull": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "FormatNilSliceAsNull": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "GetOption": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "JoinMarshalers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "JoinOptions": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "JoinUnmarshalers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Marshal": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "MarshalEncode": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "MarshalFunc": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "MarshalToFunc": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "MarshalWrite": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Marshaler": { "status": "real" }, + "MarshalerTo": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Marshalers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "MatchCaseInsensitiveNames": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "OmitZeroStructFields": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Options": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "RejectUnknownMembers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "SemanticError": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "StringifyNumbers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Unmarshal": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "UnmarshalDecode": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "UnmarshalFromFunc": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "UnmarshalFunc": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "UnmarshalRead": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Unmarshaler": { "status": "real" }, + "UnmarshalerFrom": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "Unmarshalers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "WithMarshalers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" }, + "WithUnmarshalers": { "status": "blocked", "reason": "encoding/json/v2 is not supported; it needs the internals of encoding/json/jsontext, which its override does not implement" } + } +} diff --git a/tests/tests/package_import_encoding_json_unmarshal_pointer_hook/package_import_encoding_json_unmarshal_pointer_hook.gs.ts b/tests/tests/package_import_encoding_json_unmarshal_pointer_hook/package_import_encoding_json_unmarshal_pointer_hook.gs.ts index 1a1639a41..d0fc921dd 100644 --- a/tests/tests/package_import_encoding_json_unmarshal_pointer_hook/package_import_encoding_json_unmarshal_pointer_hook.gs.ts +++ b/tests/tests/package_import_encoding_json_unmarshal_pointer_hook/package_import_encoding_json_unmarshal_pointer_hook.gs.ts @@ -4,6 +4,7 @@ import * as $ from "@goscript/builtin/index.js" import * as json from "@goscript/encoding/json/index.js" import * as fmt from "@goscript/fmt/index.js" +import type * as v2 from "@goscript/encoding/json/v2/index.js" export type Maps = $.Slice diff --git a/tests/tests/package_import_json_marshaler_alias/expected.log b/tests/tests/package_import_json_marshaler_alias/expected.log new file mode 100644 index 000000000..2a50d1f43 --- /dev/null +++ b/tests/tests/package_import_json_marshaler_alias/expected.log @@ -0,0 +1 @@ +x < y diff --git a/tests/tests/package_import_json_marshaler_alias/index.ts b/tests/tests/package_import_json_marshaler_alias/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests/package_import_json_marshaler_alias/package_import_json_marshaler_alias.go b/tests/tests/package_import_json_marshaler_alias/package_import_json_marshaler_alias.go new file mode 100644 index 000000000..106bfeed9 --- /dev/null +++ b/tests/tests/package_import_json_marshaler_alias/package_import_json_marshaler_alias.go @@ -0,0 +1,15 @@ +package main + +import ( + "html/template" + "os" +) + +func main() { + // html/template, compiled from source, uses json.Marshaler, which is an + // alias of a type of encoding/json/v2 since Go 1.27. + t := template.Must(template.New("t").Parse(`{{.}}` + "\n")) + if err := t.Execute(os.Stdout, "x < y"); err != nil { + println("error:", err.Error()) + } +} diff --git a/tests/tests/package_import_json_marshaler_alias/package_import_json_marshaler_alias.gs.ts b/tests/tests/package_import_json_marshaler_alias/package_import_json_marshaler_alias.gs.ts new file mode 100644 index 000000000..1fedc7715 --- /dev/null +++ b/tests/tests/package_import_json_marshaler_alias/package_import_json_marshaler_alias.gs.ts @@ -0,0 +1,31 @@ +// Generated file based on package_import_json_marshaler_alias.go +// Updated when compliance tests are re-run, DO NOT EDIT! + +import * as $ from "@goscript/builtin/index.js" +import * as template from "@goscript/html/template/index.js" +import * as os from "@goscript/os/index.js" +import type * as io from "@goscript/io/index.js" +import "@goscript/os/index.js" + +export async function main(): globalThis.Promise { + // html/template, compiled from source, uses json.Marshaler, which is an + // alias of a type of encoding/json/v2 since Go 1.27. + let t = template.Must(...((await template.Template.prototype.Parse.call(template.New("t"), "{{.}}" + "\n")) as [template.Template | $.VarRef | null, $.GoError])) + { + let err = await (t !== null && (t as any).__isVarRef !== true && (t as any).__goValue === undefined ? ((t as any) as template.Template).Execute($.interfaceValue(os.Stdout, "*os.File", __goscriptStatic0()), "x < y") : template.Template.prototype.Execute.call(t, $.interfaceValue(os.Stdout, "*os.File", __goscriptStatic0()), "x < y")) + if (err != null) { + await $.println("error:", (__goscriptAwait = (err !== null && (err as any).__isVarRef !== true && ((err as any).__goValue === undefined || (err as any).__goType?.charCodeAt(0) !== 42) ? (err as Exclude<$.GoError, null>) : $.pointerValue>(err)).Error(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait)) + } + } +} + +var __goscriptStatic0$value: any +function __goscriptStatic0(): any { + return __goscriptStatic0$value ??= ($.overrideTypeMethods(/* @__PURE__ */ $.pointerType("os.File"), "os.File", () => [$.methodSignature("Chdir", [], ["error"], true), $.methodSignature("Chmod", [["mode", /* @__PURE__ */ $.basicType("uint32", "fs.FileMode")]], ["error"], true), $.methodSignature("Chown", [["uid", /* @__PURE__ */ $.basicType("int")], ["gid", /* @__PURE__ */ $.basicType("int")]], ["error"], true), $.methodSignature("Close", [], ["error"], true), $.methodSignature("Fd", [], [/* @__PURE__ */ $.basicType("uintptr")], true), $.methodSignature("Name", [], [/* @__PURE__ */ $.basicType("string")], true), $.methodSignature("Read", [["b", /* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("uint8"))]], [["n", /* @__PURE__ */ $.basicType("int")], ["err", "error"]], true), $.methodSignature("ReadAt", [["b", /* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("uint8"))], ["off", /* @__PURE__ */ $.basicType("int64")]], [["n", /* @__PURE__ */ $.basicType("int")], ["err", "error"]], true), $.methodSignature("ReadDir", [["n", /* @__PURE__ */ $.basicType("int")]], [/* @__PURE__ */ $.sliceType("fs.DirEntry"), "error"], true), $.methodSignature("ReadFrom", [["r", "io.Reader"]], [["n", /* @__PURE__ */ $.basicType("int64")], ["err", "error"]], true), $.methodSignature("Readdir", [["n", /* @__PURE__ */ $.basicType("int")]], [/* @__PURE__ */ $.sliceType("fs.FileInfo"), "error"], true), $.methodSignature("Readdirnames", [["n", /* @__PURE__ */ $.basicType("int")]], [["names", /* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("string"))], ["err", "error"]], true), $.methodSignature("Seek", [["offset", /* @__PURE__ */ $.basicType("int64")], ["whence", /* @__PURE__ */ $.basicType("int")]], [["ret", /* @__PURE__ */ $.basicType("int64")], ["err", "error"]], true), $.methodSignature("SetDeadline", [["t", "time.Time"]], ["error"], true), $.methodSignature("SetReadDeadline", [["t", "time.Time"]], ["error"], true), $.methodSignature("SetWriteDeadline", [["t", "time.Time"]], ["error"], true), $.methodSignature("Stat", [], ["fs.FileInfo", "error"], true), $.methodSignature("Sync", [], ["error"], true), $.methodSignature("SyscallConn", [], ["syscall.RawConn", "error"], true), $.methodSignature("Truncate", [["size", /* @__PURE__ */ $.basicType("int64")]], ["error"], true), $.methodSignature("Write", [["b", /* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("uint8"))]], [["n", /* @__PURE__ */ $.basicType("int")], ["err", "error"]], true), $.methodSignature("WriteAt", [["b", /* @__PURE__ */ $.sliceType(/* @__PURE__ */ $.basicType("uint8"))], ["off", /* @__PURE__ */ $.basicType("int64")]], [["n", /* @__PURE__ */ $.basicType("int")], ["err", "error"]], true), $.methodSignature("WriteString", [["s", /* @__PURE__ */ $.basicType("string")]], [["n", /* @__PURE__ */ $.basicType("int")], ["err", "error"]], true), $.methodSignature("WriteTo", [["w", "io.Writer"]], [["n", /* @__PURE__ */ $.basicType("int64")], ["err", "error"]], true)])) +} + +var __goscriptAwait: any + +if ($.isMainScript(import.meta)) { + await main() +} diff --git a/tests/tests/package_import_json_marshaler_alias/tsconfig.json b/tests/tests/package_import_json_marshaler_alias/tsconfig.json new file mode 100644 index 000000000..acb6abf1f --- /dev/null +++ b/tests/tests/package_import_json_marshaler_alias/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "paths": { + "*": [ + "./*" + ], + "@go/github.com/s4wave/goscript/*": [ + "../../../*" + ], + "@goscript/*": [ + "../../../tests/tests/package_import_json_marshaler_alias/run/output/@goscript/*", + "../../../gs/*", + "../../../tests/deps/*" + ], + "@goscript/github.com/s4wave/goscript/tests/tests/package_import_json_marshaler_alias/*": [ + "./*" + ] + } + }, + "extends": "../../../tests/tsconfig.base.json", + "include": [ + "index.ts", + "package_import_json_marshaler_alias.gs.ts" + ] +} diff --git a/tests/tests/reflect_structof_convert_json/reflect_structof_convert_json.gs.ts b/tests/tests/reflect_structof_convert_json/reflect_structof_convert_json.gs.ts index 4531ed3ae..93f86e888 100644 --- a/tests/tests/reflect_structof_convert_json/reflect_structof_convert_json.gs.ts +++ b/tests/tests/reflect_structof_convert_json/reflect_structof_convert_json.gs.ts @@ -4,6 +4,7 @@ import * as $ from "@goscript/builtin/index.js" import * as json from "@goscript/encoding/json/index.js" import * as reflect from "@goscript/reflect/index.js" +import type * as v2 from "@goscript/encoding/json/v2/index.js" export class Node { public declare Name: string