diff --git a/compiler/lowering.go b/compiler/lowering.go index 4e10c45fa..9cf7b2333 100644 --- a/compiler/lowering.go +++ b/compiler/lowering.go @@ -10331,6 +10331,11 @@ func (o *LoweringOwner) lowerExpr(ctx lowerFileContext, expr ast.Expr) (string, case *ast.TypeAssertExpr: return o.lowerTypeAssertExpr(ctx, typed) case *ast.IndexListExpr: + // A generic function instantiated with several type arguments, used + // as a value, keeps them, as lowerIndexExpr does for one. + if signature := genericFunctionSignature(ctx, typed.X); signature != nil { + return o.lowerGenericFunctionValue(ctx, typed.X, typed.Indices, signature) + } return o.lowerExpr(ctx, typed.X) default: return "undefined", []Diagnostic{loweringUnsupportedAt(ctx, typed, "expression", ctx.semPkg.pkgPath, "unsupported expression kind")} @@ -14705,7 +14710,14 @@ func (o *LoweringOwner) lowerGenericFunctionValue( typeArgExprs []ast.Expr, signature *types.Signature, ) (string, []Diagnostic) { + // An override function that takes no type arguments, such as those of + // the slices override, is its own value, as calls of it pass none. calleeExpr, diagnostics := o.lowerExpr(ctx, callee) + if o.callUsesOverridePackage(ctx, callee) && !o.overrideFacts().TakesTypeArgs(calledFunction(ctx.semPkg.source, callee)) { + return calleeExpr, diagnostics + } + + // Otherwise the value calls the function with the type arguments. typeArgs := o.genericTypeArgsExpr(ctx, callee, typeArgExprs) signatureCtx := ctx.withFunctionTypeDepth(ctx.functionTypeDepth + 1) params := o.tsSignatureParamsFor(signatureCtx, signature, false) diff --git a/tests/tests/generic_func_value_type_args/expected.log b/tests/tests/generic_func_value_type_args/expected.log new file mode 100644 index 000000000..fb7470802 --- /dev/null +++ b/tests/tests/generic_func_value_type_args/expected.log @@ -0,0 +1,5 @@ +call neuron true +variable neuron true +map neuron true +single x +slices 1 diff --git a/tests/tests/generic_func_value_type_args/generic_func_value_type_args.go b/tests/tests/generic_func_value_type_args/generic_func_value_type_args.go new file mode 100644 index 000000000..ff0f98482 --- /dev/null +++ b/tests/tests/generic_func_value_type_args/generic_func_value_type_args.go @@ -0,0 +1,43 @@ +package main + +import ( + "slices" + + "github.com/s4wave/goscript/tests/tests/generic_func_value_type_args/lib" +) + +type Sim struct{} + +type Config struct { + lib.BaseConfig + Rate float32 +} + +func (cfg *Config) Defaults() { cfg.Name = "neuron" } + +// embed needs both of its type arguments. +func embed[S, C any](label string) { + _, cfg := lib.NewConfig[C]() + var s S + println(label, cfg.AsBaseConfig().Name, any(&s) != nil) +} + +func first[T any](v T) T { return v } + +func main() { + // A generic function instantiated with several type arguments keeps them + // when it is used as a value, as in a call. + embed[Sim, Config]("call") + f := embed[Sim, Config] + f("variable") + sims := map[string]func(string){"neuron": embed[Sim, Config]} + sims["neuron"]("map") + + // So does one with a single type argument. + g := first[string] + println("single", g("x")) + + // A function of the slices override, which takes no type arguments. + index := slices.Index[[]int, int] + println("slices", index([]int{4, 5, 6}, 5)) +} diff --git a/tests/tests/generic_func_value_type_args/generic_func_value_type_args.gs.ts b/tests/tests/generic_func_value_type_args/generic_func_value_type_args.gs.ts new file mode 100644 index 000000000..08c5c55d7 --- /dev/null +++ b/tests/tests/generic_func_value_type_args/generic_func_value_type_args.gs.ts @@ -0,0 +1,112 @@ +// Generated file based on generic_func_value_type_args.go +// Updated when compliance tests are re-run, DO NOT EDIT! + +import * as $ from "@goscript/builtin/index.js" +import * as slices from "@goscript/slices/index.js" +import * as lib from "@goscript/github.com/s4wave/goscript/tests/tests/generic_func_value_type_args/lib/index.js" +import "@goscript/slices/index.js" + +export class Sim { + constructor(__goscriptValue?: boolean) { + ;(this as any)[$.structValueMarker] = __goscriptValue === true + } + + public clone(): Sim { + return new Sim(true) + } + + public __goscriptAssign(source: Sim): this { + return this + } + + static __typeInfo = $.registerStructType( + "main.Sim", + () => new Sim(), + () => [], + Sim, + () => [] + ) +} + +export class Config { + public declare BaseConfig: lib.BaseConfig + + public declare Rate: number + + constructor(__goscriptValue?: boolean, BaseConfig$?: lib.BaseConfig, Rate$?: number) { + this.BaseConfig = BaseConfig$ ?? new lib.BaseConfig(true) + this.Rate = Rate$ ?? 0 + ;(this as any)[$.structValueMarker] = __goscriptValue === true + } + + public clone(): Config { + return new Config(true, this.BaseConfig.clone(), this.Rate) + } + + public __goscriptAssign(source: Config): this { + this.BaseConfig.__goscriptAssign(source.BaseConfig) + this.Rate = source.Rate + return this + } + + public Defaults(): void { + const cfg: Config | $.VarRef | null = this; + $.pointerValue(cfg).BaseConfig.Name = "neuron" + } + + public AsBaseConfig(): any { + return lib.BaseConfig.prototype.AsBaseConfig.call($.fieldRef(this, "BaseConfig")) + } + + static __typeInfo = $.registerStructType( + "main.Config", + () => new Config(), + () => [{ name: "Defaults", args: [], returns: [], pointerReceiver: true }, { name: "AsBaseConfig", args: [], returns: [{ type: /* @__PURE__ */ $.pointerType("lib.BaseConfig") }], pointerReceiver: true }], + Config, + () => [{ name: "BaseConfig", key: "BaseConfig", type: "lib.BaseConfig", anonymous: true }, { name: "Rate", key: "Rate", type: /* @__PURE__ */ $.basicType("float32") }] + ) +} + +export async function embed(__typeArgs: $.GenericTypeArgs | undefined, label: string): globalThis.Promise { + let [, cfg] = lib.NewConfig({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, C: __typeArgs?.["C"] ?? { type: { kind: $.TypeKind.Interface, methods: [] }, zero: () => null }}) + let s: $.VarRef = $.varRef($.genericZero(__typeArgs, "S", null)) + await $.println(label, $.pointerValue(((__goscriptAwait = (cfg !== null && (cfg as any).__isVarRef !== true && ((cfg as any).__goValue === undefined || (cfg as any).__goType?.charCodeAt(0) !== 42) ? (cfg as Exclude) : $.pointerValue>(cfg)).AsBaseConfig(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait) as lib.BaseConfig | $.VarRef | null)).Name, $.typeParamPointerInterfaceValue(s, __typeArgs?.["S"], "*S", /* @__PURE__ */ $.pointerType(__typeArgs?.["S"]?.type ?? { kind: $.TypeKind.Interface, methods: [] })) != null) +} + +export function first(__typeArgs: $.GenericTypeArgs | undefined, v: any): any { + return $.copyElement(v) +} + +export async function main(): globalThis.Promise { + // A generic function instantiated with several type arguments keeps them + // when it is used as a value, as in a call. + await embed(__goscriptStatic0(), "call") + let f: ((label: string) => void) | null = $.functionValue(async (label: string): globalThis.Promise => await embed(__goscriptStatic0(), label), ({ kind: $.TypeKind.Function, params: [/* @__PURE__ */ $.basicType("string")], results: [] } as $.FunctionTypeInfo)) + void (__goscriptAwait = f!("variable"), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait) + let sims = $.makeMap void) | null>([["neuron", $.functionValue(async (label: string): globalThis.Promise => await embed(__goscriptStatic0(), label), ({ kind: $.TypeKind.Function, params: [/* @__PURE__ */ $.basicType("string")], results: [] } as $.FunctionTypeInfo))]]) + void (__goscriptAwait = $.mapGet void) | null, ((_p0: string) => void) | null>(sims, "neuron", null)[0]!("map"), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait) + + // So does one with a single type argument. + let g: ((v: string) => string | globalThis.Promise) | null = $.functionValue((v: any): any => first(__goscriptStatic1(), v), ({ kind: $.TypeKind.Function, params: [{ kind: $.TypeKind.Interface, methods: [] }], results: [{ kind: $.TypeKind.Interface, methods: [] }] } as $.FunctionTypeInfo)) + await $.println("single", (g!("x") as string)) + + // A function of the slices override, which takes no type arguments. + let index: ((s: $.Slice, v: number) => number | globalThis.Promise) | null = slices.Index + await $.println("slices", (index!($.arrayToSlice([4, 5, 6]), 5) as number)) +} + +var __goscriptStatic0$value: any +function __goscriptStatic0(): any { + return __goscriptStatic0$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, S: { type: "main.Sim", zero: () => new Sim(true) }, C: { type: "main.Config", zero: () => new Config(true) }}) +} + +var __goscriptStatic1$value: any +function __goscriptStatic1(): any { + return __goscriptStatic1$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, T: { type: /* @__PURE__ */ $.basicType("string"), zero: () => "" }}) +} + +var __goscriptAwait: any + +if ($.isMainScript(import.meta)) { + await main() +} diff --git a/tests/tests/generic_func_value_type_args/index.ts b/tests/tests/generic_func_value_type_args/index.ts new file mode 100644 index 000000000..a6e33406a --- /dev/null +++ b/tests/tests/generic_func_value_type_args/index.ts @@ -0,0 +1 @@ +export { Config, Sim } from "./generic_func_value_type_args.gs.ts" diff --git a/tests/tests/generic_func_value_type_args/lib/index.ts b/tests/tests/generic_func_value_type_args/lib/index.ts new file mode 100644 index 000000000..121c99eb9 --- /dev/null +++ b/tests/tests/generic_func_value_type_args/lib/index.ts @@ -0,0 +1,3 @@ +export type { Config } from "./lib.gs.ts" +export { BaseConfig, NewConfig } from "./lib.gs.ts" +import "./lib.gs.ts" diff --git a/tests/tests/generic_func_value_type_args/lib/lib.go b/tests/tests/generic_func_value_type_args/lib/lib.go new file mode 100644 index 000000000..32da3688e --- /dev/null +++ b/tests/tests/generic_func_value_type_args/lib/lib.go @@ -0,0 +1,22 @@ +package lib + +// Config is implemented by pointers to configs that embed BaseConfig. +type Config interface { + AsBaseConfig() *BaseConfig + Defaults() +} + +// BaseConfig is embedded in configs. +type BaseConfig struct { + Name string +} + +func (bc *BaseConfig) AsBaseConfig() *BaseConfig { return bc } + +// NewConfig makes a new config of type C, which needs its type argument. +func NewConfig[C any]() (*C, Config) { + cfgC := new(C) + cfg := any(cfgC).(Config) + cfg.Defaults() + return cfgC, cfg +} diff --git a/tests/tests/generic_func_value_type_args/lib/lib.gs.ts b/tests/tests/generic_func_value_type_args/lib/lib.gs.ts new file mode 100644 index 000000000..4bbb53407 --- /dev/null +++ b/tests/tests/generic_func_value_type_args/lib/lib.gs.ts @@ -0,0 +1,55 @@ +// Generated file based on lib.go +// Updated when compliance tests are re-run, DO NOT EDIT! + +import * as $ from "@goscript/builtin/index.js" + +$.registerTypePackagePath("github.com/s4wave/goscript/tests/tests/generic_func_value_type_args/lib", ["lib.Config", "lib.BaseConfig"]) + +export type Config = { + AsBaseConfig(): BaseConfig | $.VarRef | null + Defaults(): void +} + +$.registerInterfaceType( + "lib.Config", + null, + [{ name: "AsBaseConfig", args: [], returns: [{ type: /* @__PURE__ */ $.pointerType("lib.BaseConfig") }] }, { name: "Defaults", args: [], returns: [] }] +); + +export class BaseConfig { + public declare Name: string + + constructor(__goscriptValue?: boolean, Name$?: string) { + this.Name = Name$ ?? "" + ;(this as any)[$.structValueMarker] = __goscriptValue === true + } + + public clone(): BaseConfig { + return new BaseConfig(true, this.Name) + } + + public __goscriptAssign(source: BaseConfig): this { + this.Name = source.Name + return this + } + + public AsBaseConfig(): BaseConfig | $.VarRef | null { + const bc: BaseConfig | $.VarRef | null = this; + return bc + } + + static __typeInfo = $.registerStructType( + "lib.BaseConfig", + () => new BaseConfig(), + () => [{ name: "AsBaseConfig", args: [], returns: [{ type: /* @__PURE__ */ $.pointerType("lib.BaseConfig") }], pointerReceiver: true }], + BaseConfig, + () => [{ name: "Name", key: "Name", type: /* @__PURE__ */ $.basicType("string") }] + ) +} + +export function NewConfig(__typeArgs: $.GenericTypeArgs | undefined): [any, Config | null] { + let cfgC = $.genericNew(__typeArgs, "C", null) + let cfg = $.mustTypeAssert($.typeParamPointerInterfaceValue(cfgC, __typeArgs?.["C"], "*C", /* @__PURE__ */ $.pointerType(__typeArgs?.["C"]?.type ?? { kind: $.TypeKind.Interface, methods: [] })), "lib.Config") + void (cfg !== null && (cfg as any).__isVarRef !== true && ((cfg as any).__goValue === undefined || (cfg as any).__goType?.charCodeAt(0) !== 42) ? (cfg as Exclude) : $.pointerValue>(cfg)).Defaults() + return [cfgC, cfg] +} diff --git a/tests/tests/generic_func_value_type_args/tsconfig.json b/tests/tests/generic_func_value_type_args/tsconfig.json new file mode 100644 index 000000000..7a09a27a7 --- /dev/null +++ b/tests/tests/generic_func_value_type_args/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "paths": { + "*": [ + "./*" + ], + "@go/github.com/s4wave/goscript/*": [ + "../../../*" + ], + "@goscript/*": [ + "../../../tests/tests/generic_func_value_type_args/run/output/@goscript/*", + "../../../gs/*", + "../../../tests/deps/*" + ], + "@goscript/github.com/s4wave/goscript/tests/tests/generic_func_value_type_args/*": [ + "./*" + ] + } + }, + "extends": "../../../tests/tsconfig.base.json", + "include": [ + "generic_func_value_type_args.gs.ts", + "index.ts", + "lib/index.ts", + "lib/lib.gs.ts" + ] +}