diff --git a/.golangci.yml b/.golangci.yml index bb8b986c..370d4f7c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,12 @@ version: "2" linters: + exclusions: + rules: + # Keep the third-party interpreter diff limited to the concurrency fix. + # Kit-owned integration and regression tests remain fully linted. + - path: internal/yaegi/ + linters: [errcheck, govet, modernize, staticcheck, unused] enable: - modernize # staticcheck and unused catch bugs and dead code that went undetected diff --git a/README.md b/README.md index 4787dff9..5432acee 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ # KIT (Knowledge Inference Tool) +## Extension provider guide + +See [callback-based model providers](www/pages/extensions/providers.md) for the offline examples and current implementation status. + ## SDK guides - [Decision API](https://go-kit.dev/sdk/decisions): typed probabilistic questions, not chat. diff --git a/cmd/models.go b/cmd/models.go index bb68049b..4c854e21 100644 --- a/cmd/models.go +++ b/cmd/models.go @@ -4,12 +4,16 @@ import ( "fmt" "sort" + "github.com/mark3labs/kit/internal/extensions" + "github.com/mark3labs/kit/internal/kitsetup" "github.com/mark3labs/kit/internal/models" kit "github.com/mark3labs/kit/pkg/kit" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var modelsAllFlag bool +var modelsExtensionsFlag bool var modelsCmd = &cobra.Command{ Use: "models [provider]", @@ -36,10 +40,41 @@ Examples: func init() { modelsCmd.Flags().BoolVar(&modelsAllFlag, "all", false, "show all providers in the database, not just fantasy-compatible ones") + modelsCmd.Flags().BoolVar(&modelsExtensionsFlag, "extensions", false, "execute trusted extension Init code and include its declared models") rootCmd.AddCommand(modelsCmd) } func runModels(_ *cobra.Command, args []string) error { + if modelsExtensionsFlag && !viper.GetBool("no-extensions") { + loaded, err := extensions.LoadExtensionsScoped(viper.GetStringSlice("extension"), bareFlag) + if err != nil { + return err + } + if _, err := kitsetup.PrepareProviderFactories(loaded, nil); err != nil { + return err + } + entries := kitsetup.PrepareProviderResolver(loaded).List() + if len(args) == 1 { + if items, ok := entries[args[0]]; ok { + for _, id := range sortedModelIDs(items) { + fmt.Println(id) + } + return nil + } + } + if len(args) == 0 { + providers := make([]string, 0, len(entries)) + for provider := range entries { + providers = append(providers, provider) + } + sort.Strings(providers) + for _, provider := range providers { + for _, id := range sortedModelIDs(entries[provider]) { + fmt.Printf("%s/%s\n", provider, id) + } + } + } + } if len(args) == 1 { return printProvider(args[0]) } diff --git a/cmd/root.go b/cmd/root.go index c1d8704d..dea98d4e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1437,6 +1437,8 @@ func runInteractiveModeBubbleTea(_ context.Context, deps runModeDeps) error { SkillItems: snap.skillItems, GetSkillItems: prov.getSkillItems, ExtensionItems: snap.extensionItems, + GetModelEntries: prov.getModelEntries, + GetThinkingLevels: prov.getThinkingLevels, GetExtensionItems: prov.getExtensionItems, StartupExtensionMessages: snap.startupExtensionMessages, GetWidgets: prov.getWidgets, diff --git a/cmd/run_mode.go b/cmd/run_mode.go index d200b05e..07a562e5 100644 --- a/cmd/run_mode.go +++ b/cmd/run_mode.go @@ -73,6 +73,8 @@ type startupSnapshot struct { // uiProviders holds read-only getters that the TUI polls to render extension // chrome and to refresh lists after hot-reload. None of these mutate state. type uiProviders struct { + getModelEntries func() []ui.ModelEntry + getThinkingLevels func() []string getPromptTemplates func() []*prompts.PromptTemplate getSkillItems func() []ui.SkillItem getExtensionItems func() []ui.ExtensionItem @@ -281,8 +283,12 @@ func newRunApp(k *kit.Kit, cli *ui.CLI, mcpConfig *config.Config, modelName stri if cli != nil { usageTracker = cli.GetUsageTracker() } else { - usageTracker = ui.CreateUsageTracker(viper.GetString("model"), k.IsOAuth()) - ui.UpdateUsageTrackerForMode(usageTracker, viper.GetString("model"), k.CurrentModelMode(), k.IsOAuth()) + if info := k.GetModelInfo(); info != nil { + usageTracker = ui.NewUsageTracker(info, kit.GetCurrentProvider(k.GetCurrentModel()), 80, k.IsOAuth()) + } + if k.CurrentModelMode() != "" { + ui.UpdateUsageTrackerForMode(usageTracker, viper.GetString("model"), k.CurrentModelMode(), k.IsOAuth()) + } } if usageTracker != nil { appOpts.UsageTracker = usageTracker @@ -500,6 +506,14 @@ func mcpResourceReaderForUI(k *kit.Kit) ui.MCPResourceReader { // buildUIProviders wires the read-only getters the TUI polls. func buildUIProviders(k *kit.Kit) uiProviders { return uiProviders{ + getThinkingLevels: k.SupportedThinkingLevels, + getModelEntries: func() []ui.ModelEntry { + entries := []ui.ModelEntry{} + for _, item := range k.GetAvailableModels() { + entries = append(entries, ui.ModelEntry{Provider: item.Provider, ModelID: item.ModelID, Name: item.Name, ContextLimit: item.ContextLimit, Reasoning: item.Reasoning, Available: modelEntryAvailable(k, item.Provider, item.ModelID), AvailabilityUnknown: kit.GetProviderInfo(item.Provider) == nil}) + } + return entries + }, getPromptTemplates: func() []*prompts.PromptTemplate { return loadPromptTemplates(true) }, getSkillItems: func() []ui.SkillItem { return reloadSkillItems(k) }, // getExtensionItems re-collects the loaded extension list, used by the @@ -573,7 +587,9 @@ func buildUIActions(k *kit.Kit, appInstance *app.App, usageTracker *ui.UsageTrac // and m.modelName directly after setModel returns. Extension model // changes instead use the deferred NotifyModelChanged notification. // Update usage tracker with new model info for correct token counting. - ui.UpdateUsageTrackerForModel(usageTracker, modelString, k.IsOAuth()) + if info := k.GetModelInfo(); info != nil && usageTracker != nil { + usageTracker.UpdateModelInfo(info, kit.GetCurrentProvider(modelString), k.IsOAuth()) + } return nil }, // saveProviderAPIKey backs the /connect command. Keys go to the @@ -717,3 +733,12 @@ func startContentWatcher(ctx context.Context, appInstance *app.App) func() { go contentWatcher.Start(ctx) return func() { _ = contentWatcher.Close() } } + +func modelEntryAvailable(k *kit.Kit, provider, model string) bool { + if kit.GetProviderInfo(provider) != nil { + return kit.ValidateEnvironment(provider, "") == nil + } + // Extension auth callbacks are not invoked from the UI event loop. Until + // asynchronous status refresh completes, only no-auth providers are enabled. + return k.CheckModelAvailable(provider + "/" + model) +} diff --git a/examples/extensions/fake-provider.go b/examples/extensions/fake-provider.go new file mode 100644 index 00000000..61990526 --- /dev/null +++ b/examples/extensions/fake-provider.go @@ -0,0 +1,41 @@ +//go:build ignore + +package main + +import "kit/ext" + +// Helpers precede registration because Yaegi does not reliably resolve later +// named helpers referenced from function literals. +func fakeCreate(req ext.ProviderCreateRequest) ext.ProviderCreateResult { + return ext.ProviderCreateResult{Runtime: ext.ProviderRuntime{ + Stream: func(req ext.ProviderRequest, emit func(ext.ProviderEvent) bool) ext.ProviderResult { + select { + case <-req.Done: + return ext.ProviderResult{Error: &ext.ProviderError{Code: "canceled"}} + default: + } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextStart, ID: "greeting"}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextDelta, ID: "greeting", Delta: "Hello from a fake provider."}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextEnd, ID: "greeting"}) { + return ext.ProviderResult{} + } + emit(ext.ProviderEvent{Type: ext.ProviderFinish, FinishReason: ext.ProviderFinishStop}) + return ext.ProviderResult{} + }, + Close: func() error { return nil }, + }} +} + +// Init declares a deterministic offline provider. No API key is required. +// kit --bare -e examples/extensions/fake-provider.go --model fake/echo --no-session --no-core-tools --quiet "hello" +func Init(api ext.API) { + api.RegisterProvider(ext.ProviderDef{ + ID: "fake", Name: "Fake provider", Auth: ext.ProviderAuth{Mode: ext.ProviderAuthNone}, + Models: []ext.ProviderModel{{ID: "echo", Name: "Deterministic greeting", Capabilities: ext.ProviderCapabilities{Streaming: true, Tools: true, InputModalities: []string{"text"}}}}, + Create: fakeCreate, + }) +} diff --git a/examples/extensions/fake-tool-provider.go b/examples/extensions/fake-tool-provider.go new file mode 100644 index 00000000..c7185ed8 --- /dev/null +++ b/examples/extensions/fake-tool-provider.go @@ -0,0 +1,52 @@ +//go:build ignore + +package main + +import "kit/ext" + +func toolCreate(req ext.ProviderCreateRequest) ext.ProviderCreateResult { + return ext.ProviderCreateResult{Runtime: ext.ProviderRuntime{Stream: func(req ext.ProviderRequest, emit func(ext.ProviderEvent) bool) ext.ProviderResult { + // History, not a process-global counter, determines the next response. + resultSeen := false + for _, message := range req.Messages { + for _, part := range message.Parts { + if part.Type == ext.ProviderPartToolResult && part.CallID == "add-call" && part.Text == "5" && !part.IsError { + resultSeen = true + } + } + } + if resultSeen { + if !emit(ext.ProviderEvent{Type: ext.ProviderTextStart, ID: "answer"}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextDelta, ID: "answer", Delta: "5"}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextEnd, ID: "answer"}) { + return ext.ProviderResult{} + } + emit(ext.ProviderEvent{Type: ext.ProviderFinish, FinishReason: ext.ProviderFinishStop}) + } else { + if !emit(ext.ProviderEvent{Type: ext.ProviderToolStart, ID: "add-call", ToolName: "add"}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderToolDelta, ID: "add-call", Delta: `{"a":2,`}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderToolDelta, ID: "add-call", Delta: `"b":3}`}) { + return ext.ProviderResult{} + } + if !emit(ext.ProviderEvent{Type: ext.ProviderToolEnd, ID: "add-call"}) { + return ext.ProviderResult{} + } + emit(ext.ProviderEvent{Type: ext.ProviderFinish, FinishReason: ext.ProviderFinishToolCalls}) + } + return ext.ProviderResult{} + }}} +} + +func Init(api ext.API) { + api.RegisterProvider(ext.ProviderDef{ID: "fake-tool", Name: "History-driven tool fixture", Create: toolCreate, + Models: []ext.ProviderModel{{ID: "add", Name: "Add", Capabilities: ext.ProviderCapabilities{Streaming: true, Tools: true}}}, + }) +} diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 21f9e56e..6da74e21 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -1474,6 +1474,7 @@ func (a *Agent) SetModel(ctx context.Context, config *models.ProviderConfig) err a.modelConfig = config a.providerErr = nil if a.currentModel != nil { + a.currentModel.resolver.Store(config.Resolver) a.currentModel.set(config.ModelString) } @@ -1577,7 +1578,10 @@ func (a *Agent) GetMaxTokens() int { // If MCP tools are still loading in the background, Close waits for them // to finish before closing connections to avoid resource leaks. The wait // is bounded so a hung MCP transport can never block shutdown forever. -func (a *Agent) Close() error { +func (a *Agent) Close() error { return a.CloseContext(context.Background()) } + +// CloseContext bounds cooperative provider cleanup by the caller's deadline. +func (a *Agent) CloseContext(ctx context.Context) error { // Wait for background MCP loading to finish before closing connections. if a.mcpReady != nil { select { @@ -1591,7 +1595,13 @@ func (a *Agent) Close() error { toolErr = a.toolManager.Close() } if a.providerCloser != nil { - if err := a.providerCloser.Close(); err != nil && toolErr == nil { + var err error + if closer, ok := a.providerCloser.(interface{ CloseContext(context.Context) error }); ok { + err = closer.CloseContext(ctx) + } else { + err = a.providerCloser.Close() + } + if err != nil && toolErr == nil { toolErr = err } } diff --git a/internal/agent/image_gate.go b/internal/agent/image_gate.go index 6aa4a4ed..c205c6e5 100644 --- a/internal/agent/image_gate.go +++ b/internal/agent/image_gate.go @@ -68,12 +68,14 @@ func imageUnsupportedText(resp fantasy.ToolResponse, model string) string { // activeModel holds the model string of the active model. Tools read it while // they run, possibly in parallel with SetModel, so it is atomic. type activeModel struct { - v atomic.Pointer[string] + resolver atomic.Pointer[models.Resolver] + v atomic.Pointer[string] } func newActiveModel(cfg *models.ProviderConfig) *activeModel { m := &activeModel{} if cfg != nil { + m.resolver.Store(cfg.Resolver) m.set(cfg.ModelString) } return m @@ -87,6 +89,10 @@ func (m *activeModel) imageInput() (bool, string) { if p == nil || *p == "" { return true, "" } + provider, _, _ := models.ParseModelString(*p) + if m.resolver.Load().Owns(provider) { + return true, *p + } // Preserve input for explicit adapter validation. return modelImageInput(*p), *p } diff --git a/internal/agent/retry.go b/internal/agent/retry.go index 6883f72e..5667837c 100644 --- a/internal/agent/retry.go +++ b/internal/agent/retry.go @@ -110,6 +110,10 @@ func retryableRequestError(err error) bool { if err == nil || err == context.DeadlineExceeded || errors.Is(err, context.Canceled) { return false } + var extensionError interface{ ExtensionRetryable() bool } + if errors.As(err, &extensionError) { + return extensionError.ExtensionRetryable() + } streamMessage := retryableStreamMessage(err.Error()) if pe, ok := errors.AsType[*fantasy.ProviderError](err); ok { if pe.AuthError || pe.StatusCode == http.StatusUnauthorized || pe.StatusCode == http.StatusForbidden { diff --git a/internal/agent/stream_turn.go b/internal/agent/stream_turn.go index 7c32ed7a..ef480f1a 100644 --- a/internal/agent/stream_turn.go +++ b/internal/agent/stream_turn.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "errors" "fmt" + "slices" "strings" "sync" @@ -391,6 +392,52 @@ func (t *streamTurn) onToolResult(tr fantasy.ToolResultContent) error { } func (t *streamTurn) onStepFinish(step fantasy.StepResult) error { + // Host-executed tool results do not inherit model metadata in the model + // library. Carry the matching extension origin so persistence keeps IDs. + if metadata := message.ExtensionOptions(fantasy.ProviderOptions(step.ProviderMetadata)); metadata != nil { + for i := range step.Messages { + positions := map[string]int{} + for index, id := range metadata.Order { + positions[id] = index + } + slices.SortStableFunc(step.Messages[i].Content, func(a, b fantasy.MessagePart) int { + left, right := message.ExtensionOptions(a.Options()), message.ExtensionOptions(b.Options()) + if left == nil || right == nil { + return 0 + } + return positions[left.BlockID] - positions[right.BlockID] + }) + step.Messages[i].ProviderOptions = message.CloneOptions(step.Messages[i].ProviderOptions) + if step.Messages[i].ProviderOptions == nil { + step.Messages[i].ProviderOptions = fantasy.ProviderOptions{} + } + step.Messages[i].ProviderOptions["kit-extension"] = metadata + } + } + origins := map[string]*message.ExtensionMetadata{} + for _, msg := range step.Messages { + for _, part := range msg.Content { + if call, ok := part.(fantasy.ToolCallPart); ok { + if metadata := message.ExtensionOptions(call.ProviderOptions); metadata != nil { + origins[call.ToolCallID] = metadata + } + } + } + } + for i, msg := range step.Messages { + for j, part := range msg.Content { + if result, ok := part.(fantasy.ToolResultPart); ok { + if metadata := origins[result.ToolCallID]; metadata != nil { + result.ProviderOptions = message.CloneOptions(result.ProviderOptions) + if result.ProviderOptions == nil { + result.ProviderOptions = fantasy.ProviderOptions{} + } + result.ProviderOptions["kit-extension"] = &message.ExtensionMetadata{Provider: metadata.Provider} + step.Messages[i].Content[j] = result + } + } + } + } // Accumulate messages from completed steps so they can be // persisted even if a later step is cancelled. t.completedStepMessages = append(t.completedStepMessages, step.Messages...) diff --git a/internal/extbridge/context.go b/internal/extbridge/context.go index 442568d8..a0c0bf79 100644 --- a/internal/extbridge/context.go +++ b/internal/extbridge/context.go @@ -219,7 +219,7 @@ func BaseContext(ctx context.Context, kitInstance *kit.Kit) extensions.Context { // Model Resolution API // ------------------------------------------------------------------- ResolveModelChain: func(preferences []string) extensions.ModelResolutionResult { - return kit.ResolveModelChain(preferences) + return kitInstance.ResolveModelChain(preferences) }, GetModelCapabilities: func(model string) (extensions.ModelCapabilities, string) { // An empty model means "the one in use", matching the convention @@ -234,10 +234,10 @@ func BaseContext(ctx context.Context, kitInstance *kit.Kit) extensions.Context { if model == "" { model = kitInstance.GetCurrentModel() } - return kit.GetModelCapabilities(model) + return kitInstance.GetModelCapabilities(model) }, CheckModelAvailable: func(model string) bool { - return kit.CheckModelAvailable(model) + return kitInstance.CheckModelAvailable(model) }, GetCurrentProvider: func() string { return kit.GetCurrentProvider(kitInstance.Extensions().GetContext().Model) diff --git a/internal/extensions/api.go b/internal/extensions/api.go index 1716b72d..8d2a7969 100644 --- a/internal/extensions/api.go +++ b/internal/extensions/api.go @@ -1076,6 +1076,10 @@ type ModelConditional struct { // ModelCapabilities describes what a model supports. type ModelCapabilities struct { + Tools bool + CapabilitiesKnown bool + ContextLimitKnown bool + OutputLimitKnown bool // Provider is the provider ID (e.g., "anthropic"). Provider string // ModelID is the model identifier (e.g., "claude-sonnet-4-20250929"). @@ -1338,6 +1342,7 @@ type API struct { onMessageEnd func(func(MessageEndEvent, Context)) onSessionStart func(func(SessionStartEvent, Context)) onSessionShutdown func(func(SessionShutdownEvent, Context)) + registerProviderFn func(ProviderDef) registerToolFn func(ToolDef) registerCmdFn func(CommandDef) registerToolRendererFn func(ToolRenderConfig) @@ -2114,6 +2119,12 @@ type OverlayResult struct { // ModelInfoEntry represents a known model from the registry. Used by // GetAvailableModels to let extensions discover which models are available. type ModelInfoEntry struct { + InputModalities []string + Tools bool + Streaming bool + CapabilitiesKnown bool + ContextLimitKnown bool + OutputLimitKnown bool // Provider is the provider ID (e.g. "anthropic", "openai"). Provider string // ModelID is the model identifier (e.g. "claude-sonnet-4-5-20250929"). diff --git a/internal/extensions/loader.go b/internal/extensions/loader.go index 87bd8742..aeade2fa 100644 --- a/internal/extensions/loader.go +++ b/internal/extensions/loader.go @@ -1,6 +1,7 @@ package extensions import ( + "errors" "fmt" "os" "path/filepath" @@ -10,7 +11,7 @@ import ( "sync" "github.com/charmbracelet/log" - "github.com/traefik/yaegi/interp" + "github.com/mark3labs/kit/internal/yaegi/interp" "github.com/traefik/yaegi/stdlib" "github.com/traefik/yaegi/stdlib/unrestricted" ) @@ -38,9 +39,33 @@ func LoadExtensions(extraPaths []string) ([]LoadedExtension, error) { // are all skipped — and only extraPaths load. Use it so that running Kit // inside a directory does not silently execute that directory's extensions. func LoadExtensionsScoped(extraPaths []string, bare bool) ([]LoadedExtension, error) { + result := LoadExtensionsWithDiagnostics(extraPaths, bare) + return result.Extensions, errors.Join(result.RegistrationErrors...) +} + +// ExtensionLoadResult retains ordered per-path failures without changing the +// historical tolerance for unrelated broken extensions at ordinary startup. +type ExtensionLoadResult struct { + Extensions []LoadedExtension + Diagnostics []error + RegistrationErrors []error +} + +// LoadExtensionsStrict validates a complete reload candidate before publication. +func LoadExtensionsStrict(extraPaths []string, bare bool) ([]LoadedExtension, error) { + result := LoadExtensionsWithDiagnostics(extraPaths, bare) + if err := errors.Join(result.Diagnostics...); err != nil { + return nil, err + } + return result.Extensions, nil +} + +// LoadExtensionsWithDiagnostics loads once and preserves every discovered error. +func LoadExtensionsWithDiagnostics(extraPaths []string, bare bool) ExtensionLoadResult { + var result ExtensionLoadResult paths := discoverExtensionPaths(extraPaths, bare) if len(paths) == 0 { - return nil, nil + return result } // Compile every extension in parallel, then run the Init functions one @@ -71,16 +96,22 @@ func LoadExtensionsScoped(extraPaths []string, bare bool) ([]LoadedExtension, er var loaded []LoadedExtension for i, p := range paths { if errs[i] != nil { + result.Diagnostics = append(result.Diagnostics, fmt.Errorf("extension %s: %w", p, errs[i])) continue } ext, err := initExtension(p, initFns[i]) if err != nil { + result.Diagnostics = append(result.Diagnostics, fmt.Errorf("extension %s: %w", p, err)) + if ext != nil && len(ext.RegistrationErrors) > 0 { + result.RegistrationErrors = append(result.RegistrationErrors, ext.RegistrationErrors...) + } continue } loaded = append(loaded, *ext) log.Debug("loaded extension", "path", p, "handlers", countHandlers(ext), "tools", len(ext.Tools), "commands", len(ext.Commands), "tool_renderers", len(ext.ToolRenderers)) } - return loaded, nil + result.Extensions = loaded + return result } // pathSet is a thread-safe helper for deduplicating and ordering file paths. @@ -498,6 +529,7 @@ func initExtension(path string, initFn func(API)) (*LoadedExtension, error) { } api := API{ + registerProviderFn: ext.registerProvider, onToolCall: resultReg[ToolCallEvent, ToolCallResult](reg, ToolCall), onToolCallInputStart: notifyReg[ToolCallInputStartEvent](reg, ToolCallInputStart), onToolCallInputDelta: notifyReg[ToolCallInputDeltaEvent](reg, ToolCallInputDelta), @@ -572,6 +604,9 @@ func initExtension(path string, initFn func(API)) (*LoadedExtension, error) { return nil, err } + if len(ext.RegistrationErrors) > 0 { + return ext, errors.Join(ext.RegistrationErrors...) + } return ext, nil } diff --git a/internal/extensions/provider_adapter.go b/internal/extensions/provider_adapter.go new file mode 100644 index 00000000..86f54147 --- /dev/null +++ b/internal/extensions/provider_adapter.go @@ -0,0 +1,402 @@ +package extensions + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "sync" + "sync/atomic" + "time" + + "charm.land/fantasy" + "github.com/kaptinlin/jsonschema" + "github.com/mark3labs/kit/internal/message" +) + +// providerAdapter is compiled code; no interpreted type implements a host +// interface. The gate serializes one runtime without locking other runtimes. +type providerAdapter struct { + provider string + owner *ProviderOwner + model string + capabilities ProviderCapabilities + parameters ProviderParameters + runtime ProviderRuntime + gate chan struct{} + lifetime context.Context + cancel context.CancelFunc + closeOnce sync.Once + closeDone chan struct{} + closeErr error + callbackGID atomic.Int64 + credential string +} + +// NewProviderModel creates a private compiled adapter. Callers validate owned +// definitions/collisions before this boundary. Credentials are only in Create. +func NewProviderModel(ctx context.Context, def ProviderDef, request ProviderCreateRequest) (model fantasy.LanguageModel, closer func(context.Context) error, err error) { + if !def.owner.acquire() { + return nil, nil, errors.New("extension provider generation is retired") + } + transferred := false + defer func() { + if !transferred { + def.owner.release() + } + }() + var capability ProviderCapabilities + found := false + for _, item := range def.Models { + if item.ID == request.Model { + capability = item.Capabilities + if item.SettingsJSON != "" { + request.Parameters.SettingsJSON = item.SettingsJSON + } + found = true + break + } + } + if !found { + if !def.AllowUnknownModels { + return nil, nil, fmt.Errorf("extension provider %s does not declare model %q", def.ID, request.Model) + } + capability = def.UnknownModelDefaults + } + defer func() { + if recover() != nil { + model = nil + closer = nil + err = fmt.Errorf("panic in extension provider %s Create", def.ID) + } + }() + if ctx.Err() != nil { + return nil, nil, ctx.Err() + } + callbackCtx, callbackDone, callbackErr := def.owner.callback(ctx) + if callbackErr != nil { + return nil, nil, callbackErr + } + defer callbackDone() + ctx = callbackCtx + request.Provider = def.ID + request.Done = ctx.Done() + if level := request.Parameters.ReasoningLevel; level != "" && level != "off" { + allowed := false + for _, supported := range capability.ReasoningLevels { + if supported == level { + allowed = true + } + } + if !capability.Reasoning || !allowed { + return nil, nil, errors.New("unsupported extension reasoning level") + } + } + if def.Auth.Mode == ProviderAuthExternal && def.Auth.Status != nil { + checkCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + status := def.Auth.Status(ProviderAuthRequest{Done: checkCtx.Done()}) + checkErr := checkCtx.Err() + cancel() + if checkErr != nil { + return nil, nil, checkErr + } + if status.Error != nil || status.Status == ProviderAuthUnconfigured { + return nil, nil, errors.New("extension provider authentication is not configured") + } + } + if def.ValidateSettings != nil { + if failure := def.ValidateSettings(request.Parameters.SettingsJSON); failure != nil { + return nil, nil, errors.New("extension provider settings validation failed") + } + } + result := def.Create(request) + if result.Error != nil { + code := result.Error.Code + if request.APIKey != "" { + code = strings.ReplaceAll(code, request.APIKey, "[redacted]") + } + return nil, nil, fmt.Errorf("extension provider %s Create failed (%s)", def.ID, code) + } + if result.Runtime.Stream == nil { + if result.Runtime.Close != nil { + _ = result.Runtime.Close() + } + return nil, nil, fmt.Errorf("extension provider %s returned no Stream callback", def.ID) + } + lifetime, cancel := context.WithCancel(context.Background()) + transferred = true + adapter := &providerAdapter{credential: request.APIKey, owner: def.owner, provider: def.ID, model: request.Model, capabilities: capability, parameters: request.Parameters, runtime: result.Runtime, gate: make(chan struct{}, 1), lifetime: lifetime, cancel: cancel, closeDone: make(chan struct{})} + if !def.owner.track(adapter) { + _ = adapter.closeContext(ctx) + return nil, nil, errors.New("extension provider generation is shutting down") + } + return adapter, adapter.closeContext, nil +} + +func (p *providerAdapter) Provider() string { return p.provider } +func (p *providerAdapter) Model() string { return p.model } +func (p *providerAdapter) GenerateObject(context.Context, fantasy.ObjectCall) (*fantasy.ObjectResponse, error) { + return nil, errors.New("extension providers do not support object generation") +} +func (p *providerAdapter) StreamObject(context.Context, fantasy.ObjectCall) (fantasy.ObjectStreamResponse, error) { + return nil, errors.New("extension providers do not support object streaming") +} + +func (p *providerAdapter) closeContext(ctx context.Context) error { + if p.callbackGID.Load() == goroutineID() { + return errors.New("cannot close extension provider from its own callback") + } + p.closeOnce.Do(func() { + p.cancel() + go func() { + defer close(p.closeDone) + defer p.owner.release() + defer p.owner.untrack(p) + // An active noncooperative callback retains the owner until it returns. + p.gate <- struct{}{} + defer func() { <-p.gate }() + defer func() { + if recover() != nil { + p.closeErr = errors.New("panic in extension provider Close") + } + }() + if p.runtime.Close != nil { + p.callbackGID.Store(goroutineID()) + defer p.callbackGID.Store(0) + p.closeErr = p.runtime.Close() + if p.closeErr != nil && p.credential != "" { + p.closeErr = errors.New(strings.ReplaceAll(p.closeErr.Error(), p.credential, "[redacted]")) + } + } + }() + }) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + select { + case <-p.closeDone: + return p.closeErr + case <-ctx.Done(): + return fmt.Errorf("extension provider %s/%s shutdown: %w", p.provider, p.model, ctx.Err()) + } +} + +func (p *providerAdapter) Stream(ctx context.Context, call fantasy.Call) (fantasy.StreamResponse, error) { + request, err := p.convertRequest(call) + if err != nil { + return nil, err + } + return func(yield func(fantasy.StreamPart) bool) { + requestCtx, cancel := context.WithCancel(ctx) + stop := context.AfterFunc(p.lifetime, cancel) + defer stop() + defer cancel() + select { + case p.gate <- struct{}{}: + case <-requestCtx.Done(): + yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeError, Error: requestCtx.Err()}) + return + } + defer func() { <-p.gate }() + if err := requestCtx.Err(); err != nil { + yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeError, Error: err}) + return + } + p.callbackGID.Store(goroutineID()) + defer p.callbackGID.Store(0) + consumerStopped := false + forward := func(part fantasy.StreamPart) bool { + if consumerStopped { + return false + } + if !yield(part) { + consumerStopped = true + return false + } + return true + } + runtime := p.runtime + stream := runtime.Stream + runtime.Stream = func(request ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + result := stream(request, emit) + if result.Error != nil { + copy := *result.Error + if p.credential != "" { + copy.Message = strings.ReplaceAll(copy.Message, p.credential, "[redacted]") + copy.Code = strings.ReplaceAll(copy.Code, p.credential, "[redacted]") + } + result.Error = © + } + return result + } + if err := runProviderStream(requestCtx, runtime, request, forward); err != nil && !consumerStopped { + yield(fantasy.StreamPart{Type: fantasy.StreamPartTypeError, Error: err}) + } + }, nil +} + +func (p *providerAdapter) Generate(ctx context.Context, call fantasy.Call) (*fantasy.Response, error) { + stream, err := p.Stream(ctx, call) + if err != nil { + return nil, err + } + response := &fantasy.Response{} + texts := map[string]*fantasy.TextContent{} + reasoning := map[string]*fantasy.ReasoningContent{} + for event := range stream { + switch event.Type { + case fantasy.StreamPartTypeTextStart: + item := &fantasy.TextContent{} + texts[event.ID] = item + response.Content = append(response.Content, item) + case fantasy.StreamPartTypeTextDelta: + texts[event.ID].Text += event.Delta + case fantasy.StreamPartTypeTextEnd: + texts[event.ID].ProviderMetadata = event.ProviderMetadata + case fantasy.StreamPartTypeReasoningStart: + item := &fantasy.ReasoningContent{} + reasoning[event.ID] = item + response.Content = append(response.Content, item) + case fantasy.StreamPartTypeReasoningDelta: + reasoning[event.ID].Text += event.Delta + case fantasy.StreamPartTypeReasoningEnd: + reasoning[event.ID].ProviderMetadata = event.ProviderMetadata + case fantasy.StreamPartTypeToolCall: + response.Content = append(response.Content, fantasy.ToolCallContent{ToolCallID: event.ID, ToolName: event.ToolCallName, Input: event.ToolCallInput, ProviderMetadata: event.ProviderMetadata}) + case fantasy.StreamPartTypeFinish: + response.ProviderMetadata = event.ProviderMetadata + response.FinishReason = event.FinishReason + response.Usage = event.Usage + case fantasy.StreamPartTypeError: + return nil, event.Error + } + } + if ctx.Err() != nil { + return nil, ctx.Err() + } + if response.FinishReason == "" { + return nil, errors.New("extension provider stream canceled without finish") + } + return response, nil +} + +func (p *providerAdapter) convertRequest(call fantasy.Call) (ProviderRequest, error) { + request := ProviderRequest{Provider: p.provider, Model: p.model, ToolChoice: ProviderToolChoice{Type: ProviderToolAuto}, Parameters: ProviderParameters{MaxOutputTokens: call.MaxOutputTokens, Temperature: call.Temperature, TopP: call.TopP, TopK: call.TopK, PresencePenalty: call.PresencePenalty, FrequencyPenalty: call.FrequencyPenalty}} + request.Parameters.ReasoningLevel = p.parameters.ReasoningLevel + request.Parameters.SettingsJSON = p.parameters.SettingsJSON + request.Parameters.StopSequences = append([]string(nil), p.parameters.StopSequences...) + seenTools := map[string]bool{} + for _, tool := range call.Tools { + function, ok := tool.(fantasy.FunctionTool) + if !ok || !p.capabilities.Tools { + return request, errors.New("extension model does not support exposed tools") + } + if function.Name == "" || seenTools[function.Name] { + return request, errors.New("invalid or duplicate exposed tool name") + } + seenTools[function.Name] = true + if function.InputSchema["type"] != "object" { + return request, errors.New("tool schema must be an object") + } + schema, err := json.Marshal(function.InputSchema) + if err != nil { + return request, fmt.Errorf("invalid tool schema: %w", err) + } + compiler := jsonschema.NewCompiler() + clear(compiler.Loaders) // Schema preflight must never perform outbound I/O. + if _, err := compiler.Compile(schema); err != nil { + return request, errors.New("invalid exposed tool schema") + } + request.Tools = append(request.Tools, ProviderTool{Name: function.Name, Description: function.Description, SchemaJSON: string(schema)}) + } + if call.ToolChoice != nil { + switch *call.ToolChoice { + case fantasy.ToolChoiceAuto: + request.ToolChoice.Type = ProviderToolAuto + case fantasy.ToolChoiceNone: + request.ToolChoice.Type = ProviderToolNone + case fantasy.ToolChoiceRequired: + request.ToolChoice.Type = ProviderToolRequired + default: + request.ToolChoice = ProviderToolChoice{Type: ProviderToolNamed, Name: string(*call.ToolChoice)} + found := false + for _, tool := range request.Tools { + if tool.Name == request.ToolChoice.Name { + found = true + } + } + if !found { + return request, errors.New("named tool is not exposed") + } + } + } + for _, msg := range call.Prompt { + converted := ProviderMessage{Role: string(msg.Role)} + if metadata := message.ExtensionOptions(msg.ProviderOptions); metadata != nil { + if metadata.Provider != p.provider { + return request, errors.New("unsupported extension replay namespace") + } + for _, replay := range metadata.Replay { + if replay.Version != 1 || replay.Provider != p.provider || !json.Valid([]byte(replay.JSON)) { + return request, errors.New("unsupported extension message replay metadata") + } + converted.Replay = append(converted.Replay, ProviderReplayData{Version: replay.Version, Provider: replay.Provider, JSON: replay.JSON}) + } + } + for _, part := range msg.Content { + var item ProviderPart + switch v := part.(type) { + case fantasy.TextPart: + item = ProviderPart{Type: ProviderPartText, Text: v.Text} + case fantasy.ReasoningPart: + if !p.capabilities.Reasoning { + return request, errors.New("extension model does not support reasoning history") + } + item = ProviderPart{Type: ProviderPartReasoning, Text: v.Text} + case fantasy.FilePart: + allowed := false + for _, modality := range p.capabilities.InputModalities { + if modality == "file" || (modality == "image" && strings.HasPrefix(v.MediaType, "image/")) || (modality == "audio" && strings.HasPrefix(v.MediaType, "audio/")) || (modality == "video" && strings.HasPrefix(v.MediaType, "video/")) { + allowed = true + } + } + if !allowed { + return request, errors.New("extension model does not support input modality") + } + item = ProviderPart{Type: ProviderPartFile, Data: append([]byte(nil), v.Data...), MIMEType: v.MediaType, Filename: v.Filename} + case fantasy.ToolCallPart: + item = ProviderPart{Type: ProviderPartToolCall, CallID: v.ToolCallID, ToolName: v.ToolName, ArgumentsJSON: v.Input} + case fantasy.ToolResultPart: + item = ProviderPart{Type: ProviderPartToolResult, CallID: v.ToolCallID} + switch output := v.Output.(type) { + case fantasy.ToolResultOutputContentText: + item.Text = output.Text + case fantasy.ToolResultOutputContentError: + item.IsError = true + item.Text = output.Error.Error() + default: + return request, errors.New("unsupported extension tool result modality") + } + default: + return request, errors.New("unsupported extension message part") + } + if metadata := message.ExtensionOptions(part.Options()); metadata != nil { + if metadata.Provider != p.provider { + return request, errors.New("unsupported extension replay namespace") + } + item.Signature = metadata.Signature + for _, replay := range metadata.Replay { + if replay.Version != 1 || replay.Provider != p.provider || !json.Valid([]byte(replay.JSON)) { + return request, errors.New("unsupported extension replay metadata") + } + item.Replay = append(item.Replay, ProviderReplayData{Version: replay.Version, Provider: replay.Provider, JSON: replay.JSON}) + } + } else if len(part.Options()) > 0 { + return request, errors.New("unsupported extension replay metadata") + } + converted.Parts = append(converted.Parts, item) + } + request.Messages = append(request.Messages, converted) + } + return request, nil +} diff --git a/internal/extensions/provider_adapter_test.go b/internal/extensions/provider_adapter_test.go new file mode 100644 index 00000000..4b26c7e4 --- /dev/null +++ b/internal/extensions/provider_adapter_test.go @@ -0,0 +1,47 @@ +package extensions + +import ( + "context" + "errors" + "strings" + "testing" + + "charm.land/fantasy" +) + +func TestProviderAdapterPreflightAndCredentialRedaction(t *testing.T) { + var requests int + def := ProviderDef{ID: "fake", AllowUnknownModels: true, Create: func(ProviderCreateRequest) ProviderCreateResult { + return ProviderCreateResult{Runtime: ProviderRuntime{Stream: func(ProviderRequest, func(ProviderEvent) bool) ProviderResult { + requests++ + return ProviderResult{Error: &ProviderError{Code: "auth", Message: "bad transient-key"}} + }}} + }} + model, close, err := NewProviderModel(context.Background(), def, ProviderCreateRequest{Model: "echo", APIKey: "transient-key"}) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := close(context.Background()); err != nil { + t.Error(err) + } + }() + _, err = model.Generate(context.Background(), fantasy.Call{Prompt: fantasy.Prompt{{Role: fantasy.MessageRoleUser, Content: []fantasy.MessagePart{fantasy.FilePart{MediaType: "image/png", Data: []byte{1}}}}}}) + if err == nil || requests != 0 { + t.Fatalf("preflight err=%v requests=%d", err, requests) + } + _, err = model.Generate(context.Background(), fantasy.Call{}) + if err == nil || strings.Contains(err.Error(), "transient-key") || !strings.Contains(err.Error(), "[redacted]") { + t.Fatalf("unsafe error=%v", err) + } + var code interface{ ExtensionErrorCode() string } + if !errors.As(err, &code) || code.ExtensionErrorCode() != "auth" { + t.Fatalf("code=%v", err) + } + if _, err := model.GenerateObject(context.Background(), fantasy.ObjectCall{}); err == nil { + t.Fatal("object operation accepted") + } + if requests != 1 { + t.Fatalf("requests=%d", requests) + } +} diff --git a/internal/extensions/provider_auth.go b/internal/extensions/provider_auth.go new file mode 100644 index 00000000..a63312e4 --- /dev/null +++ b/internal/extensions/provider_auth.go @@ -0,0 +1,97 @@ +package extensions + +import ( + "context" + "sync" + "time" +) + +// ProviderAuthCache holds only safe status, never credentials. One cache belongs +// to one definition generation. Pending checks are deduplicated and bounded. +type ProviderAuthCache struct { + mu sync.Mutex + status ProviderAuthResult + expires time.Time + pending bool + generation uint64 +} + +// Status starts an asynchronous safe status check and returns unknown while it +// runs. The callback is never invoked on the UI event-loop goroutine. +func (c *ProviderAuthCache) Status(def ProviderDef, configured bool) ProviderAuthResult { + if def.Auth.Mode == "" || def.Auth.Mode == ProviderAuthNone { + return ProviderAuthResult{Status: ProviderAuthConfigured} + } + if def.Auth.Mode == ProviderAuthAPIKey { + if configured { + return ProviderAuthResult{Status: ProviderAuthConfigured} + } + return ProviderAuthResult{Status: ProviderAuthUnconfigured, Instruction: def.Auth.SetupInstruction} + } + c.mu.Lock() + defer c.mu.Unlock() + if time.Now().Before(c.expires) { + return c.status + } + if !c.pending && def.Auth.Status != nil && def.owner.acquire() { + c.pending = true + generation := c.generation + go func() { + defer def.owner.release() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + callbackCtx, callbackDone, err := def.owner.callback(ctx) + if err != nil { + c.mu.Lock() + c.pending = false + c.mu.Unlock() + return + } + defer callbackDone() + ctx = callbackCtx + result := ProviderAuthResult{Status: ProviderAuthUnknown} + // Trusted code must cooperate with Done. We do not leak an extra waiter + // goroutine per timed-out check or start another while it remains active. + func() { + defer func() { + if recover() != nil { + result = ProviderAuthResult{Status: ProviderAuthUnknown} + } + }() + result = def.Auth.Status(ProviderAuthRequest{Done: ctx.Done()}) + }() + if ctx.Err() != nil || result.Error != nil { + result = ProviderAuthResult{Status: ProviderAuthUnknown} + } + switch result.Status { + case ProviderAuthConfigured, ProviderAuthUnconfigured, ProviderAuthUnknown: + default: + result.Status = ProviderAuthUnknown + } + c.mu.Lock() + if c.generation == generation { + c.status = result + c.expires = time.Now().Add(30 * time.Second) + } + c.pending = false + c.mu.Unlock() + }() + } + return ProviderAuthResult{Status: ProviderAuthUnknown, Instruction: def.Auth.SetupInstruction} +} + +// Refresh invalidates status without starting overlapping callbacks. +func (c *ProviderAuthCache) Refresh() { + c.mu.Lock() + c.generation++ + c.expires = time.Time{} + c.mu.Unlock() +} + +// AuthStatus queries generation-scoped safe availability without generation. +func (def ProviderDef) AuthStatus(configured bool) ProviderAuthResult { + if def.authCache == nil { + return ProviderAuthResult{Status: ProviderAuthUnknown} + } + return def.authCache.Status(def, configured) +} diff --git a/internal/extensions/provider_concurrency_probe_test.go b/internal/extensions/provider_concurrency_probe_test.go new file mode 100644 index 00000000..47e628fb --- /dev/null +++ b/internal/extensions/provider_concurrency_probe_test.go @@ -0,0 +1,123 @@ +package extensions + +import ( + "os" + "reflect" + "sync" + "testing" + "time" + + "github.com/mark3labs/kit/internal/yaegi/interp" + "github.com/traefik/yaegi/stdlib" +) + +type probeRequest struct { + Done <-chan struct{} + Entered func(string) + Release <-chan struct{} + Text string +} +type probeResult struct { + Text string + Stream func(probeRequest, func(string) bool) string + Close func() error +} +type probeDef struct { + Create func(probeRequest) probeResult +} + +// Exercise the proposed callback shapes before exposing a public contract. +func TestProviderConcurrencyProbe(t *testing.T) { + i := interp.New(interp.Options{Env: os.Environ()}) + if err := i.Use(stdlib.Symbols); err != nil { + t.Fatal(err) + } + if err := i.Use(interp.Exports{"probe/ext/ext": { + "Request": reflect.ValueOf((*probeRequest)(nil)), + "Result": reflect.ValueOf((*probeResult)(nil)), + "Def": reflect.ValueOf((*probeDef)(nil)), + }}); err != nil { + t.Fatal(err) + } + _, err := i.Eval(`package main +import "probe/ext" +func helper(req ext.Request) ext.Result { + return ext.Result{Stream: func(req ext.Request, emit func(string) bool) string { + if req.Entered != nil { + req.Entered(req.Text) + select { case <-req.Done: return "canceled"; case <-req.Release: } + } else { + select { case <-req.Done: return "canceled"; default: } + } + if !emit(req.Text) { return "stopped" } + return "ok" + }, Close: func() error { return nil }} +} +func define() ext.Def { return ext.Def{Create: helper} } +`) + if err != nil { + t.Fatal(err) + } + value, err := i.Eval("define()") + if err != nil { + t.Fatal(err) + } + def := value.Interface().(probeDef) + start := make(chan struct{}) + var wg sync.WaitGroup + for range 8 { + runtime := def.Create(probeRequest{}) + wg.Go(func() { + <-start + for range 100 { + result := runtime.Stream(probeRequest{Text: "hello"}, func(s string) bool { return s == "hello" }) + if result != "ok" { + t.Errorf("stream result = %q", result) + return + } + if err := runtime.Close(); err != nil { + t.Error(err) + } + } + }) + } + close(start) + wg.Wait() + + // All streams must enter together; cancel half and release the rest. + entered := make(chan string, 8) + results := make(chan string, 8) + release := make(chan struct{}) + var cancel []chan struct{} + for range 8 { + runtime := def.Create(probeRequest{}) + done := make(chan struct{}) + cancel = append(cancel, done) + wg.Go(func() { + results <- runtime.Stream(probeRequest{Done: done, Entered: func(s string) { entered <- s }, Release: release, Text: "hello"}, func(string) bool { return true }) + }) + } + defer func() { close(release); wg.Wait() }() + deadline := time.NewTimer(5 * time.Second) + defer deadline.Stop() + for range 8 { + select { + case <-entered: + case <-deadline.C: + t.Fatal("separate runtimes did not enter concurrently") + } + } + for _, done := range cancel[:4] { + close(done) + } + for range 4 { + select { + case result := <-results: + if result != "canceled" { + t.Errorf("result = %q", result) + } + case <-deadline.C: + t.Fatal("cancellation did not release stream") + } + } +} diff --git a/internal/extensions/provider_lifecycle_test.go b/internal/extensions/provider_lifecycle_test.go new file mode 100644 index 00000000..92ffb15d --- /dev/null +++ b/internal/extensions/provider_lifecycle_test.go @@ -0,0 +1,176 @@ +package extensions + +import ( + "context" + "errors" + "sync/atomic" + "testing" + "time" + + "charm.land/fantasy" +) + +func TestProviderCloseCancelsDrainsAndClosesOnce(t *testing.T) { + entered := make(chan struct{}) + release := make(chan struct{}) + returned := make(chan struct{}) + var closed atomic.Int32 + def := ProviderDef{ID: "fake", AllowUnknownModels: true, Create: func(ProviderCreateRequest) ProviderCreateResult { + return ProviderCreateResult{Runtime: ProviderRuntime{ + Stream: func(req ProviderRequest, _ func(ProviderEvent) bool) ProviderResult { + close(entered) + <-req.Done + <-release + return ProviderResult{} + }, + Close: func() error { closed.Add(1); return nil }, + }} + }} + model, closeModel, err := NewProviderModel(context.Background(), def, ProviderCreateRequest{Model: "echo"}) + if err != nil { + t.Fatal(err) + } + go func() { + defer close(returned) + stream, err := model.Stream(context.Background(), fantasy.Call{}) + if err != nil { + t.Error(err) + return + } + for range stream { + } + }() + <-entered + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + err = closeModel(ctx) + if !errors.Is(err, context.DeadlineExceeded) { + t.Errorf("close=%v", err) + } + if closed.Load() != 0 { + t.Error("Close ran concurrently with Stream") + } + close(release) + <-returned + if err := closeModel(context.Background()); err != nil { + t.Error(err) + } + if err := closeModel(context.Background()); err != nil { + t.Error(err) + } + if closed.Load() != 1 { + t.Errorf("close count=%d", closed.Load()) + } +} + +func TestProviderOwnerRetirementWaitsForRuntimes(t *testing.T) { + owner := &ProviderOwner{} + if !owner.acquire() { + t.Fatal("acquire") + } + shutdown := false + owner.Retire(func() { shutdown = true }) + if shutdown || owner.acquire() { + t.Fatal("retired owner accepted work or shut down early") + } + owner.release() + if !shutdown { + t.Fatal("owner not shut down") + } + owner.Retire(func() { t.Error("duplicate shutdown") }) +} + +func TestProviderReentrantCloseRejected(t *testing.T) { + var closeModel func(context.Context) error + def := ProviderDef{ID: "fake", AllowUnknownModels: true, Create: func(ProviderCreateRequest) ProviderCreateResult { + return ProviderCreateResult{Runtime: ProviderRuntime{Stream: func(_ ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + if err := closeModel(context.Background()); err == nil { + t.Error("reentrant close accepted") + } + emit(ProviderEvent{Type: ProviderFinish, FinishReason: ProviderFinishStop}) + return ProviderResult{} + }}} + }} + model, close, err := NewProviderModel(context.Background(), def, ProviderCreateRequest{Model: "echo"}) + if err != nil { + t.Fatal(err) + } + closeModel = close + if _, err := model.Generate(context.Background(), fantasy.Call{}); err != nil { + t.Fatal(err) + } + if err := closeModel(context.Background()); err != nil { + t.Fatal(err) + } +} + +func TestProviderCallbackPanicIsSafe(t *testing.T) { + runtime := ProviderRuntime{Stream: func(ProviderRequest, func(ProviderEvent) bool) ProviderResult { panic("private credential") }} + err := runProviderStream(context.Background(), runtime, ProviderRequest{}, func(fantasy.StreamPart) bool { return true }) + if err == nil || err.Error() != "panic in extension provider Stream" { + t.Fatalf("err=%v", err) + } +} + +func TestProviderIteratorStopsWithoutFurtherYield(t *testing.T) { + def := ProviderDef{ID: "fake", AllowUnknownModels: true, Create: func(ProviderCreateRequest) ProviderCreateResult { + return ProviderCreateResult{Runtime: ProviderRuntime{Stream: func(_ ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + emit(ProviderEvent{Type: ProviderTextStart, ID: "text"}) + return ProviderResult{} + }}} + }} + model, close, err := NewProviderModel(context.Background(), def, ProviderCreateRequest{Model: "echo"}) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := close(context.Background()); err != nil { + t.Error(err) + } + }() + stream, err := model.Stream(context.Background(), fantasy.Call{}) + if err != nil { + t.Fatal(err) + } + for range stream { + break + } // A second yield would panic under iter.Seq. +} + +func TestProviderOwnerCancelsCreateBeforeRuntimeTransfer(t *testing.T) { + owner := &ProviderOwner{} + entered := make(chan struct{}) + canceled := make(chan struct{}) + release := make(chan struct{}) + returned := make(chan error, 1) + var closed atomic.Int32 + def := ProviderDef{owner: owner, ID: "fake", AllowUnknownModels: true, Create: func(req ProviderCreateRequest) ProviderCreateResult { + close(entered) + <-req.Done + close(canceled) + <-release + return ProviderCreateResult{Runtime: ProviderRuntime{Stream: func(ProviderRequest, func(ProviderEvent) bool) ProviderResult { return ProviderResult{} }, Close: func() error { closed.Add(1); return nil }}} + }} + go func() { + _, _, err := NewProviderModel(context.Background(), def, ProviderCreateRequest{Model: "echo"}) + returned <- err + }() + <-entered + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + err := owner.CloseRuntimes(ctx) + if !errors.Is(err, context.DeadlineExceeded) { + t.Errorf("close=%v", err) + } + <-canceled + close(release) + if err := <-returned; err == nil { + t.Error("runtime published after shutdown") + } + if err := owner.CloseRuntimes(context.Background()); err != nil { + t.Error(err) + } + if closed.Load() != 1 { + t.Errorf("close count=%d", closed.Load()) + } +} diff --git a/internal/extensions/provider_owner.go b/internal/extensions/provider_owner.go new file mode 100644 index 00000000..03f0e0e1 --- /dev/null +++ b/internal/extensions/provider_owner.go @@ -0,0 +1,209 @@ +package extensions + +import ( + "context" + "errors" + "sync" +) + +// ProviderOwner keeps interpreter callbacks alive until all successful runtimes +// are closed. Retirement stops new creates but never invalidates old runtimes. +type ProviderOwner struct { + mu sync.Mutex + active int + retired bool + shuttingDown bool + shutdownScheduled bool + shutdown func() + runtimes map[*providerAdapter]struct{} + calls map[int64]context.CancelFunc + drained chan struct{} +} + +func (o *ProviderOwner) acquire() bool { + if o == nil { + return true + } + o.mu.Lock() + defer o.mu.Unlock() + if o.retired { + return false + } + if o.active == 0 { + o.drained = make(chan struct{}) + } + o.active++ + return true +} +func (o *ProviderOwner) release() { + if o == nil { + return + } + o.mu.Lock() + o.active-- + if o.active == 0 && o.drained != nil { + close(o.drained) + } + var shutdown func() + if o.retired && o.active == 0 { + shutdown = o.shutdown + o.shutdown = nil + } + o.mu.Unlock() + if shutdown != nil { + shutdown() + } +} + +// Retire defers interpreter shutdown until the last runtime has closed. +// It invokes no extension callbacks while holding the owner lock. +func (o *ProviderOwner) Retire(shutdown func()) { + if o == nil { + shutdown() + return + } + o.mu.Lock() + if o.shutdownScheduled { + o.mu.Unlock() + return + } + o.retired = true + o.shutdownScheduled = true + if o.active == 0 { + o.mu.Unlock() + shutdown() + return + } + o.shutdown = shutdown + o.mu.Unlock() +} + +func (o *ProviderOwner) track(runtime *providerAdapter) bool { + if o == nil { + return true + } + o.mu.Lock() + if o.shuttingDown { + o.mu.Unlock() + return false + } + if o.runtimes == nil { + o.runtimes = map[*providerAdapter]struct{}{} + } + o.runtimes[runtime] = struct{}{} + o.mu.Unlock() + return true +} +func (o *ProviderOwner) untrack(runtime *providerAdapter) { + if o == nil { + return + } + o.mu.Lock() + delete(o.runtimes, runtime) + o.mu.Unlock() +} + +// CheckReentrant rejects shutdown or reload from a runtime's active callback. +func (o *ProviderOwner) CheckReentrant() error { + if o == nil { + return nil + } + o.mu.Lock() + defer o.mu.Unlock() + if _, ok := o.calls[goroutineID()]; ok { + return errors.New("cannot retire provider generation from its own callback") + } + for runtime := range o.runtimes { + if runtime.callbackGID.Load() == goroutineID() { + return errors.New("cannot retire provider generation from its own callback") + } + } + return nil +} + +// CloseRuntimes cancels all owned runtimes before waiting for any of them. +// It must not be called synchronously from a provider callback. +func (o *ProviderOwner) CloseRuntimes(ctx context.Context) error { + if o == nil { + return nil + } + if err := o.CancelOwnedWork(); err != nil { + return err + } + o.mu.Lock() + drained := o.drained + runtimes := make([]*providerAdapter, 0, len(o.runtimes)) + for runtime := range o.runtimes { + runtimes = append(runtimes, runtime) + } + o.mu.Unlock() + for _, runtime := range runtimes { + runtime.cancel() + } + var errs []error + for _, runtime := range runtimes { + if err := runtime.closeContext(ctx); err != nil { + errs = append(errs, err) + } + } + if drained != nil { + select { + case <-drained: + case <-ctx.Done(): + errs = append(errs, ctx.Err()) + } + } + return errors.Join(errs...) +} + +func (o *ProviderOwner) callback(ctx context.Context) (context.Context, func(), error) { + ctx, cancel := context.WithCancel(ctx) + if o == nil { + return ctx, cancel, nil + } + gid := goroutineID() + o.mu.Lock() + if o.shuttingDown { + o.mu.Unlock() + cancel() + return nil, nil, errors.New("extension provider generation is shutting down") + } + if o.calls == nil { + o.calls = map[int64]context.CancelFunc{} + } + o.calls[gid] = cancel + o.mu.Unlock() + return ctx, func() { cancel(); o.mu.Lock(); delete(o.calls, gid); o.mu.Unlock() }, nil +} + +// CancelOwnedWork stops all request classes without waiting for callbacks. +func (o *ProviderOwner) CancelOwnedWork() error { + if o == nil { + return nil + } + o.mu.Lock() + if _, ok := o.calls[goroutineID()]; ok { + o.mu.Unlock() + return errors.New("cannot shut down provider generation from its own callback") + } + for runtime := range o.runtimes { + if runtime.callbackGID.Load() == goroutineID() { + o.mu.Unlock() + return errors.New("cannot shut down provider generation from its own callback") + } + } + o.retired = true + o.shuttingDown = true + var cancels []context.CancelFunc + for _, cancel := range o.calls { + cancels = append(cancels, cancel) + } + for runtime := range o.runtimes { + cancels = append(cancels, runtime.cancel) + } + o.mu.Unlock() + for _, cancel := range cancels { + cancel() + } + return nil +} diff --git a/internal/extensions/provider_registration.go b/internal/extensions/provider_registration.go new file mode 100644 index 00000000..8063bcc9 --- /dev/null +++ b/internal/extensions/provider_registration.go @@ -0,0 +1,167 @@ +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "slices" + "strings" + "unicode" +) + +func providerObject(value string) bool { + if value == "" { + return true + } + var object map[string]json.RawMessage + return json.Unmarshal([]byte(value), &object) == nil && object != nil +} + +func freezeCapabilities(c ProviderCapabilities) ProviderCapabilities { + c.InputModalities = slices.Clone(c.InputModalities) + c.ReasoningLevels = slices.Clone(c.ReasoningLevels) + return c +} + +func validProviderName(s string) bool { + if len(s) == 0 || s[0] < 'a' || s[0] > 'z' { + return false + } + for _, c := range s { + valid := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' + if !valid { + return false + } + } + return true +} + +func validateCapabilities(c ProviderCapabilities) error { + for _, modality := range c.InputModalities { + switch modality { + case "text", "image", "audio", "video", "file": + default: + return fmt.Errorf("invalid input modality") + } + } + if !c.Reasoning && len(c.ReasoningLevels) > 0 { + return fmt.Errorf("reasoning levels require reasoning support") + } + for _, level := range c.ReasoningLevels { + if strings.TrimSpace(level) == "" || strings.ContainsFunc(level, unicode.IsControl) || level == "off" { + return fmt.Errorf("invalid reasoning level") + } + } + return nil +} + +func freezeProvider(def ProviderDef) (ProviderDef, error) { + def.ID = strings.ToLower(strings.TrimSpace(def.ID)) + if !validProviderName(def.ID) { + return def, fmt.Errorf("invalid ASCII provider identifier") + } + if strings.TrimSpace(def.Name) == "" || strings.ContainsFunc(def.Name, unicode.IsControl) { + return def, fmt.Errorf("provider name is empty") + } + if def.Create == nil { + return def, fmt.Errorf("create callback is required") + } + if !providerObject(def.SettingsJSON) { + return def, fmt.Errorf("settings must be a JSON object") + } + if err := validateCapabilities(def.UnknownModelDefaults); err != nil { + return def, err + } + switch def.Auth.Mode { + case "", ProviderAuthNone, ProviderAuthAPIKey, ProviderAuthExternal: + default: + return def, fmt.Errorf("invalid auth mode") + } + if def.Auth.Mode == "" { + def.Auth.Mode = ProviderAuthNone + } + for _, variable := range def.Auth.Environment { + if strings.TrimSpace(variable) == "" || strings.ContainsAny(variable, "=\x00\r\n") { + return def, fmt.Errorf("invalid credential environment variable") + } + } + if def.ValidateSettings != nil { + if err := validateProviderSettings(def.ValidateSettings, def.SettingsJSON); err != nil { + return def, err + } + } + def.Auth.Environment = slices.Clone(def.Auth.Environment) + def.UnknownModelDefaults = freezeCapabilities(def.UnknownModelDefaults) + def.Models = slices.Clone(def.Models) + seen := map[string]bool{} + for i, model := range def.Models { + model.ID = strings.TrimSpace(model.ID) + if model.ID == "" || strings.ContainsFunc(model.ID, unicode.IsControl) { + return def, fmt.Errorf("invalid model identifier at index %d", i) + } + if seen[model.ID] { + return def, fmt.Errorf("duplicate model %q", model.ID) + } + seen[model.ID] = true + if strings.TrimSpace(model.Name) == "" || strings.ContainsFunc(model.Name, unicode.IsControl) { + return def, fmt.Errorf("model %q has an empty name", model.ID) + } + if model.ContextLimit < 0 || model.OutputLimit < 0 || (model.ContextLimit > 0 && model.OutputLimit > model.ContextLimit) { + return def, fmt.Errorf("model %q has invalid limits", model.ID) + } + for _, price := range []ProviderPrice{model.InputPrice, model.OutputPrice, model.CacheReadPrice, model.CacheWritePrice} { + if price.Value < 0 || math.IsNaN(price.Value) || math.IsInf(price.Value, 0) { + return def, fmt.Errorf("model %q has invalid pricing", model.ID) + } + } + if def.ValidateSettings != nil && model.SettingsJSON != "" { + if err := validateProviderSettings(def.ValidateSettings, model.SettingsJSON); err != nil { + return def, fmt.Errorf("model %q: %w", model.ID, err) + } + } + if !providerObject(model.SettingsJSON) { + return def, fmt.Errorf("model %q settings must be a JSON object", model.ID) + } + if err := validateCapabilities(model.Capabilities); err != nil { + return def, fmt.Errorf("model %q: %w", model.ID, err) + } + model.Capabilities = freezeCapabilities(model.Capabilities) + def.Models[i] = model + } + return def, nil +} + +func (ext *LoadedExtension) registerProvider(def ProviderDef) { + frozen, err := freezeProvider(def) + if err == nil { + for _, prior := range ext.Providers { + if prior.ID == frozen.ID { + err = fmt.Errorf("duplicate provider registration") + break + } + } + } + if err != nil { + // Do not print invalid raw IDs or configuration supplied by the extension. + ext.RegistrationErrors = append(ext.RegistrationErrors, fmt.Errorf("extension %s provider registration: %w", ext.Path, err)) + return + } + if ext.Owner == nil { + ext.Owner = &ProviderOwner{} + } + frozen.owner = ext.Owner + frozen.authCache = &ProviderAuthCache{} + ext.Providers = append(ext.Providers, frozen) +} + +func validateProviderSettings(validate func(string) *ProviderError, settings string) (err error) { + defer func() { + if recover() != nil { + err = fmt.Errorf("panic in provider settings validation") + } + }() + if validate(settings) != nil { + return fmt.Errorf("provider settings validation failed") + } + return nil +} diff --git a/internal/extensions/provider_registration_test.go b/internal/extensions/provider_registration_test.go new file mode 100644 index 00000000..71859b61 --- /dev/null +++ b/internal/extensions/provider_registration_test.go @@ -0,0 +1,47 @@ +package extensions + +import ( + "math" + "testing" +) + +func TestProviderRegistrationValidation(t *testing.T) { + base := func() ProviderDef { + return ProviderDef{ID: "fake", Name: "Fake", Create: func(ProviderCreateRequest) ProviderCreateResult { return ProviderCreateResult{} }, Models: []ProviderModel{{ID: "echo", Name: "Echo"}}} + } + tests := []struct { + name string + mutate func(*ProviderDef) + }{ + {"id slash", func(d *ProviderDef) { d.ID = "fake/other" }}, + {"id leading digit", func(d *ProviderDef) { d.ID = "1fake" }}, + {"nil create", func(d *ProviderDef) { d.Create = nil }}, + {"empty name", func(d *ProviderDef) { d.Name = " " }}, + {"control name", func(d *ProviderDef) { d.Name = "bad\nname" }}, + {"empty model", func(d *ProviderDef) { d.Models[0].ID = " " }}, + {"duplicate model", func(d *ProviderDef) { d.Models = append(d.Models, d.Models[0]) }}, + {"invalid limits", func(d *ProviderDef) { d.Models[0].ContextLimit = 2; d.Models[0].OutputLimit = 3 }}, + {"negative price", func(d *ProviderDef) { d.Models[0].InputPrice.Value = -1 }}, + {"nan price", func(d *ProviderDef) { d.Models[0].InputPrice.Value = math.NaN() }}, + {"nonobject settings", func(d *ProviderDef) { d.SettingsJSON = `[]` }}, + {"malformed settings", func(d *ProviderDef) { d.SettingsJSON = `{` }}, + {"invalid modality", func(d *ProviderDef) { d.Models[0].Capabilities.InputModalities = []string{"hologram"} }}, + {"reasoning levels without reasoning", func(d *ProviderDef) { d.Models[0].Capabilities.ReasoningLevels = []string{"high"} }}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + def := base() + test.mutate(&def) + if _, err := freezeProvider(def); err == nil { + t.Fatal("invalid registration accepted") + } + }) + } + valid := base() + valid.ID = "FAKE" + valid.Models[0].ID = "path/model" + valid.Models[0].InputPrice = ProviderPrice{Known: true, Value: 0} + if _, err := freezeProvider(valid); err != nil { + t.Fatal(err) + } +} diff --git a/internal/extensions/provider_retry_test.go b/internal/extensions/provider_retry_test.go new file mode 100644 index 00000000..ebde0969 --- /dev/null +++ b/internal/extensions/provider_retry_test.go @@ -0,0 +1,33 @@ +package extensions + +import ( + "context" + "errors" + "testing" + + "charm.land/fantasy" +) + +func TestProviderRetryOnlyBeforeExposure(t *testing.T) { + for _, kind := range []ProviderEventType{"", ProviderTextStart, ProviderReasoningStart, ProviderToolStart} { + t.Run(string(kind), func(t *testing.T) { + runtime := ProviderRuntime{Stream: func(_ ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + if kind != "" { + emit(ProviderEvent{Type: kind, ID: "block", ToolName: "add"}) + if kind == ProviderTextStart { + emit(ProviderEvent{Type: ProviderTextDelta, ID: "block", Delta: "visible"}) + } + } + return ProviderResult{Error: &ProviderError{Code: "transport", Retryable: true}} + }} + err := runProviderStream(context.Background(), runtime, ProviderRequest{Tools: []ProviderTool{{Name: "add"}}}, func(fantasy.StreamPart) bool { return true }) + var classified interface{ ExtensionRetryable() bool } + if !errors.As(err, &classified) { + t.Fatalf("unclassified %v", err) + } + if classified.ExtensionRetryable() != (kind == "") { + t.Fatalf("retryable=%v kind=%s", classified.ExtensionRetryable(), kind) + } + }) + } +} diff --git a/internal/extensions/provider_stream.go b/internal/extensions/provider_stream.go new file mode 100644 index 00000000..e69514f2 --- /dev/null +++ b/internal/extensions/provider_stream.go @@ -0,0 +1,289 @@ +package extensions + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync" + "sync/atomic" + + "charm.land/fantasy" + "github.com/kaptinlin/jsonschema" + "github.com/mark3labs/kit/internal/message" +) + +// providerStreamError is also consumed by the agent retry layer via a private +// behavioral interface. Never replay after any visible content or tool event. +type providerStreamError struct { + cause error + code string + retryable bool + exposed bool +} + +func (e *providerStreamError) Error() string { return e.cause.Error() } +func (e *providerStreamError) Unwrap() error { return e.cause } +func (e *providerStreamError) ExtensionRetryable() bool { return e.retryable && !e.exposed } +func (e *providerStreamError) ExtensionErrorCode() string { return e.code } + +func usageSnapshot(u ProviderUsage) fantasy.Usage { + return fantasy.Usage{InputTokens: u.InputTokens, OutputTokens: u.OutputTokens, TotalTokens: u.TotalTokens, ReasoningTokens: u.ReasoningTokens, CacheCreationTokens: u.CacheCreationTokens, CacheReadTokens: u.CacheReadTokens} +} +func monotonicUsage(prior, next ProviderUsage) bool { + return next.InputTokens >= prior.InputTokens && next.OutputTokens >= prior.OutputTokens && next.TotalTokens >= prior.TotalTokens && next.ReasoningTokens >= prior.ReasoningTokens && next.CacheCreationTokens >= prior.CacheCreationTokens && next.CacheReadTokens >= prior.CacheReadTokens +} + +type providerBlock struct { + kind ProviderEventType + name string + args string + open bool +} + +// runProviderStream executes on the consumer goroutine. It has no event queue +// or producer goroutine. Only final executable calls and finish are buffered. +func runProviderStream(ctx context.Context, runtime ProviderRuntime, request ProviderRequest, yield func(fantasy.StreamPart) bool) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + request.Done = ctx.Done() + var emitting atomic.Bool + var stopped atomic.Bool + var mu sync.Mutex + var first error + setError := func(err error) { + mu.Lock() + if first == nil { + first = err + } + mu.Unlock() + stopped.Store(true) + cancel() + } + getError := func() error { mu.Lock(); defer mu.Unlock(); return first } + blocks := map[string]*providerBlock{} + var calls []fantasy.StreamPart + var usage ProviderUsage + var finish fantasy.FinishReason + var exposed bool + var finishMetadata fantasy.ProviderMetadata + var blockOrder []string + send := func(part fantasy.StreamPart) bool { + if !yield(part) { + setError(context.Canceled) + return false + } + return true + } + protocol := func() bool { setError(errors.New("extension provider stream protocol violation")); return false } + emit := func(event ProviderEvent) bool { + if stopped.Load() { + return false + } + if !emitting.CompareAndSwap(false, true) { + return protocol() + } + defer emitting.Store(false) + if ctx.Err() != nil { + setError(ctx.Err()) + return false + } + if finish != "" { + return protocol() + } + if event.Signature != "" && event.Type != ProviderReasoningEnd { + return protocol() + } + if len(event.Replay) > 0 && event.Type != ProviderTextEnd && event.Type != ProviderReasoningEnd && event.Type != ProviderToolEnd && event.Type != ProviderFinish { + return protocol() + } + if event.ToolName != "" && event.Type != ProviderToolStart { + return protocol() + } + if event.FinishReason != "" && event.Type != ProviderFinish { + return protocol() + } + if event.Usage != (ProviderUsage{}) && event.Type != ProviderUsageEvent { + return protocol() + } + if (event.Type == ProviderUsageEvent || event.Type == ProviderFinish) && (event.ID != "" || event.Delta != "") { + return protocol() + } + metadata := &message.ExtensionMetadata{Provider: request.Provider, Signature: event.Signature, BlockID: event.ID} + for _, replay := range event.Replay { + if replay.Version != 1 || replay.Provider != request.Provider || !json.Valid([]byte(replay.JSON)) { + setError(errors.New("unsupported extension replay metadata")) + return false + } + metadata.Replay = append(metadata.Replay, message.ReplayData{Version: replay.Version, Provider: replay.Provider, JSON: replay.JSON}) + } + options := fantasy.ProviderMetadata{"kit-extension": metadata} + if event.Type == ProviderUsageEvent { + if !monotonicUsage(usage, event.Usage) { + return protocol() + } + usage = event.Usage + return true + } + if event.Type == ProviderFinish { + metadata.Order = append([]string(nil), blockOrder...) + finishMetadata = options + for _, block := range blocks { + if block.open { + return protocol() + } + } + switch event.FinishReason { + case ProviderFinishStop: + finish = fantasy.FinishReasonStop + case ProviderFinishLength: + finish = fantasy.FinishReasonLength + case ProviderFinishToolCalls: + finish = fantasy.FinishReasonToolCalls + default: + return protocol() + } + if (len(calls) > 0) != (event.FinishReason == ProviderFinishToolCalls) { + return protocol() + } + if (request.ToolChoice.Type == ProviderToolRequired || request.ToolChoice.Type == ProviderToolNamed) && len(calls) == 0 { + return protocol() + } + return true + } + if event.ID == "" { + return protocol() + } + block := blocks[event.ID] + part := fantasy.StreamPart{ID: event.ID, Delta: event.Delta, ProviderMetadata: options} + switch event.Type { + case ProviderTextStart, ProviderReasoningStart, ProviderToolStart: + if block != nil || event.Delta != "" { + return protocol() + } + block = &providerBlock{kind: event.Type, name: event.ToolName, open: true} + blocks[event.ID] = block + blockOrder = append(blockOrder, event.ID) + switch event.Type { + case ProviderTextStart: + part.Type = fantasy.StreamPartTypeTextStart + case ProviderReasoningStart: + part.Type = fantasy.StreamPartTypeReasoningStart + exposed = true + case ProviderToolStart: + found := false + for _, tool := range request.Tools { + if tool.Name == event.ToolName { + found = true + } + } + if !found || request.ToolChoice.Type == ProviderToolNone || (request.ToolChoice.Type == ProviderToolNamed && request.ToolChoice.Name != event.ToolName) { + return protocol() + } + part.Type = fantasy.StreamPartTypeToolInputStart + part.ToolCallName = event.ToolName + exposed = true + } + case ProviderTextDelta, ProviderReasoningDelta, ProviderToolDelta, ProviderTextEnd, ProviderReasoningEnd, ProviderToolEnd: + if block == nil || !block.open { + return protocol() + } + switch event.Type { + case ProviderTextDelta: + if block.kind != ProviderTextStart { + return protocol() + } + part.Type = fantasy.StreamPartTypeTextDelta + exposed = exposed || event.Delta != "" + case ProviderReasoningDelta: + if block.kind != ProviderReasoningStart { + return protocol() + } + part.Type = fantasy.StreamPartTypeReasoningDelta + exposed = exposed || event.Delta != "" + case ProviderToolDelta: + if block.kind != ProviderToolStart { + return protocol() + } + part.Type = fantasy.StreamPartTypeToolInputDelta + block.args += event.Delta + exposed = true + case ProviderTextEnd: + if block.kind != ProviderTextStart || event.Delta != "" { + return protocol() + } + part.Type = fantasy.StreamPartTypeTextEnd + block.open = false + case ProviderReasoningEnd: + if block.kind != ProviderReasoningStart || event.Delta != "" { + return protocol() + } + part.Type = fantasy.StreamPartTypeReasoningEnd + block.open = false + case ProviderToolEnd: + if block.kind != ProviderToolStart || event.Delta != "" { + return protocol() + } + var object map[string]json.RawMessage + if json.Unmarshal([]byte(block.args), &object) != nil || object == nil { + return protocol() + } + for _, tool := range request.Tools { + if tool.Name == block.name && tool.SchemaJSON != "" { + compiler := jsonschema.NewCompiler() + clear(compiler.Loaders) + schema, err := compiler.Compile([]byte(tool.SchemaJSON)) + if err != nil || !schema.ValidateJSON([]byte(block.args)).IsValid() { + return protocol() + } + } + } + block.open = false + part.Type = fantasy.StreamPartTypeToolInputEnd + calls = append(calls, fantasy.StreamPart{Type: fantasy.StreamPartTypeToolCall, ID: event.ID, ToolCallName: block.name, ToolCallInput: block.args, ProviderMetadata: options}) + } + default: + return protocol() + } + return send(part) + } + var result ProviderResult + func() { + defer func() { + if recover() != nil { + setError(errors.New("panic in extension provider Stream")) + } + }() + result = runtime.Stream(request, emit) + }() + stopped.Store(true) + if err := getError(); err != nil { + return &providerStreamError{cause: err, exposed: exposed} + } + if ctx.Err() != nil { + return &providerStreamError{cause: ctx.Err(), exposed: exposed} + } + if result.Error != nil { + if result.Error.Code == "canceled" { + return &providerStreamError{cause: context.Canceled, exposed: exposed} + } + if result.Error.Code == "deadline" { + return &providerStreamError{cause: context.DeadlineExceeded, exposed: exposed} + } + // The adapter redacts host-resolved credentials before this boundary. + retryable := result.Error.Retryable && (result.Error.Code == "transport" || result.Error.Code == "rate_limit") + return &providerStreamError{cause: fmt.Errorf("extension provider %s/%s: %s", request.Provider, request.Model, result.Error.Message), code: result.Error.Code, retryable: retryable, exposed: exposed} + } + if finish == "" { + return &providerStreamError{cause: errors.New("extension provider stream missing finish"), exposed: exposed} + } + for _, call := range calls { + if !send(call) { + return getError() + } + } + if !send(fantasy.StreamPart{Type: fantasy.StreamPartTypeFinish, FinishReason: finish, Usage: usageSnapshot(usage), ProviderMetadata: finishMetadata}) { + return getError() + } + return nil +} diff --git a/internal/extensions/provider_stream_test.go b/internal/extensions/provider_stream_test.go new file mode 100644 index 00000000..3dd71d1f --- /dev/null +++ b/internal/extensions/provider_stream_test.go @@ -0,0 +1,107 @@ +package extensions + +import ( + "context" + "errors" + "testing" + + "charm.land/fantasy" +) + +func TestProviderStreamProtocol(t *testing.T) { + text := []ProviderEvent{{Type: ProviderTextStart, ID: "t"}, {Type: ProviderTextDelta, ID: "t", Delta: "hello"}, {Type: ProviderTextEnd, ID: "t"}, {Type: ProviderFinish, FinishReason: ProviderFinishStop}} + tools := []ProviderEvent{{Type: ProviderToolStart, ID: "call/1", ToolName: "add"}, {Type: ProviderToolDelta, ID: "call/1", Delta: `{"a":2,`}, {Type: ProviderToolDelta, ID: "call/1", Delta: `"b":3}`}, {Type: ProviderToolEnd, ID: "call/1"}, {Type: ProviderFinish, FinishReason: ProviderFinishToolCalls}} + tests := []struct { + name string + events []ProviderEvent + result ProviderResult + valid bool + executable int + }{ + {name: "greeting", events: text, valid: true}, + {name: "signature only reasoning", events: []ProviderEvent{{Type: ProviderReasoningStart, ID: "r"}, {Type: ProviderReasoningEnd, ID: "r", Signature: "signature"}, {Type: ProviderFinish, FinishReason: ProviderFinishStop}}, valid: true}, + {name: "empty", events: []ProviderEvent{{Type: ProviderFinish, FinishReason: ProviderFinishStop}}, valid: true}, + {name: "tools", events: tools, valid: true, executable: 1}, + {name: "delta before start", events: text[1:]}, + {name: "missing finish", events: text[:3]}, + {name: "post finish", events: append(append([]ProviderEvent{}, text...), ProviderEvent{Type: ProviderTextStart, ID: "x"})}, + {name: "repeated finish", events: []ProviderEvent{{Type: ProviderFinish, FinishReason: ProviderFinishStop}, {Type: ProviderFinish, FinishReason: ProviderFinishStop}}}, + {name: "duplicate block", events: []ProviderEvent{{Type: ProviderTextStart, ID: "t"}, {Type: ProviderTextStart, ID: "t"}}}, + {name: "wrong block", events: []ProviderEvent{{Type: ProviderTextStart, ID: "t"}, {Type: ProviderToolDelta, ID: "t"}}}, + {name: "open block", events: []ProviderEvent{{Type: ProviderTextStart, ID: "t"}, {Type: ProviderFinish, FinishReason: ProviderFinishStop}}}, + {name: "unknown tool", events: []ProviderEvent{{Type: ProviderToolStart, ID: "c", ToolName: "hidden"}}}, + {name: "invalid arguments", events: []ProviderEvent{{Type: ProviderToolStart, ID: "c", ToolName: "add"}, {Type: ProviderToolDelta, ID: "c", Delta: `[]`}, {Type: ProviderToolEnd, ID: "c"}}}, + {name: "late error", events: tools, result: ProviderResult{Error: &ProviderError{Code: "transport", Retryable: true}}}, + {name: "usage decreases", events: []ProviderEvent{{Type: ProviderUsageEvent, Usage: ProviderUsage{InputTokens: 2}}, {Type: ProviderUsageEvent, Usage: ProviderUsage{InputTokens: 1}}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + runtime := ProviderRuntime{Stream: func(_ ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + for _, event := range tt.events { + emit(event) + } + return tt.result + }} + var got []fantasy.StreamPart + err := runProviderStream(context.Background(), runtime, ProviderRequest{Tools: []ProviderTool{{Name: "add"}}}, func(p fantasy.StreamPart) bool { got = append(got, p); return true }) + if (err == nil) != tt.valid { + t.Fatalf("err=%v valid=%v", err, tt.valid) + } + calls, finishes := 0, 0 + for _, part := range got { + if part.Type == fantasy.StreamPartTypeToolCall { + calls++ + } + if part.Type == fantasy.StreamPartTypeFinish { + finishes++ + } + } + if calls != tt.executable || (!tt.valid && finishes != 0) { + t.Fatalf("executable=%d finish=%d", calls, finishes) + } + }) + } +} + +func TestProviderStreamConcurrentEmitRejected(t *testing.T) { + entered := make(chan struct{}) + release := make(chan struct{}) + runtime := ProviderRuntime{Stream: func(_ ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + done := make(chan struct{}) + go func() { defer close(done); emit(ProviderEvent{Type: ProviderTextStart, ID: "one"}) }() + <-entered + if emit(ProviderEvent{Type: ProviderTextStart, ID: "two"}) { + t.Error("concurrent emit accepted") + } + close(release) + <-done + return ProviderResult{} + }} + err := runProviderStream(context.Background(), runtime, ProviderRequest{}, func(fantasy.StreamPart) bool { close(entered); <-release; return true }) + if err == nil { + t.Fatal("concurrent emit did not fail") + } +} + +func TestProviderStreamConsumerStopCancelsDone(t *testing.T) { + observed := false + runtime := ProviderRuntime{Stream: func(req ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + if emit(ProviderEvent{Type: ProviderTextStart, ID: "t"}) { + t.Error("consumer stop ignored") + } + select { + case <-req.Done: + observed = true + default: + t.Error("Done not canceled") + } + if emit(ProviderEvent{Type: ProviderFinish, FinishReason: ProviderFinishStop}) { + t.Error("accepted after stop") + } + return ProviderResult{} + }} + err := runProviderStream(context.Background(), runtime, ProviderRequest{}, func(fantasy.StreamPart) bool { return false }) + if !errors.Is(err, context.Canceled) || !observed { + t.Fatalf("err=%v observed=%v", err, observed) + } +} diff --git a/internal/extensions/provider_symbols_test.go b/internal/extensions/provider_symbols_test.go new file mode 100644 index 00000000..39e131e8 --- /dev/null +++ b/internal/extensions/provider_symbols_test.go @@ -0,0 +1,36 @@ +package extensions + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" +) + +func TestEveryProviderDeclarationHasInterpreterSymbol(t *testing.T) { + file, err := parser.ParseFile(token.NewFileSet(), "providers.go", nil, 0) + if err != nil { + t.Fatal(err) + } + symbols := Symbols()["kit/ext/ext"] + for _, declaration := range file.Decls { + group, ok := declaration.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range group.Specs { + switch value := spec.(type) { + case *ast.TypeSpec: + if _, ok := symbols[value.Name.Name]; !ok { + t.Errorf("missing type %s", value.Name.Name) + } + case *ast.ValueSpec: + for _, name := range value.Names { + if _, ok := symbols[name.Name]; !ok { + t.Errorf("missing constant %s", name.Name) + } + } + } + } + } +} diff --git a/internal/extensions/provider_usage_test.go b/internal/extensions/provider_usage_test.go new file mode 100644 index 00000000..44a6f248 --- /dev/null +++ b/internal/extensions/provider_usage_test.go @@ -0,0 +1,32 @@ +package extensions + +import ( + "context" + "testing" + + "charm.land/fantasy" +) + +func TestProviderCumulativeUsageCountedOnce(t *testing.T) { + runtime := ProviderRuntime{Stream: func(_ ProviderRequest, emit func(ProviderEvent) bool) ProviderResult { + emit(ProviderEvent{Type: ProviderUsageEvent, Usage: ProviderUsage{InputTokens: 10, OutputTokens: 1, CacheReadTokens: 3}}) + emit(ProviderEvent{Type: ProviderUsageEvent, Usage: ProviderUsage{InputTokens: 10, OutputTokens: 2, CacheReadTokens: 3}}) + emit(ProviderEvent{Type: ProviderFinish, FinishReason: ProviderFinishStop}) + return ProviderResult{} + }} + finishes := 0 + err := runProviderStream(context.Background(), runtime, ProviderRequest{}, func(part fantasy.StreamPart) bool { + if part.Type == fantasy.StreamPartTypeFinish { + finishes++ + if part.Usage.InputTokens != 10 || part.Usage.OutputTokens != 2 || part.Usage.CacheReadTokens != 3 { + t.Errorf("usage=%+v", part.Usage) + } + } else if part.Usage != (fantasy.Usage{}) { + t.Error("usage forwarded outside finish") + } + return true + }) + if err != nil || finishes != 1 { + t.Fatalf("err=%v finishes=%d", err, finishes) + } +} diff --git a/internal/extensions/providers.go b/internal/extensions/providers.go new file mode 100644 index 00000000..84ba5904 --- /dev/null +++ b/internal/extensions/providers.go @@ -0,0 +1,266 @@ +package extensions + +// ProviderDef declares an instance-owned model provider. Create must not execute +// tools. Only Kit executes tools, using the definitions exposed in each request. +// Callbacks are trusted cooperative code: observe Done and join all emitters +// before returning. A successful Create transfers runtime ownership to Kit. +type ProviderDef struct { + owner *ProviderOwner + authCache *ProviderAuthCache + ID string + Name string + Models []ProviderModel + Auth ProviderAuth + AllowUnknownModels bool + UnknownModelDefaults ProviderCapabilities + SettingsJSON string + ValidateSettings func(string) *ProviderError + Create func(ProviderCreateRequest) ProviderCreateResult +} + +// ProviderCapabilities is the authoritative capability declaration for a model. +type ProviderCapabilities struct { + InputModalities []string + Tools bool + Streaming bool + Reasoning bool + ReasoningLevels []string +} + +// ProviderModel describes a selectable model. Limits of zero are unknown. +// Prices are USD per million tokens; Known distinguishes absent and free. +type ProviderModel struct { + ID string + Name string + ContextLimit int64 + OutputLimit int64 + Capabilities ProviderCapabilities + InputPrice ProviderPrice + OutputPrice ProviderPrice + CacheReadPrice ProviderPrice + CacheWritePrice ProviderPrice + SettingsJSON string +} + +// ProviderPrice distinguishes an unknown price from an explicit zero. +type ProviderPrice struct { + Known bool + Value float64 +} + +// ProviderAuth declares credential resolution, not an OAuth login flow. +type ProviderAuth struct { + Mode ProviderAuthMode + Environment []string + SetupInstruction string + Status func(ProviderAuthRequest) ProviderAuthResult +} + +// ProviderAuthMode identifies how the extension obtains credentials. +type ProviderAuthMode string + +const ( + ProviderAuthNone ProviderAuthMode = "none" + ProviderAuthAPIKey ProviderAuthMode = "api_key" + ProviderAuthExternal ProviderAuthMode = "external" +) + +// ProviderAuthRequest bounds an optional safe status check. +type ProviderAuthRequest struct{ Done <-chan struct{} } + +// ProviderAuthResult never contains a credential. +type ProviderAuthResult struct { + Status ProviderAuthStatus + Instruction string + Error *ProviderError +} + +// ProviderAuthStatus identifies safe picker availability. +type ProviderAuthStatus string + +const ( + ProviderAuthConfigured ProviderAuthStatus = "configured" + ProviderAuthUnconfigured ProviderAuthStatus = "unconfigured" + ProviderAuthUnknown ProviderAuthStatus = "unknown" +) + +// ProviderCreateRequest contains transient credentials. Never log or persist it. +type ProviderCreateRequest struct { + Provider string + Model string + Parameters ProviderParameters + Endpoint string + APIKey string + UserAgent string + Done <-chan struct{} +} + +// ProviderCreateResult has exactly one terminal error path. On failure, the +// extension owns cleanup of partial resources; on success Kit owns Runtime. +type ProviderCreateResult struct { + Runtime ProviderRuntime + Error *ProviderError +} + +// ProviderRuntime is one model runtime. Calls on it are serialized by Kit; +// separate runtimes can be called concurrently. Close is called at most once. +type ProviderRuntime struct { + Stream func(ProviderRequest, func(ProviderEvent) bool) ProviderResult + Close func() error +} + +// ProviderResult is returned after Stream has stopped all emitters. +type ProviderResult struct{ Error *ProviderError } + +// ProviderError carries safe diagnostic text, an optional code and retry policy. +// Never include credentials, full requests, or sensitive configuration in Message. +type ProviderError struct { + Code string + Message string + Retryable bool +} + +// ProviderParameters preserves explicit zeros with pointers. +type ProviderParameters struct { + MaxOutputTokens *int64 + Temperature *float64 + TopP *float64 + TopK *int64 + PresencePenalty *float64 + FrequencyPenalty *float64 + StopSequences []string + ReasoningLevel string + SettingsJSON string +} + +// ProviderRequest contains the selected branch's history and exposed schemas, +// never executable tools or a runtime Context. Emit is synchronous; after false +// it must not be called again. Usage snapshots are cumulative per request. +type ProviderRequest struct { + Provider string + Model string + Messages []ProviderMessage + Tools []ProviderTool + ToolChoice ProviderToolChoice + Parameters ProviderParameters + Done <-chan struct{} +} + +// ProviderTool is a JSON object schema, not an executable callback. +type ProviderTool struct { + Name string + Description string + SchemaJSON string +} + +// ProviderToolChoice selects auto, none, required, or a named exposed tool. +type ProviderToolChoice struct { + Type ProviderToolChoiceType + Name string +} + +// ProviderToolChoiceType is the tool selection policy. +type ProviderToolChoiceType string + +const ( + ProviderToolAuto ProviderToolChoiceType = "auto" + ProviderToolNone ProviderToolChoiceType = "none" + ProviderToolRequired ProviderToolChoiceType = "required" + ProviderToolNamed ProviderToolChoiceType = "named" +) + +// ProviderReplayData preserves opaque valid JSON under its originating provider. +// Version 1 is supported. Do not put credentials in replay data. +type ProviderReplayData struct { + Version int + Provider string + JSON string +} + +// ProviderMessage preserves ordered concrete parts. +type ProviderMessage struct { + Role string + Parts []ProviderPart + Replay []ProviderReplayData +} + +// ProviderPart is a tagged record. CallID is exact and must not be normalized. +type ProviderPart struct { + Type ProviderPartType + Text string + Signature string + CallID string + ToolName string + ArgumentsJSON string + IsError bool + MIMEType string + Filename string + Data []byte + Replay []ProviderReplayData +} + +// ProviderPartType identifies a concrete history part. +type ProviderPartType string + +const ( + ProviderPartText ProviderPartType = "text" + ProviderPartReasoning ProviderPartType = "reasoning" + ProviderPartFile ProviderPartType = "file" + ProviderPartToolCall ProviderPartType = "tool_call" + ProviderPartToolResult ProviderPartType = "tool_result" +) + +// ProviderEvent is one protocol event. Tool-end has no full argument payload: +// Kit assembles argument deltas. Finish and executable calls are withheld until +// Stream returns successfully, so failed protocols cannot execute tools. +type ProviderEvent struct { + Type ProviderEventType + ID string + Delta string + ToolName string + Signature string + Replay []ProviderReplayData + Usage ProviderUsage + FinishReason ProviderFinishReason +} + +// ProviderEventType identifies a stream state transition. +type ProviderEventType string + +const ( + ProviderTextStart ProviderEventType = "text_start" + ProviderTextDelta ProviderEventType = "text_delta" + ProviderTextEnd ProviderEventType = "text_end" + ProviderReasoningStart ProviderEventType = "reasoning_start" + ProviderReasoningDelta ProviderEventType = "reasoning_delta" + ProviderReasoningEnd ProviderEventType = "reasoning_end" + ProviderToolStart ProviderEventType = "tool_start" + ProviderToolDelta ProviderEventType = "tool_delta" + ProviderToolEnd ProviderEventType = "tool_end" + ProviderUsageEvent ProviderEventType = "usage" + ProviderFinish ProviderEventType = "finish" +) + +// ProviderFinishReason identifies successful termination. +type ProviderFinishReason string + +const ( + ProviderFinishStop ProviderFinishReason = "stop" + ProviderFinishLength ProviderFinishReason = "length" + ProviderFinishToolCalls ProviderFinishReason = "tool_calls" +) + +// ProviderUsage is a nonnegative, nondecreasing cumulative snapshot. Kit counts +// the final snapshot once. Cache tokens use the same units as other token counts. +type ProviderUsage struct { + InputTokens int64 + OutputTokens int64 + TotalTokens int64 + ReasoningTokens int64 + CacheCreationTokens int64 + CacheReadTokens int64 +} + +// RegisterProvider registers metadata and creation callbacks with this load. +// Invalid registrations invalidate the candidate even if Init ignores them. +func (a *API) RegisterProvider(def ProviderDef) { a.registerProviderFn(def) } diff --git a/internal/extensions/providers_test.go b/internal/extensions/providers_test.go new file mode 100644 index 00000000..cbad3a52 --- /dev/null +++ b/internal/extensions/providers_test.go @@ -0,0 +1,95 @@ +package extensions + +import ( + "strings" + "sync" + "testing" +) + +const providerFixture = `package main +import ext "kit/ext" +func create(req ext.ProviderCreateRequest) ext.ProviderCreateResult { + return ext.ProviderCreateResult{Runtime: ext.ProviderRuntime{ + Stream: func(req ext.ProviderRequest, emit func(ext.ProviderEvent) bool) ext.ProviderResult { + select { case <-req.Done: return ext.ProviderResult{Error: &ext.ProviderError{Code: "canceled"}}; default: } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextStart, ID: "text"}) { return ext.ProviderResult{} } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextDelta, ID: "text", Delta: "Hello from a fake provider."}) { return ext.ProviderResult{} } + if !emit(ext.ProviderEvent{Type: ext.ProviderTextEnd, ID: "text"}) { return ext.ProviderResult{} } + emit(ext.ProviderEvent{Type: ext.ProviderFinish, FinishReason: ext.ProviderFinishStop}) + return ext.ProviderResult{} + }, + Close: func() error { return nil }, + }} +} +func Init(api ext.API) { + api.RegisterProvider(ext.ProviderDef{ID: "FAKE", Name: "Fake", Create: create, + Models: []ext.ProviderModel{{ID:"echo",Name:"Echo",Capabilities: ext.ProviderCapabilities{Streaming:true}}}, + }) +}` + +func TestProviderProductionLoaderCallbacks(t *testing.T) { + loaded, err := LoadExtensionsScoped([]string{writeExt(t, providerFixture)}, true) + if err != nil { + t.Fatal(err) + } + if len(loaded) != 1 || len(loaded[0].Providers) != 1 { + t.Fatalf("registrations = %#v", loaded) + } + def := loaded[0].Providers[0] + if def.ID != "fake" { + t.Fatalf("ID = %q", def.ID) + } + var wg sync.WaitGroup + for range 8 { + runtime := def.Create(ProviderCreateRequest{}).Runtime + wg.Go(func() { + for range 100 { + text := "" + result := runtime.Stream(ProviderRequest{}, func(event ProviderEvent) bool { text += event.Delta; return true }) + if result.Error != nil || text != "Hello from a fake provider." { + t.Errorf("text=%q error=%v", text, result.Error) + } + } + if err := runtime.Close(); err != nil { + t.Error(err) + } + }) + } + wg.Wait() +} + +func TestProviderRegistrationFreezesMetadata(t *testing.T) { + def := ProviderDef{ID: "Fake", Name: "Fake", Create: func(ProviderCreateRequest) ProviderCreateResult { return ProviderCreateResult{} }, Models: []ProviderModel{{ID: "a/b", Name: "AB", Capabilities: ProviderCapabilities{InputModalities: []string{"text"}}}}} + frozen, err := freezeProvider(def) + if err != nil { + t.Fatal(err) + } + def.Models[0].Capabilities.InputModalities[0] = "image" + def.Models[0].ID = "changed" + if frozen.Models[0].ID != "a/b" || frozen.Models[0].Capabilities.InputModalities[0] != "text" { + t.Fatal("registration shares caller metadata") + } +} + +func TestInvalidProviderRegistrationFailsBulkLoad(t *testing.T) { + path := writeExt(t, strings.Replace(providerFixture, `ID: "FAKE"`, `ID: "not/a/provider"`, 1)) + loaded, err := LoadExtensionsScoped([]string{path}, true) + if err == nil || len(loaded) != 0 { + t.Fatalf("loaded=%d err=%v", len(loaded), err) + } + if !strings.Contains(err.Error(), path) { + t.Fatalf("missing owner: %v", err) + } +} + +func TestStrictProviderCandidateRetainsCompileFailures(t *testing.T) { + bad := writeExt(t, "package main\nfunc invalid syntax") + good := writeExt(t, providerFixture) + result := LoadExtensionsWithDiagnostics([]string{bad, good}, true) + if len(result.Diagnostics) != 1 || len(result.Extensions) != 1 { + t.Fatalf("diagnostics=%v count=%d", result.Diagnostics, len(result.Extensions)) + } + if loaded, err := LoadExtensionsStrict([]string{bad, good}, true); err == nil || loaded != nil { + t.Fatalf("strict loaded=%v err=%v", loaded, err) + } +} diff --git a/internal/extensions/runner.go b/internal/extensions/runner.go index f7f511c7..00343620 100644 --- a/internal/extensions/runner.go +++ b/internal/extensions/runner.go @@ -143,6 +143,9 @@ type ShortcutEntry struct { // loaded, and initialised. It holds the registered handlers and any custom // tools, commands, or tool renderers the extension provided. type LoadedExtension struct { + Owner *ProviderOwner + Providers []ProviderDef + RegistrationErrors []error Path string Handlers map[EventType][]HandlerFunc Tools []ToolDef @@ -571,7 +574,13 @@ func (r *Runner) RegisteredCommands() []CommandDef { // Extensions returns the loaded extensions for inspection (e.g. CLI list). func (r *Runner) Extensions() []LoadedExtension { - return r.extensions + r.mu.RLock() + defer r.mu.RUnlock() + result := slices.Clone(r.extensions) + for i := range result { + result[i].Providers = slices.Clone(result[i].Providers) + } + return result } // --------------------------------------------------------------------------- @@ -1076,8 +1085,9 @@ func (r *Runner) SaveStateToFile(path string) error { // disabled tools, custom event subscriptions). Option overrides are // preserved across reloads since they represent user intent. // -// The caller is responsible for emitting SessionShutdown before calling -// Reload and SessionStart after. +// The caller validates the whole candidate first, retires old provider owners, +// and emits SessionStart after publication. Provider owners can defer shutdown +// until their last runtime closes; ordinary extensions shut down immediately. func (r *Runner) Reload(exts []LoadedExtension) { // Build the new mutex slice before taking the lock; init() on a fresh // reentrantMu cannot block. diff --git a/internal/extensions/symbols.go b/internal/extensions/symbols.go index 105e9f46..9bc5934e 100644 --- a/internal/extensions/symbols.go +++ b/internal/extensions/symbols.go @@ -3,7 +3,7 @@ package extensions import ( "reflect" - "github.com/traefik/yaegi/interp" + "github.com/mark3labs/kit/internal/yaegi/interp" ) // Symbols returns the Yaegi export table that makes KIT's extension API @@ -31,6 +31,64 @@ import ( func Symbols() interp.Exports { return interp.Exports{ "kit/ext/ext": map[string]reflect.Value{ + // Concrete provider protocol types and typed constants. + "ProviderDef": reflect.ValueOf((*ProviderDef)(nil)), + "ProviderCapabilities": reflect.ValueOf((*ProviderCapabilities)(nil)), + "ProviderModel": reflect.ValueOf((*ProviderModel)(nil)), + "ProviderPrice": reflect.ValueOf((*ProviderPrice)(nil)), + "ProviderAuth": reflect.ValueOf((*ProviderAuth)(nil)), + "ProviderAuthMode": reflect.ValueOf((*ProviderAuthMode)(nil)), + "ProviderAuthRequest": reflect.ValueOf((*ProviderAuthRequest)(nil)), + "ProviderAuthResult": reflect.ValueOf((*ProviderAuthResult)(nil)), + "ProviderAuthStatus": reflect.ValueOf((*ProviderAuthStatus)(nil)), + "ProviderCreateRequest": reflect.ValueOf((*ProviderCreateRequest)(nil)), + "ProviderCreateResult": reflect.ValueOf((*ProviderCreateResult)(nil)), + "ProviderRuntime": reflect.ValueOf((*ProviderRuntime)(nil)), + "ProviderResult": reflect.ValueOf((*ProviderResult)(nil)), + "ProviderError": reflect.ValueOf((*ProviderError)(nil)), + "ProviderParameters": reflect.ValueOf((*ProviderParameters)(nil)), + "ProviderRequest": reflect.ValueOf((*ProviderRequest)(nil)), + "ProviderTool": reflect.ValueOf((*ProviderTool)(nil)), + "ProviderToolChoice": reflect.ValueOf((*ProviderToolChoice)(nil)), + "ProviderToolChoiceType": reflect.ValueOf((*ProviderToolChoiceType)(nil)), + "ProviderReplayData": reflect.ValueOf((*ProviderReplayData)(nil)), + "ProviderMessage": reflect.ValueOf((*ProviderMessage)(nil)), + "ProviderPart": reflect.ValueOf((*ProviderPart)(nil)), + "ProviderPartType": reflect.ValueOf((*ProviderPartType)(nil)), + "ProviderEvent": reflect.ValueOf((*ProviderEvent)(nil)), + "ProviderEventType": reflect.ValueOf((*ProviderEventType)(nil)), + "ProviderFinishReason": reflect.ValueOf((*ProviderFinishReason)(nil)), + "ProviderUsage": reflect.ValueOf((*ProviderUsage)(nil)), + "ProviderAuthNone": reflect.ValueOf(ProviderAuthNone), + "ProviderAuthAPIKey": reflect.ValueOf(ProviderAuthAPIKey), + "ProviderAuthExternal": reflect.ValueOf(ProviderAuthExternal), + "ProviderAuthConfigured": reflect.ValueOf(ProviderAuthConfigured), + "ProviderAuthUnconfigured": reflect.ValueOf(ProviderAuthUnconfigured), + "ProviderAuthUnknown": reflect.ValueOf(ProviderAuthUnknown), + "ProviderToolAuto": reflect.ValueOf(ProviderToolAuto), + "ProviderToolNone": reflect.ValueOf(ProviderToolNone), + "ProviderToolRequired": reflect.ValueOf(ProviderToolRequired), + "ProviderToolNamed": reflect.ValueOf(ProviderToolNamed), + "ProviderPartText": reflect.ValueOf(ProviderPartText), + "ProviderPartReasoning": reflect.ValueOf(ProviderPartReasoning), + "ProviderPartFile": reflect.ValueOf(ProviderPartFile), + "ProviderPartToolCall": reflect.ValueOf(ProviderPartToolCall), + "ProviderPartToolResult": reflect.ValueOf(ProviderPartToolResult), + "ProviderTextStart": reflect.ValueOf(ProviderTextStart), + "ProviderTextDelta": reflect.ValueOf(ProviderTextDelta), + "ProviderTextEnd": reflect.ValueOf(ProviderTextEnd), + "ProviderReasoningStart": reflect.ValueOf(ProviderReasoningStart), + "ProviderReasoningDelta": reflect.ValueOf(ProviderReasoningDelta), + "ProviderReasoningEnd": reflect.ValueOf(ProviderReasoningEnd), + "ProviderToolStart": reflect.ValueOf(ProviderToolStart), + "ProviderToolDelta": reflect.ValueOf(ProviderToolDelta), + "ProviderToolEnd": reflect.ValueOf(ProviderToolEnd), + "ProviderUsageEvent": reflect.ValueOf(ProviderUsageEvent), + "ProviderFinish": reflect.ValueOf(ProviderFinish), + "ProviderFinishStop": reflect.ValueOf(ProviderFinishStop), + "ProviderFinishLength": reflect.ValueOf(ProviderFinishLength), + "ProviderFinishToolCalls": reflect.ValueOf(ProviderFinishToolCalls), + // Struct types (nil pointer trick for type registration) "API": reflect.ValueOf((*API)(nil)), "Context": reflect.ValueOf((*Context)(nil)), diff --git a/internal/extensions/test_api.go b/internal/extensions/test_api.go index 340fa4e8..61b9b21e 100644 --- a/internal/extensions/test_api.go +++ b/internal/extensions/test_api.go @@ -9,6 +9,7 @@ func NewTestAPI(ext *LoadedExtension) API { } return API{ + registerProviderFn: ext.registerProvider, onToolCall: func(h func(ToolCallEvent, Context) *ToolCallResult) { reg(ToolCall, func(e Event, c Context) Result { r := h(e.(ToolCallEvent), c) diff --git a/internal/kitsetup/providers.go b/internal/kitsetup/providers.go new file mode 100644 index 00000000..e6fe0d71 --- /dev/null +++ b/internal/kitsetup/providers.go @@ -0,0 +1,136 @@ +package kitsetup + +import ( + "context" + "fmt" + "io" + "os" + "slices" + "strings" + + "github.com/mark3labs/kit/internal/auth" + "github.com/mark3labs/kit/internal/extensions" + "github.com/mark3labs/kit/internal/models" +) + +type providerCloser struct{ close func(context.Context) error } + +func (c providerCloser) Close() error { return c.close(context.Background()) } +func (c providerCloser) CloseContext(ctx context.Context) error { return c.close(ctx) } + +var _ io.Closer = providerCloser{} + +// PrepareProviderFactories adapts owned extension callbacks without modifying +// process registries. Sorted owner/ID checks make collision errors deterministic. +func PrepareProviderFactories(loaded []extensions.LoadedExtension, existing map[string]models.ProviderFactory) (map[string]models.ProviderFactory, error) { + factories := make(map[string]models.ProviderFactory, len(existing)) + reserved := map[string]bool{} + for _, id := range models.GetGlobalRegistry().GetSupportedProviders() { + reserved[strings.ToLower(id)] = true + } + for _, id := range models.RegisteredProviderFactories() { + reserved[id] = true + } + for _, id := range []string{"anthropic", "openai", "openai-compat", "google", "google-vertex", "google-vertex-anthropic", "azure", "bedrock", "ollama", "openrouter", "vercel", "copilot", "github-copilot"} { + reserved[id] = true + } + for id, factory := range existing { + factories[id] = factory + reserved[id] = true + } + owned := slices.Clone(loaded) + slices.SortFunc(owned, func(a, b extensions.LoadedExtension) int { return strings.Compare(a.Path, b.Path) }) + owners := map[string]string{} + for _, owner := range owned { + for _, definition := range owner.Providers { + if reserved[definition.ID] { + return nil, fmt.Errorf("extension %s provider %s collides with an existing provider", owner.Path, definition.ID) + } + if prior, exists := owners[definition.ID]; exists { + return nil, fmt.Errorf("provider %s registered by both %s and %s", definition.ID, prior, owner.Path) + } + owners[definition.ID] = owner.Path + factories[definition.ID] = func(ctx context.Context, cfg *models.ProviderConfig, model string) (*models.ProviderResult, error) { + key := cfg.ProviderAPIKey + if key == "" { + if manager, err := auth.NewCredentialManager(); err == nil { + key, _ = manager.GetProviderAPIKey(definition.ID) + } + } + if key == "" { + for _, variable := range definition.Auth.Environment { + if key = os.Getenv(variable); key != "" { + break + } + } + } + if definition.Auth.Mode == extensions.ProviderAuthAPIKey && key == "" { + return nil, fmt.Errorf("extension provider %s requires an API key: %s", definition.ID, definition.Auth.SetupInstruction) + } + params := extensions.ProviderParameters{ReasoningLevel: string(cfg.ThinkingLevel), SettingsJSON: definition.SettingsJSON, StopSequences: slices.Clone(cfg.StopSequences)} + maxTokens := int64(cfg.MaxTokens) + params.MaxOutputTokens = &maxTokens + if cfg.Temperature != nil { + v := float64(*cfg.Temperature) + params.Temperature = &v + } + if cfg.TopP != nil { + v := float64(*cfg.TopP) + params.TopP = &v + } + if cfg.TopK != nil { + v := int64(*cfg.TopK) + params.TopK = &v + } + if cfg.PresencePenalty != nil { + v := float64(*cfg.PresencePenalty) + params.PresencePenalty = &v + } + if cfg.FrequencyPenalty != nil { + v := float64(*cfg.FrequencyPenalty) + params.FrequencyPenalty = &v + } + adapter, close, err := extensions.NewProviderModel(ctx, definition, extensions.ProviderCreateRequest{Model: model, APIKey: key, Endpoint: cfg.ProviderURL, UserAgent: cfg.UserAgent, Parameters: params}) + if err != nil { + return nil, err + } + return &models.ProviderResult{Model: adapter, Closer: providerCloser{close: close}}, nil + } + } + } + return factories, nil +} + +// PrepareProviderResolver snapshots metadata without creating a model or MCP. +func PrepareProviderResolver(loaded []extensions.LoadedExtension) *models.Resolver { + entries := map[string]map[string]models.ModelInfo{} + unknown := map[string]bool{} + for _, owner := range loaded { + for _, def := range owner.Providers { + entries[def.ID] = map[string]models.ModelInfo{} + unknown[def.ID] = def.AllowUnknownModels + for _, item := range def.Models { + info := models.ModelInfo{CapabilitiesKnown: true, Tools: item.Capabilities.Tools, Streaming: item.Capabilities.Streaming, ID: item.ID, Name: item.Name, Limit: models.Limit{Context: int(item.ContextLimit), Output: int(item.OutputLimit)}, Reasoning: item.Capabilities.Reasoning, ReasoningIsGraded: true, ReasoningLevels: slices.Clone(item.Capabilities.ReasoningLevels), InputModalities: slices.Clone(item.Capabilities.InputModalities)} + info.Cost = models.Cost{Input: item.InputPrice.Value, Output: item.OutputPrice.Value, Published: item.InputPrice.Known && item.OutputPrice.Known} + if item.CacheReadPrice.Known { + value := item.CacheReadPrice.Value + info.Cost.CacheRead = &value + } + if item.CacheWritePrice.Known { + value := item.CacheWritePrice.Value + info.Cost.CacheWrite = &value + } + entries[def.ID][item.ID] = info + } + } + } + resolver := models.NewResolver(entries, unknown) + for _, owner := range loaded { + for _, def := range owner.Providers { + resolver.SetOwner(def.ID, owner.Path) + caps := def.UnknownModelDefaults + resolver.SetUnknownMetadata(def.ID, models.ModelInfo{Tools: caps.Tools, Reasoning: caps.Reasoning, ReasoningLevels: slices.Clone(caps.ReasoningLevels), InputModalities: slices.Clone(caps.InputModalities)}) + } + } + return resolver +} diff --git a/internal/kitsetup/providers_test.go b/internal/kitsetup/providers_test.go new file mode 100644 index 00000000..eda86e60 --- /dev/null +++ b/internal/kitsetup/providers_test.go @@ -0,0 +1,34 @@ +package kitsetup + +import ( + "context" + "testing" + + "github.com/mark3labs/kit/internal/extensions" + "github.com/mark3labs/kit/internal/models" +) + +func TestProviderFactoryCollisions(t *testing.T) { + factory := func(context.Context, *models.ProviderConfig, string) (*models.ProviderResult, error) { return nil, nil } + for _, id := range []string{"openai", "anthropic", "copilot", "ollama"} { + t.Run(id, func(t *testing.T) { + _, err := PrepareProviderFactories([]extensions.LoadedExtension{{Path: "owner.go", Providers: []extensions.ProviderDef{{ID: id}}}}, nil) + if err == nil { + t.Fatal("reserved provider accepted") + } + }) + } + if _, err := PrepareProviderFactories([]extensions.LoadedExtension{{Path: "owner.go", Providers: []extensions.ProviderDef{{ID: "local"}}}}, map[string]models.ProviderFactory{"local": factory}); err == nil { + t.Fatal("instance collision accepted") + } + if err := models.RegisterProviderFactory("extension-collision-test", factory); err != nil { + t.Fatal(err) + } + defer models.UnregisterProviderFactory("extension-collision-test") + if _, err := PrepareProviderFactories([]extensions.LoadedExtension{{Path: "owner.go", Providers: []extensions.ProviderDef{{ID: "extension-collision-test"}}}}, nil); err == nil { + t.Fatal("global collision accepted") + } + if _, err := PrepareProviderFactories([]extensions.LoadedExtension{{Path: "b.go", Providers: []extensions.ProviderDef{{ID: "fake"}}}, {Path: "a.go", Providers: []extensions.ProviderDef{{ID: "fake"}}}}, nil); err == nil { + t.Fatal("cross-owner collision accepted") + } +} diff --git a/internal/kitsetup/setup.go b/internal/kitsetup/setup.go index e15fb924..800b1a4f 100644 --- a/internal/kitsetup/setup.go +++ b/internal/kitsetup/setup.go @@ -6,7 +6,6 @@ package kitsetup import ( "context" "fmt" - "os" "charm.land/fantasy" @@ -21,6 +20,9 @@ import ( // AgentSetupOptions configures agent creation. type AgentSetupOptions struct { + // PreparedExtensions is non-nil when configuration already loaded extensions. + // An empty non-nil slice prevents a second discovery/Init pass. + PreparedExtensions []extensions.LoadedExtension // MCPConfig is the MCP server configuration. Required. MCPConfig *config.Config // ShowSpinner shows a loading spinner for Ollama models. @@ -259,11 +261,19 @@ func SetupAgent(ctx context.Context, opts AgentSetupOptions) (*AgentSetupResult, var extCreationOpts extensionCreationOpts if !noExtensions { var extErr error - extRunner, extCreationOpts, extErr = loadExtensions(v, opts.Bare) + if opts.PreparedExtensions != nil { + extRunner, extCreationOpts = runnerForExtensions(v, opts.PreparedExtensions) + } else { + extRunner, extCreationOpts, extErr = loadExtensions(v, opts.Bare) + if extErr == nil && extRunner != nil { + modelConfig.ProviderFactories, extErr = PrepareProviderFactories(extRunner.Extensions(), modelConfig.ProviderFactories) + modelConfig.Resolver = PrepareProviderResolver(extRunner.Extensions()) + } + } if extErr != nil { // stderr: stdout is reserved for the response payload in // --quiet / --json mode. - fmt.Fprintf(os.Stderr, "Warning: Failed to load extensions: %v\n", extErr) + return nil, fmt.Errorf("loading extension providers: %w", extErr) } } @@ -349,10 +359,14 @@ func loadExtensions(v *viper.Viper, bare bool) (*extensions.Runner, extensionCre return nil, extensionCreationOpts{}, err } + runner, creation := runnerForExtensions(v, loaded) + return runner, creation, nil +} + +func runnerForExtensions(v *viper.Viper, loaded []extensions.LoadedExtension) (*extensions.Runner, extensionCreationOpts) { if len(loaded) == 0 { - return nil, extensionCreationOpts{}, nil + return nil, extensionCreationOpts{} } - runner := extensions.NewRunner(loaded) runner.SetConfigStore(v) @@ -365,5 +379,5 @@ func loadExtensions(v *viper.Viper, bare bool) (*extensions.Runner, extensionCre return runner, extensionCreationOpts{ toolWrapper: wrapper, extraTools: extTools, - }, nil + } } diff --git a/internal/message/content.go b/internal/message/content.go index 4fcb83d6..cd5b0915 100644 --- a/internal/message/content.go +++ b/internal/message/content.go @@ -54,7 +54,8 @@ type ContentPart interface { // TextContent holds plain text content within a message. type TextContent struct { - Text string `json:"text"` + Replay []ReplayData `json:"replay,omitempty"` + Text string `json:"text"` } func (TextContent) isPart() {} @@ -63,8 +64,9 @@ func (TextContent) isPart() {} // Provider-specific metadata (signatures, etc.) is preserved for round-trip // fidelity when the conversation is sent back to the provider. type ReasoningContent struct { - Thinking string `json:"thinking"` - Signature string `json:"signature,omitempty"` // Anthropic + Thinking string `json:"thinking"` + Signature string `json:"signature,omitempty"` // Anthropic + Replay []ReplayData `json:"replay,omitempty"` } func (ReasoningContent) isPart() {} @@ -72,10 +74,11 @@ func (ReasoningContent) isPart() {} // ToolCall represents a tool invocation initiated by the LLM. It is stored // as a content part within an assistant message, not as a separate message. type ToolCall struct { - ID string `json:"id"` - Name string `json:"name"` - Input string `json:"input"` // JSON string of arguments - Finished bool `json:"finished"` + Replay []ReplayData `json:"replay,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Input string `json:"input"` // JSON string of arguments + Finished bool `json:"finished"` } func (ToolCall) isPart() {} @@ -83,10 +86,11 @@ func (ToolCall) isPart() {} // ToolResult represents the result of executing a tool. It is stored as a // content part within a tool-role message, linked to a ToolCall by ID. type ToolResult struct { - ToolCallID string `json:"tool_call_id"` - Name string `json:"name"` - Content string `json:"content"` - IsError bool `json:"is_error"` + Replay []ReplayData `json:"replay,omitempty"` + ToolCallID string `json:"tool_call_id"` + Name string `json:"name"` + Content string `json:"content"` + IsError bool `json:"is_error"` // MediaData holds the base64 payload of a media tool result, such as an // image returned by the read tool. It is empty for a text result. The // value is base64 so that a resumed session rebuilds the same message @@ -102,8 +106,10 @@ func (ToolResult) isPart() {} // bytes (not base64-encoded); serialization handles encoding. MediaType is a // MIME type such as "image/png" or "image/jpeg". type ImageContent struct { - Data []byte `json:"data"` - MediaType string `json:"media_type"` + Replay []ReplayData `json:"replay,omitempty"` + Filename string `json:"filename,omitempty"` + Data []byte `json:"data"` + MediaType string `json:"media_type"` } func (ImageContent) isPart() {} @@ -132,13 +138,15 @@ const ( // carry text, reasoning, and multiple tool calls as discrete, typed blocks // rather than flattening everything into strings. type Message struct { - ID string `json:"id"` - Role MessageRole `json:"role"` - Parts []ContentPart `json:"parts"` - Model string `json:"model,omitempty"` - Provider string `json:"provider,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ExtensionProvider string `json:"extension_provider,omitempty"` + Replay []ReplayData `json:"replay,omitempty"` + ID string `json:"id"` + Role MessageRole `json:"role"` + Parts []ContentPart `json:"parts"` + Model string `json:"model,omitempty"` + Provider string `json:"provider,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // --- Typed accessors --- @@ -311,6 +319,9 @@ func UnmarshalParts(data []byte) ([]ContentPart, error) { // mixed TextPart and ToolCallPart content. Tool-role messages produce // ToolResultPart entries. func (m *Message) ToLLMMessages() []fantasy.Message { + if m.ExtensionProvider != "" { + return orderedExtensionMessages(m) + } switch m.Role { case RoleAssistant: var parts []fantasy.MessagePart @@ -425,9 +436,21 @@ func FromLLMMessage(msg fantasy.Message) Message { UpdatedAt: time.Now(), } + if metadata := ExtensionOptions(msg.ProviderOptions); metadata != nil { + m.ExtensionProvider = metadata.Provider + m.Replay = metadata.Replay + } for _, part := range msg.Content { + metadata := ExtensionOptions(part.Options()) + if metadata != nil { + m.ExtensionProvider = metadata.Provider + } switch p := part.(type) { case fantasy.TextPart: + if metadata != nil { + m.Parts = append(m.Parts, TextContent{Text: p.Text, Replay: metadata.Replay}) + continue + } if p.Text != "" { // Strip ... tags that some models wrap reasoning in cleanedText := thinkTagRegex.ReplaceAllString(p.Text, "") @@ -436,7 +459,12 @@ func FromLLMMessage(msg fantasy.Message) Message { } } case fantasy.ToolCallPart: + var replay []ReplayData + if metadata != nil { + replay = metadata.Replay + } m.Parts = append(m.Parts, ToolCall{ + Replay: replay, ID: p.ToolCallID, Name: p.ToolName, Input: p.Input, @@ -446,6 +474,9 @@ func FromLLMMessage(msg fantasy.Message) Message { result := ToolResult{ ToolCallID: p.ToolCallID, } + if metadata != nil { + result.Replay = metadata.Replay + } switch r := p.Output.(type) { case fantasy.ToolResultOutputContentText: result.Content = r.Text @@ -459,15 +490,24 @@ func FromLLMMessage(msg fantasy.Message) Message { } m.Parts = append(m.Parts, result) case fantasy.ReasoningPart: - if p.Text != "" { - m.Parts = append(m.Parts, ReasoningContent{ - Thinking: p.Text, - }) + if p.Text != "" || metadata != nil { + content := ReasoningContent{Thinking: p.Text} + if metadata != nil { + content.Signature = metadata.Signature + content.Replay = metadata.Replay + } + m.Parts = append(m.Parts, content) } case fantasy.FilePart: + var replay []ReplayData + if metadata != nil { + replay = metadata.Replay + } if len(p.Data) > 0 { m.Parts = append(m.Parts, ImageContent{ Data: p.Data, + Replay: replay, + Filename: p.Filename, MediaType: p.MediaType, }) } diff --git a/internal/message/replay.go b/internal/message/replay.go new file mode 100644 index 00000000..32c554de --- /dev/null +++ b/internal/message/replay.go @@ -0,0 +1,108 @@ +package message + +import ( + "encoding/json" + "fmt" + "maps" + "slices" + + "charm.land/fantasy" +) + +// ReplayData is opaque JSON scoped to the provider that produced it. +type ReplayData struct { + Version int `json:"version"` + Provider string `json:"provider"` + JSON string `json:"json"` +} + +// ExtensionMetadata is a compiled carrier. Its fields contain no credentials. +// It marks exact extension-origin IDs and ordered history without affecting +// legacy built-in normalization. +type ExtensionMetadata struct { + Provider string `json:"provider"` + BlockID string `json:"block_id,omitempty"` + Order []string `json:"order,omitempty"` + Signature string `json:"signature,omitempty"` + Replay []ReplayData `json:"replay,omitempty"` +} + +func (*ExtensionMetadata) Options() {} +func (m ExtensionMetadata) MarshalJSON() ([]byte, error) { + type plain ExtensionMetadata + return fantasy.MarshalProviderType("kit.extension.replay.v1", plain(m)) +} +func (m *ExtensionMetadata) UnmarshalJSON(data []byte) error { + type plain ExtensionMetadata + var value plain + if err := fantasy.UnmarshalProviderType(data, &value); err != nil { + return err + } + *m = ExtensionMetadata(value) + return nil +} +func init() { + fantasy.RegisterProviderType("kit.extension.replay.v1", func(data []byte) (fantasy.ProviderOptionsData, error) { + var value ExtensionMetadata + if err := json.Unmarshal(data, &value); err != nil { + return nil, err + } + return &value, nil + }) +} + +// ExtensionOptions returns only the compiled extension replay carrier. +func ExtensionOptions(options fantasy.ProviderOptions) *ExtensionMetadata { + if value, ok := options["kit-extension"].(*ExtensionMetadata); ok { + copy := *value + copy.Replay = slices.Clone(value.Replay) + copy.Order = slices.Clone(value.Order) + return © + } + return nil +} + +// CloneOptions isolates extension metadata while keeping other providers' option +// behavior unchanged. +func CloneOptions(options fantasy.ProviderOptions) fantasy.ProviderOptions { + if options == nil { + return nil + } + copy := maps.Clone(options) + if ext := ExtensionOptions(options); ext != nil { + copy["kit-extension"] = ext + } + return copy +} + +func orderedExtensionMessages(m *Message) []fantasy.Message { + var content []fantasy.MessagePart + for _, part := range m.Parts { + options := fantasy.ProviderOptions{"kit-extension": &ExtensionMetadata{Provider: m.ExtensionProvider}} + switch value := part.(type) { + case TextContent: + options["kit-extension"] = &ExtensionMetadata{Provider: m.ExtensionProvider, Replay: slices.Clone(value.Replay)} + content = append(content, fantasy.TextPart{Text: value.Text, ProviderOptions: options}) + case ReasoningContent: + options["kit-extension"] = &ExtensionMetadata{Provider: m.ExtensionProvider, Signature: value.Signature, Replay: slices.Clone(value.Replay)} + content = append(content, fantasy.ReasoningPart{Text: value.Thinking, ProviderOptions: options}) + case ImageContent: + options["kit-extension"] = &ExtensionMetadata{Provider: m.ExtensionProvider, Replay: slices.Clone(value.Replay)} + content = append(content, fantasy.FilePart{Data: slices.Clone(value.Data), MediaType: value.MediaType, Filename: value.Filename, ProviderOptions: options}) + case ToolCall: + options["kit-extension"] = &ExtensionMetadata{Provider: m.ExtensionProvider, Replay: slices.Clone(value.Replay)} + content = append(content, fantasy.ToolCallPart{ToolCallID: value.ID, ToolName: value.Name, Input: value.Input, ProviderOptions: options}) + case ToolResult: + options["kit-extension"] = &ExtensionMetadata{Provider: m.ExtensionProvider, Replay: slices.Clone(value.Replay)} + var output fantasy.ToolResultOutputContent = fantasy.ToolResultOutputContentText{Text: value.Content} + if value.IsError { + output = fantasy.ToolResultOutputContentError{Error: fmt.Errorf("%s", value.Content)} + } + if value.MediaData != "" { + output = fantasy.ToolResultOutputContentMedia{Data: value.MediaData, MediaType: value.MediaType, Text: value.Content} + } + content = append(content, fantasy.ToolResultPart{ToolCallID: value.ToolCallID, Output: output, ProviderOptions: options}) + } + } + return []fantasy.Message{{Role: fantasy.MessageRole(m.Role), Content: content, ProviderOptions: fantasy.ProviderOptions{"kit-extension": &ExtensionMetadata{Provider: m.ExtensionProvider, Replay: slices.Clone(m.Replay)}}}} +} diff --git a/internal/message/replay_test.go b/internal/message/replay_test.go new file mode 100644 index 00000000..58a03e36 --- /dev/null +++ b/internal/message/replay_test.go @@ -0,0 +1,53 @@ +package message + +import ( + "encoding/json" + "reflect" + "testing" + + "charm.land/fantasy" +) + +func TestExtensionHistoryPreservesOrderAndIDs(t *testing.T) { + options := fantasy.ProviderOptions{"kit-extension": &ExtensionMetadata{Provider: "fake", Signature: "signed", Replay: []ReplayData{{Version: 1, Provider: "fake", JSON: `{"opaque":true}`}}}} + input := fantasy.Message{Role: fantasy.MessageRoleAssistant, ProviderOptions: options, Content: []fantasy.MessagePart{ + fantasy.TextPart{Text: "before", ProviderOptions: options}, + fantasy.ReasoningPart{Text: "", ProviderOptions: options}, + fantasy.TextPart{Text: "after", ProviderOptions: options}, + fantasy.ToolCallPart{ToolCallID: "exact/id.with:punctuation", ToolName: "add", Input: `{"a":2}`, ProviderOptions: options}, + fantasy.FilePart{Filename: "document.pdf", MediaType: "application/pdf", Data: []byte{1, 2}, ProviderOptions: options}, + }} + stored := FromLLMMessage(input) + data, err := MarshalParts(stored.Parts) + if err != nil { + t.Fatal(err) + } + stored.Parts, err = UnmarshalParts(data) + if err != nil { + t.Fatal(err) + } + output := stored.ToLLMMessages() + if len(output) != 1 || len(output[0].Content) != 5 { + t.Fatalf("output=%+v", output) + } + if output[0].Content[3].(fantasy.ToolCallPart).ToolCallID != "exact/id.with:punctuation" { + t.Fatal("ID normalized") + } + reasoning := output[0].Content[1].(fantasy.ReasoningPart) + if metadata := ExtensionOptions(reasoning.ProviderOptions); metadata.Signature != "signed" || len(metadata.Replay) != 1 { + t.Fatalf("metadata=%+v", metadata) + } + file := output[0].Content[4].(fantasy.FilePart) + if file.Filename != "document.pdf" || !reflect.DeepEqual(file.Data, []byte{1, 2}) { + t.Fatal("file changed") + } + encoded, err := json.Marshal(options) + if err != nil || len(encoded) == 0 { + t.Fatalf("metadata serialization: %v", err) + } + clone := CloneOptions(options) + clone["kit-extension"].(*ExtensionMetadata).Replay[0].JSON = "changed" + if options["kit-extension"].(*ExtensionMetadata).Replay[0].JSON == "changed" { + t.Fatal("clone aliases replay") + } +} diff --git a/internal/models/providers.go b/internal/models/providers.go index e36e569a..429b3c48 100644 --- a/internal/models/providers.go +++ b/internal/models/providers.go @@ -179,6 +179,8 @@ func ParseThinkingLevel(s string) ThinkingLevel { // ProviderConfig holds configuration for creating LLM providers. type ProviderConfig struct { + // Resolver is the instance catalog snapshot, when extensions are loaded. + Resolver *Resolver // UserAgent is the effective per-instance HTTP identity. Factories own enforcement. UserAgent string // HTTPTransport wraps the base transport for built-in provider requests. @@ -367,7 +369,7 @@ func createProvider(ctx context.Context, config *ProviderConfig) (*ProviderResul // suggestions on likely typos; when unknown we let the provider // API be the authority except for Copilot, whose non-GPT catalog entries // require unsupported wire protocols. - modelInfo := registry.LookupModel(lookupProvider, modelName) + modelInfo := config.Resolver.Lookup(lookupProvider, modelName) switch { case hasFactory: // The factory is the authority on which models it serves. Database diff --git a/internal/models/registry.go b/internal/models/registry.go index 93a84754..57ea1b7a 100644 --- a/internal/models/registry.go +++ b/internal/models/registry.go @@ -19,8 +19,12 @@ var embeddedModelsJSON []byte // ModelInfo represents information about a specific model. type ModelInfo struct { - ID string - Name string + // Extension capabilities are authoritative when CapabilitiesKnown is true. + CapabilitiesKnown bool + Tools bool + Streaming bool + ID string + Name string // Type is the catalog model kind: ModelTypeDecision, or empty for chat // models. Type string diff --git a/internal/models/resolver.go b/internal/models/resolver.go new file mode 100644 index 00000000..c5f95707 --- /dev/null +++ b/internal/models/resolver.go @@ -0,0 +1,130 @@ +package models + +import ( + "fmt" + "maps" + "slices" + "strings" +) + +// Resolver combines an immutable instance catalog with the process catalog. +// It does not retain extension implementation types or publish global entries. +type Resolver struct { + entries map[string]map[string]ModelInfo + unknown map[string]bool + owners map[string]string + unknownMetadata map[string]ModelInfo +} + +// NewResolver snapshots instance metadata. Unknown lookups do not populate lists. +func NewResolver(entries map[string]map[string]ModelInfo, unknown map[string]bool) *Resolver { + result := &Resolver{entries: make(map[string]map[string]ModelInfo, len(entries)), unknown: maps.Clone(unknown)} + for provider, items := range entries { + result.entries[provider] = map[string]ModelInfo{} + for id, info := range items { + result.entries[provider][id] = cloneModelInfo(info) + } + } + return result +} +func cloneModelInfo(info ModelInfo) ModelInfo { + if info.Cost.CacheRead != nil { + value := *info.Cost.CacheRead + info.Cost.CacheRead = &value + } + if info.Cost.CacheWrite != nil { + value := *info.Cost.CacheWrite + info.Cost.CacheWrite = &value + } + info.InputModalities = slices.Clone(info.InputModalities) + info.ReasoningLevels = slices.Clone(info.ReasoningLevels) + return info +} + +// SetOwner records a source identity before the resolver is published. +func (r *Resolver) SetOwner(provider, path string) { + if r.owners == nil { + r.owners = map[string]string{} + } + r.owners[provider] = path +} + +// SetUnknownMetadata supplies explicit provider-level unknown-model defaults. +func (r *Resolver) SetUnknownMetadata(provider string, info ModelInfo) { + if r.unknownMetadata == nil { + r.unknownMetadata = map[string]ModelInfo{} + } + r.unknownMetadata[provider] = cloneModelInfo(info) +} + +// Owner returns the registered source identity. +func (r *Resolver) Owner(provider string) string { + if r == nil { + return "" + } + return r.owners[provider] +} + +// Owns reports whether this generation declares the provider. +func (r *Resolver) Owns(provider string) bool { + if r == nil { + return false + } + _, ok := r.entries[strings.ToLower(provider)] + return ok +} + +// Lookup returns a metadata copy, falling back only for non-instance providers. +func (r *Resolver) Lookup(provider, id string) *ModelInfo { + if r != nil { + if items, ok := r.entries[strings.ToLower(provider)]; ok { + if info, ok := items[id]; ok { + copy := cloneModelInfo(info) + return © + } + if r.unknown[strings.ToLower(provider)] { + info := cloneModelInfo(r.unknownMetadata[strings.ToLower(provider)]) + info.ID = id + info.Name = id + info.CapabilitiesKnown = true + info.Streaming = true + info.ReasoningIsGraded = true + if len(info.InputModalities) == 0 { + info.InputModalities = []string{"text"} + } + return &info + } + return nil + } + } + return GetGlobalRegistry().LookupModel(provider, id) +} + +// Validate checks instance declarations without accepting removed providers. +func (r *Resolver) Validate(model string) error { + provider, id, err := ParseModelString(model) + if err != nil { + return err + } + if r.Owns(provider) { + if r.Lookup(provider, id) == nil { + return fmt.Errorf("extension provider %s does not declare model %q", provider, id) + } + return nil + } + return GetGlobalRegistry().ValidateModelString(model) +} + +// List returns a copy of declared instance entries only. +func (r *Resolver) List() map[string]map[string]ModelInfo { + result := map[string]map[string]ModelInfo{} + if r != nil { + for provider, entries := range r.entries { + result[provider] = map[string]ModelInfo{} + for id, info := range entries { + result[provider][id] = cloneModelInfo(info) + } + } + } + return result +} diff --git a/internal/models/resolver_test.go b/internal/models/resolver_test.go new file mode 100644 index 00000000..5b305f42 --- /dev/null +++ b/internal/models/resolver_test.go @@ -0,0 +1,24 @@ +package models + +import "testing" + +func TestResolverIsolatedAndUnknownLookupDoesNotDeclare(t *testing.T) { + input := map[string]map[string]ModelInfo{"fake": {"echo": {ID: "echo", Name: "Echo", InputModalities: []string{"text"}}}} + resolver := NewResolver(input, map[string]bool{"fake": true}) + input["fake"]["echo"] = ModelInfo{Name: "changed"} + if got := resolver.Lookup("fake", "echo"); got.Name != "Echo" { + t.Fatalf("lookup=%+v", got) + } + unknown := resolver.Lookup("fake", "unlisted") + if unknown == nil || unknown.Limit.Context != 0 || unknown.Cost.Published || len(resolver.List()["fake"]) != 1 { + t.Fatalf("unknown=%+v", unknown) + } + known := resolver.Lookup("fake", "echo") + known.InputModalities[0] = "image" + if resolver.Lookup("fake", "echo").InputModalities[0] != "text" { + t.Fatal("lookup aliases resolver") + } + if GetGlobalRegistry().LookupModel("fake", "echo") != nil { + t.Fatal("global catalog changed") + } +} diff --git a/internal/session/entry.go b/internal/session/entry.go index d2a56b9e..b2d0b594 100644 --- a/internal/session/entry.go +++ b/internal/session/entry.go @@ -68,6 +68,8 @@ type treeEntry interface{ header() *Entry } // content uses the same type-tagged parts format as the existing session // persistence layer, enabling reuse of MarshalParts/UnmarshalParts. type MessageEntry struct { + ExtensionProvider string `json:"extension_provider,omitempty"` + Replay []message.ReplayData `json:"replay,omitempty"` Entry Role string `json:"role"` Parts json.RawMessage `json:"parts"` // type-tagged parts array @@ -181,11 +183,13 @@ func NewMessageEntry(parentID string, msg message.Message) (*MessageEntry, error return nil, fmt.Errorf("failed to marshal message parts: %w", err) } return &MessageEntry{ - Entry: NewEntry(EntryTypeMessage, parentID), - Role: string(msg.Role), - Parts: parts, - Model: msg.Model, - Provider: msg.Provider, + Entry: NewEntry(EntryTypeMessage, parentID), + ExtensionProvider: msg.ExtensionProvider, + Replay: msg.Replay, + Role: string(msg.Role), + Parts: parts, + Model: msg.Model, + Provider: msg.Provider, }, nil } @@ -395,12 +399,14 @@ func (e *MessageEntry) ToMessage() (message.Message, error) { return message.Message{}, fmt.Errorf("failed to unmarshal parts: %w", err) } return message.Message{ - ID: e.ID, - Role: message.MessageRole(e.Role), - Parts: parts, - Model: e.Model, - Provider: e.Provider, - CreatedAt: e.Timestamp, - UpdatedAt: e.Timestamp, + ExtensionProvider: e.ExtensionProvider, + Replay: e.Replay, + ID: e.ID, + Role: message.MessageRole(e.Role), + Parts: parts, + Model: e.Model, + Provider: e.Provider, + CreatedAt: e.Timestamp, + UpdatedAt: e.Timestamp, }, nil } diff --git a/internal/session/provider_replay_test.go b/internal/session/provider_replay_test.go new file mode 100644 index 00000000..bd8ba475 --- /dev/null +++ b/internal/session/provider_replay_test.go @@ -0,0 +1,85 @@ +package session + +import ( + "encoding/json" + "testing" + + "charm.land/fantasy" + "github.com/mark3labs/kit/internal/message" +) + +func TestExtensionReplaySurvivesEntryAndContextClone(t *testing.T) { + options := fantasy.ProviderOptions{"kit-extension": &message.ExtensionMetadata{Provider: "fake", Signature: "signature", Replay: []message.ReplayData{{Version: 1, Provider: "fake", JSON: `{"value":1}`}}}} + original := fantasy.Message{Role: fantasy.MessageRoleAssistant, ProviderOptions: options, Content: []fantasy.MessagePart{ + fantasy.TextPart{Text: "before", ProviderOptions: options}, + fantasy.ReasoningPart{Text: "", ProviderOptions: options}, + fantasy.ToolCallPart{ToolCallID: "call/with:punctuation", ToolName: "add", Input: `{}`, ProviderOptions: options}, + }} + entry, err := NewMessageEntry("", message.FromLLMMessage(original)) + if err != nil { + t.Fatal(err) + } + data, err := json.Marshal(entry) + if err != nil { + t.Fatal(err) + } + var reopened MessageEntry + if err := json.Unmarshal(data, &reopened); err != nil { + t.Fatal(err) + } + decoded, err := reopened.ToMessage() + if err != nil { + t.Fatal(err) + } + tm := InMemoryTreeSession("/test") + if _, err := tm.AppendMessage(decoded); err != nil { + t.Fatal(err) + } + first, _, _ := tm.BuildContext() + if len(first) != 1 || len(first[0].Content) != 3 { + t.Fatalf("context=%+v", first) + } + if first[0].Content[2].(fantasy.ToolCallPart).ToolCallID != "call/with:punctuation" { + t.Fatal("ID changed") + } + metadata := first[0].Content[1].Options()["kit-extension"].(*message.ExtensionMetadata) + metadata.Replay[0].JSON = "mutated" + second, _, _ := tm.BuildContext() + if message.ExtensionOptions(second[0].Content[1].Options()).Replay[0].JSON != "{\"value\":1}" { + t.Fatal("context replay aliases cache") + } +} + +func TestExtensionReplaySaveAndReopen(t *testing.T) { + t.Setenv("HOME", t.TempDir()) + tm, err := CreateTreeSession(t.TempDir()) + if err != nil { + t.Fatal(err) + } + options := fantasy.ProviderOptions{"kit-extension": &message.ExtensionMetadata{Provider: "fake", Replay: []message.ReplayData{{Version: 1, Provider: "fake", JSON: `{"state":2}`}}}} + original := fantasy.Message{Role: fantasy.MessageRoleAssistant, ProviderOptions: options, Content: []fantasy.MessagePart{fantasy.ReasoningPart{Text: "", ProviderOptions: options}, fantasy.TextPart{Text: "saved", ProviderOptions: options}}} + if _, err := tm.AppendLLMMessage(original); err != nil { + t.Fatal(err) + } + path := tm.GetFilePath() + if err := tm.Close(); err != nil { + t.Fatal(err) + } + reopened, err := OpenTreeSession(path) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := reopened.Close(); err != nil { + t.Error(err) + } + }() + messages, _, _ := reopened.BuildContext() + if len(messages) != 1 || len(messages[0].Content) != 2 { + t.Fatalf("messages=%+v", messages) + } + metadata := message.ExtensionOptions(messages[0].ProviderOptions) + if metadata == nil || metadata.Provider != "fake" || len(metadata.Replay) != 1 { + t.Fatalf("metadata=%+v", metadata) + } +} diff --git a/internal/session/tree_manager.go b/internal/session/tree_manager.go index 54826db9..6004a48f 100644 --- a/internal/session/tree_manager.go +++ b/internal/session/tree_manager.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "maps" "os" "path/filepath" "slices" @@ -238,11 +237,13 @@ func (tm *TreeManager) ForkToNewSession(cwd string, targetID string) (*TreeManag newEntry = © case *MessageEntry: newEntry = &MessageEntry{ - Entry: cloneEntry(e.Entry, newID, prevNewID), - Role: e.Role, - Parts: e.Parts, - Model: e.Model, - Provider: e.Provider, + ExtensionProvider: e.ExtensionProvider, + Replay: slices.Clone(e.Replay), + Entry: cloneEntry(e.Entry, newID, prevNewID), + Role: e.Role, + Parts: e.Parts, + Model: e.Model, + Provider: e.Provider, } // Copy label if present. if label, ok := tm.labels[oldID]; ok { @@ -1119,10 +1120,10 @@ func (tm *TreeManager) BuildContext() (messages []fantasy.Message, provider stri // it: the Content slice, every ProviderOptions map, and FilePart.Data are // copied. All other fields are values or immutable strings. // -// The values inside a ProviderOptions map are copied shallowly. The cache -// never holds any: ToLLMMessages does not set provider options. +// Extension replay carriers are deep-copied, including their slices. Other +// provider-specific option values retain the existing shallow-copy behavior. func cloneLLMMessage(msg fantasy.Message) fantasy.Message { - msg.ProviderOptions = maps.Clone(msg.ProviderOptions) + msg.ProviderOptions = message.CloneOptions(msg.ProviderOptions) if msg.Content == nil { return msg } @@ -1130,20 +1131,20 @@ func cloneLLMMessage(msg fantasy.Message) fantasy.Message { for i, part := range msg.Content { switch p := part.(type) { case fantasy.TextPart: - p.ProviderOptions = maps.Clone(p.ProviderOptions) + p.ProviderOptions = message.CloneOptions(p.ProviderOptions) part = p case fantasy.ReasoningPart: - p.ProviderOptions = maps.Clone(p.ProviderOptions) + p.ProviderOptions = message.CloneOptions(p.ProviderOptions) part = p case fantasy.FilePart: - p.ProviderOptions = maps.Clone(p.ProviderOptions) + p.ProviderOptions = message.CloneOptions(p.ProviderOptions) p.Data = bytes.Clone(p.Data) part = p case fantasy.ToolCallPart: - p.ProviderOptions = maps.Clone(p.ProviderOptions) + p.ProviderOptions = message.CloneOptions(p.ProviderOptions) part = p case fantasy.ToolResultPart: - p.ProviderOptions = maps.Clone(p.ProviderOptions) + p.ProviderOptions = message.CloneOptions(p.ProviderOptions) part = p } content[i] = part diff --git a/internal/ui/model.go b/internal/ui/model.go index a45414f4..acea3cfa 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -415,6 +415,9 @@ type UIVisibility struct { // AppModelOptions holds configuration passed to NewAppModel. type AppModelOptions struct { + // GetModelEntries supplies instance catalog metadata without extension types. + GetModelEntries func() []ModelEntry + GetThinkingLevels func() []string // ModelName is the display name of the model (e.g. "claude-sonnet-4-5"). ModelName string @@ -703,6 +706,8 @@ type AppModelOptions struct { // All messages (completed and streaming) are rendered via the ScrollList // viewport. The alt screen owns the full terminal. type AppModel struct { + getModelEntries func() []ModelEntry + getThinkingLevels func() []string // conversationPane is the transcript pane on screen: parentPane, or the // pane of the subagent run that the user views (see childView). It is // embedded so that the transcript code reads m.messages, m.stream and @@ -1240,6 +1245,8 @@ func NewAppModel(appCtrl AppController, opts AppModelOptions) *AppModel { m.mcpPrompts = opts.MCPPrompts m.getMCPPrompts = opts.GetMCPPrompts m.expandMCPPrompt = opts.ExpandMCPPrompt + m.getModelEntries = opts.GetModelEntries + m.getThinkingLevels = opts.GetThinkingLevels m.getWidgets = opts.GetWidgets m.getHeader = opts.GetHeader m.getFooter = opts.GetFooter @@ -2812,7 +2819,7 @@ func (m *AppModel) copyNotice(n int) string { // cycleThinkingLevel advances to the next thinking level and applies it. func (m *AppModel) cycleThinkingLevel() { - supported := models.SupportedThinkingLevels(m.providerName, m.modelName) + supported := m.supportedThinkingLevels() levels := make([]string, len(supported)) for i, level := range supported { levels[i] = string(level) @@ -5023,7 +5030,7 @@ func (m *AppModel) openModelSelectorForProviderError() { return } currentModel := m.providerName + "/" + m.modelName - m.modelSelector = NewModelSelector(currentModel, m.width, m.height) + m.modelSelector = m.newModelSelector(currentModel) m.state = stateModelSelector } @@ -5040,7 +5047,7 @@ func (m *AppModel) handleModelCommand(args string) tea.Cmd { if args == "" { // Open the interactive model selector. currentModel := m.providerName + "/" + m.modelName - m.modelSelector = NewModelSelector(currentModel, m.width, m.height) + m.modelSelector = m.newModelSelector(currentModel) m.state = stateModelSelector return nil } @@ -5417,8 +5424,7 @@ func (m *AppModel) handleThinkingCommand(args string) tea.Cmd { if args == "" { // Match /model behaviour: open a modal picker directly, no space required. - m.thinkingLevelSelector = NewThinkingLevelSelector( - m.providerName, m.modelName, m.thinkingLevel, m.width, m.height) + m.thinkingLevelSelector = NewThinkingLevelSelectorWithLevels(m.thinkingLevel, m.width, m.height, m.supportedThinkingLevels()) m.state = stateThinkingLevelSelector return nil } diff --git a/internal/ui/model_selector.go b/internal/ui/model_selector.go index 0301bd84..8f0737ba 100644 --- a/internal/ui/model_selector.go +++ b/internal/ui/model_selector.go @@ -12,12 +12,13 @@ import ( // ModelEntry holds display metadata for a single model in the selector. type ModelEntry struct { - Provider string - ModelID string - Name string // human-friendly name (e.g. "Claude Haiku 4.5") - ContextLimit int - Reasoning bool - Available bool // provider has credentials configured + Provider string + ModelID string + Name string // human-friendly name (e.g. "Claude Haiku 4.5") + ContextLimit int + Reasoning bool + Available bool // provider has credentials configured + AvailabilityUnknown bool // checking or externally managed status } // ModelSelectedMsg is sent when the user selects a model from the selector. @@ -45,6 +46,11 @@ type ModelSelectorComponent struct { // registry. Every known model is listed; models whose provider has no // credentials are dimmed and cannot be selected. func NewModelSelector(currentModel string, width, height int) *ModelSelectorComponent { + return NewModelSelectorWithEntries(currentModel, width, height, nil) +} + +// NewModelSelectorWithEntries uses host-owned instance metadata when supplied. +func NewModelSelectorWithEntries(currentModel string, width, height int, entries []ModelEntry) *ModelSelectorComponent { registry := models.GetGlobalRegistry() var allModels []ModelEntry @@ -84,6 +90,9 @@ func NewModelSelector(currentModel string, width, height int) *ModelSelectorComp } } + if entries != nil { + allModels = append([]ModelEntry(nil), entries...) + } // Sort: usable models first, then alphabetically by model ID, grouped // by provider. Unavailable models sink to the bottom so the list still // opens on something the user can actually pick. @@ -103,6 +112,9 @@ func NewModelSelector(currentModel string, width, height int) *ModelSelectorComp desc := fmt.Sprintf("[%s]", m.Provider) if !m.Available { desc = fmt.Sprintf("[%s] no credentials", m.Provider) + if m.AvailabilityUnknown { + desc = fmt.Sprintf("[%s] checking authentication", m.Provider) + } } items[i] = PopupItem{ Label: m.ModelID, @@ -294,3 +306,11 @@ func fuzzyScoreModelEntry(query string, entry ModelEntry) int { return 0 } + +func (m *AppModel) newModelSelector(currentModel string) *ModelSelectorComponent { + var entries []ModelEntry + if m.getModelEntries != nil { + entries = m.getModelEntries() + } + return NewModelSelectorWithEntries(currentModel, m.width, m.height, entries) +} diff --git a/internal/ui/provider_catalog_test.go b/internal/ui/provider_catalog_test.go new file mode 100644 index 00000000..0036ddb8 --- /dev/null +++ b/internal/ui/provider_catalog_test.go @@ -0,0 +1,21 @@ +package ui + +import "testing" + +func TestProviderCatalogEntriesAreHostOwned(t *testing.T) { + entries := []ModelEntry{{Provider: "fake", ModelID: "echo", Name: "Echo", Available: true}} + selector := NewModelSelectorWithEntries("fake/echo", 80, 24, entries) + entries[0].ModelID = "changed" + items := selector.popup.Items() + if len(items) != 1 || items[0].Label != "echo" { + t.Fatalf("items=%+v", items) + } + if item := items[0].Meta.(ModelEntry); item.Provider != "fake" || !item.Available { + t.Fatalf("entry=%+v", item) + } + app := NewAppModel(nil, AppModelOptions{Width: 80, Height: 24, GetThinkingLevels: func() []string { return []string{"off", "custom"} }}) + levels := app.supportedThinkingLevels() + if len(levels) != 2 || levels[1] != "custom" { + t.Fatalf("levels=%v", levels) + } +} diff --git a/internal/ui/thinking_level_selector.go b/internal/ui/thinking_level_selector.go index 7a4e65c3..cab745c1 100644 --- a/internal/ui/thinking_level_selector.go +++ b/internal/ui/thinking_level_selector.go @@ -35,8 +35,11 @@ type ThinkingLevelSelectorComponent struct { // currentLevel is the level to mark as active in the list. When the model is // unknown or ungraded, every level Kit understands is offered. func NewThinkingLevelSelector(provider, modelName, currentLevel string, width, height int) *ThinkingLevelSelectorComponent { - levels := models.SupportedThinkingLevels(provider, modelName) + return NewThinkingLevelSelectorWithLevels(currentLevel, width, height, models.SupportedThinkingLevels(provider, modelName)) +} +// NewThinkingLevelSelectorWithLevels accepts the host's instance-aware ladder. +func NewThinkingLevelSelectorWithLevels(currentLevel string, width, height int, levels []models.ThinkingLevel) *ThinkingLevelSelectorComponent { items := make([]PopupItem, len(levels)) for i, l := range levels { s := string(l) @@ -103,3 +106,15 @@ func (t *ThinkingLevelSelectorComponent) View() tea.View { func (t *ThinkingLevelSelectorComponent) RenderOverlay() string { return t.popup.Render() } + +func (m *AppModel) supportedThinkingLevels() []models.ThinkingLevel { + if m.getThinkingLevels == nil { + return models.SupportedThinkingLevels(m.providerName, m.modelName) + } + values := m.getThinkingLevels() + levels := make([]models.ThinkingLevel, len(values)) + for i, value := range values { + levels[i] = models.ThinkingLevel(value) + } + return levels +} diff --git a/internal/ui/usage_tracker.go b/internal/ui/usage_tracker.go index 00f6c6ba..45a75c97 100644 --- a/internal/ui/usage_tracker.go +++ b/internal/ui/usage_tracker.go @@ -251,6 +251,8 @@ func (ut *UsageTracker) RenderUsageInfo() string { switch { case ut.isOAuth: costStr = "" + case ut.modelInfo != nil && ut.modelInfo.CapabilitiesKnown && !ut.modelInfo.Cost.Published: + costStr = baseStyle.Foreground(theme.Muted).Render("cost unknown") case ut.sessionStats.TotalCost <= 0: costStr = baseStyle.Foreground(theme.Muted).Render("$0") case ut.sessionStats.TotalCost < 0.01: diff --git a/internal/yaegi/LICENSE b/internal/yaegi/LICENSE new file mode 100644 index 00000000..6b2b3331 --- /dev/null +++ b/internal/yaegi/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Containous SAS + Copyright 2020 Traefik Labs SAS + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/internal/yaegi/README.md b/internal/yaegi/README.md new file mode 100644 index 00000000..91afb722 --- /dev/null +++ b/internal/yaegi/README.md @@ -0,0 +1,51 @@ +# Local interpreter patch + +This directory contains the interpreter from +[`github.com/traefik/yaegi` v0.16.1](https://github.com/traefik/yaegi/tree/v0.16.1). +The upstream Apache-2.0 license is in `LICENSE`. The standard-library export +tables still come from the pinned upstream module. + +Kit needs concurrent callbacks from separate provider runtimes in the same +interpreter. Upstream `_select` captures a mutable `[]reflect.SelectCase` in +its compiled node. Concurrent executions write channel values into that same +slice. This races even when extension code has no shared mutable state. + +## Changes from upstream + +- `interp/run.go`: copy the compiled select-case template into an + invocation-local slice before setting channel values and cancellation state. + The compiled template is immutable after compilation. No lock is held while + waiting on a channel, so independent calls can proceed concurrently. +- `interp/type.go`: point the private `unsafe2` import at this directory. +- Copy only production interpreter files and the two `unsafe2` build variants. + Generated standard-library tables remain upstream dependencies. + +Do not edit the shared module cache or use an absolute `replace` directive. +Both the production extension loader and the public test harness use this +interpreter. Keep their imports aligned. + +## Verification + +`internal/extensions/provider_concurrency_probe_test.go` exercises concrete +nested create/stream/close callback shapes. It creates separate runtimes from +one interpreter, invokes them concurrently, checks synchronous emission, and +uses channel barriers to verify that canceling some calls does not block the +others. It does not rely on shared extension variables or sleeps. + +Run: + +```sh +go test -race ./internal/extensions -run TestProviderConcurrencyProbe -count=20 +go test -race ./... +``` + +The test must fail under the race detector if the slice-copy patch is removed. +This patch does **not** make extension package globals thread-safe or promise +that every interpreter operation is safe concurrently. Provider integration +still needs real interpreted concurrency tests for every callback boundary. + +When updating Yaegi, compare this directory with the new upstream version and +re-run the regression tests. Remove this copy when an upstream release fixes +the problem and passes Kit's tests. Do not replace the patch with a mutex held +across callbacks: a blocked stream must not prevent a separate runtime from +observing cancellation or producing output. diff --git a/internal/yaegi/interp/ast.go b/internal/yaegi/interp/ast.go new file mode 100644 index 00000000..1e71ac43 --- /dev/null +++ b/internal/yaegi/interp/ast.go @@ -0,0 +1,966 @@ +package interp + +import ( + "fmt" + "go/ast" + "go/constant" + "go/parser" + "go/scanner" + "go/token" + "reflect" + "strconv" + "strings" + "sync/atomic" +) + +// nkind defines the kind of AST, i.e. the grammar category. +type nkind uint + +// Node kinds for the go language. +const ( + undefNode nkind = iota + addressExpr + arrayType + assignStmt + assignXStmt + basicLit + binaryExpr + blockStmt + branchStmt + breakStmt + callExpr + caseBody + caseClause + chanType + chanTypeSend + chanTypeRecv + commClause + commClauseDefault + compositeLitExpr + constDecl + continueStmt + declStmt + deferStmt + defineStmt + defineXStmt + ellipsisExpr + exprStmt + fallthroughtStmt + fieldExpr + fieldList + fileStmt + forStmt0 // for {} + forStmt1 // for init; ; {} + forStmt2 // for cond {} + forStmt3 // for init; cond; {} + forStmt4 // for ; ; post {} + forStmt5 // for ; cond; post {} + forStmt6 // for init; ; post {} + forStmt7 // for init; cond; post {} + forRangeStmt // for range {} + funcDecl + funcLit + funcType + goStmt + gotoStmt + identExpr + ifStmt0 // if cond {} + ifStmt1 // if cond {} else {} + ifStmt2 // if init; cond {} + ifStmt3 // if init; cond {} else {} + importDecl + importSpec + incDecStmt + indexExpr + indexListExpr + interfaceType + keyValueExpr + labeledStmt + landExpr + lorExpr + mapType + parenExpr + rangeStmt + returnStmt + selectStmt + selectorExpr + selectorImport + sendStmt + sliceExpr + starExpr + structType + switchStmt + switchIfStmt + typeAssertExpr + typeDecl + typeSpec // type A int + typeSpecAssign // type A = int + typeSwitch + unaryExpr + valueSpec + varDecl +) + +var kinds = [...]string{ + undefNode: "undefNode", + addressExpr: "addressExpr", + arrayType: "arrayType", + assignStmt: "assignStmt", + assignXStmt: "assignXStmt", + basicLit: "basicLit", + binaryExpr: "binaryExpr", + blockStmt: "blockStmt", + branchStmt: "branchStmt", + breakStmt: "breakStmt", + callExpr: "callExpr", + caseBody: "caseBody", + caseClause: "caseClause", + chanType: "chanType", + chanTypeSend: "chanTypeSend", + chanTypeRecv: "chanTypeRecv", + commClause: "commClause", + commClauseDefault: "commClauseDefault", + compositeLitExpr: "compositeLitExpr", + constDecl: "constDecl", + continueStmt: "continueStmt", + declStmt: "declStmt", + deferStmt: "deferStmt", + defineStmt: "defineStmt", + defineXStmt: "defineXStmt", + ellipsisExpr: "ellipsisExpr", + exprStmt: "exprStmt", + fallthroughtStmt: "fallthroughStmt", + fieldExpr: "fieldExpr", + fieldList: "fieldList", + fileStmt: "fileStmt", + forStmt0: "forStmt0", + forStmt1: "forStmt1", + forStmt2: "forStmt2", + forStmt3: "forStmt3", + forStmt4: "forStmt4", + forStmt5: "forStmt5", + forStmt6: "forStmt6", + forStmt7: "forStmt7", + forRangeStmt: "forRangeStmt", + funcDecl: "funcDecl", + funcType: "funcType", + funcLit: "funcLit", + goStmt: "goStmt", + gotoStmt: "gotoStmt", + identExpr: "identExpr", + ifStmt0: "ifStmt0", + ifStmt1: "ifStmt1", + ifStmt2: "ifStmt2", + ifStmt3: "ifStmt3", + importDecl: "importDecl", + importSpec: "importSpec", + incDecStmt: "incDecStmt", + indexExpr: "indexExpr", + indexListExpr: "indexListExpr", + interfaceType: "interfaceType", + keyValueExpr: "keyValueExpr", + labeledStmt: "labeledStmt", + landExpr: "landExpr", + lorExpr: "lorExpr", + mapType: "mapType", + parenExpr: "parenExpr", + rangeStmt: "rangeStmt", + returnStmt: "returnStmt", + selectStmt: "selectStmt", + selectorExpr: "selectorExpr", + selectorImport: "selectorImport", + sendStmt: "sendStmt", + sliceExpr: "sliceExpr", + starExpr: "starExpr", + structType: "structType", + switchStmt: "switchStmt", + switchIfStmt: "switchIfStmt", + typeAssertExpr: "typeAssertExpr", + typeDecl: "typeDecl", + typeSpec: "typeSpec", + typeSpecAssign: "typeSpecAssign", + typeSwitch: "typeSwitch", + unaryExpr: "unaryExpr", + valueSpec: "valueSpec", + varDecl: "varDecl", +} + +func (k nkind) String() string { + if k < nkind(len(kinds)) { + return kinds[k] + } + return "nKind(" + strconv.Itoa(int(k)) + ")" +} + +// astError represents an error during AST build stage. +type astError error + +// action defines the node action to perform at execution. +type action uint + +// Node actions for the go language. +// It is important for type checking that *Assign directly +// follows it non-assign counterpart. +const ( + aNop action = iota + aAddr + aAssign + aAssignX + aAdd + aAddAssign + aAnd + aAndAssign + aAndNot + aAndNotAssign + aBitNot + aBranch + aCall + aCallSlice + aCase + aCompositeLit + aConvert + aDec + aEqual + aGreater + aGreaterEqual + aGetFunc + aGetIndex + aGetMethod + aGetSym + aInc + aLand + aLor + aLower + aLowerEqual + aMethod + aMul + aMulAssign + aNeg + aNot + aNotEqual + aOr + aOrAssign + aPos + aQuo + aQuoAssign + aRange + aRecv + aRem + aRemAssign + aReturn + aSend + aShl + aShlAssign + aShr + aShrAssign + aSlice + aSlice0 + aStar + aSub + aSubAssign + aTypeAssert + aXor + aXorAssign +) + +var actions = [...]string{ + aNop: "nop", + aAddr: "&", + aAssign: "=", + aAssignX: "X=", + aAdd: "+", + aAddAssign: "+=", + aAnd: "&", + aAndAssign: "&=", + aAndNot: "&^", + aAndNotAssign: "&^=", + aBitNot: "^", + aBranch: "branch", + aCall: "call", + aCallSlice: "callSlice", + aCase: "case", + aCompositeLit: "compositeLit", + aConvert: "convert", + aDec: "--", + aEqual: "==", + aGreater: ">", + aGreaterEqual: ">=", + aGetFunc: "getFunc", + aGetIndex: "getIndex", + aGetMethod: "getMethod", + aGetSym: ".", + aInc: "++", + aLand: "&&", + aLor: "||", + aLower: "<", + aLowerEqual: "<=", + aMethod: "Method", + aMul: "*", + aMulAssign: "*=", + aNeg: "-", + aNot: "!", + aNotEqual: "!=", + aOr: "|", + aOrAssign: "|=", + aPos: "+", + aQuo: "/", + aQuoAssign: "/=", + aRange: "range", + aRecv: "<-", + aRem: "%", + aRemAssign: "%=", + aReturn: "return", + aSend: "<~", + aShl: "<<", + aShlAssign: "<<=", + aShr: ">>", + aShrAssign: ">>=", + aSlice: "slice", + aSlice0: "slice0", + aStar: "*", + aSub: "-", + aSubAssign: "-=", + aTypeAssert: "TypeAssert", + aXor: "^", + aXorAssign: "^=", +} + +func (a action) String() string { + if a < action(len(actions)) { + return actions[a] + } + return "Action(" + strconv.Itoa(int(a)) + ")" +} + +func isAssignAction(a action) bool { + switch a { + case aAddAssign, aAndAssign, aAndNotAssign, aMulAssign, aOrAssign, + aQuoAssign, aRemAssign, aShlAssign, aShrAssign, aSubAssign, aXorAssign: + return true + } + return false +} + +func (interp *Interpreter) firstToken(src string) token.Token { + var s scanner.Scanner + file := interp.fset.AddFile("", interp.fset.Base(), len(src)) + s.Init(file, []byte(src), nil, 0) + + _, tok, _ := s.Scan() + return tok +} + +func ignoreError(err error, src string) bool { + se, ok := err.(scanner.ErrorList) + if !ok { + return false + } + if len(se) == 0 { + return false + } + return ignoreScannerError(se[0], src) +} + +func wrapInMain(src string) string { + return fmt.Sprintf("package main; func main() {%s\n}", src) +} + +func (interp *Interpreter) parse(src, name string, inc bool) (node ast.Node, err error) { + mode := parser.DeclarationErrors + + // Allow incremental parsing of declarations or statements, by inserting + // them in a pseudo file package or function. Those statements or + // declarations will be always evaluated in the global scope. + var tok token.Token + var inFunc bool + if inc { + tok = interp.firstToken(src) + switch tok { + case token.PACKAGE: + // nothing to do. + case token.CONST, token.FUNC, token.IMPORT, token.TYPE, token.VAR: + src = "package main;" + src + default: + inFunc = true + src = wrapInMain(src) + } + // Parse comments in REPL mode, to allow tag setting. + mode |= parser.ParseComments + } + + if ok, err := interp.buildOk(&interp.context, name, src); !ok || err != nil { + return nil, err // skip source not matching build constraints + } + + f, err := parser.ParseFile(interp.fset, name, src, mode) + if err != nil { + // only retry if we're on an expression/statement about a func + if !inc || tok != token.FUNC { + return nil, err + } + // do not bother retrying if we know it's an error we're going to ignore later on. + if ignoreError(err, src) { + return nil, err + } + // do not lose initial error, in case retrying fails. + initialError := err + // retry with default source code "wrapping", in the main function scope. + src := wrapInMain(strings.TrimPrefix(src, "package main;")) + f, err = parser.ParseFile(interp.fset, name, src, mode) + if err != nil { + return nil, initialError + } + } + + if inFunc { + // return the body of the wrapper main function + return f.Decls[0].(*ast.FuncDecl).Body, nil + } + + setYaegiTags(&interp.context, f.Comments) + return f, nil +} + +// Note: no type analysis is performed at this stage, it is done in pre-order +// processing of CFG, in order to accommodate forward type declarations. + +// ast parses src string containing Go code and generates the corresponding AST. +// The package name and the AST root node are returned. +// The given name is used to set the filename of the relevant source file in the +// interpreter's FileSet. +func (interp *Interpreter) ast(f ast.Node) (string, *node, error) { + var err error + var root *node + var anc astNode + var st nodestack + pkgName := "main" + + addChild := func(root **node, anc astNode, pos token.Pos, kind nkind, act action) *node { + var i interface{} + nindex := atomic.AddInt64(&interp.nindex, 1) + n := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: kind, action: act, val: &i, gen: builtin[act]} + n.start = n + if anc.node == nil { + *root = n + } else { + anc.node.child = append(anc.node.child, n) + if anc.node.action == aCase { + ancAst := anc.ast.(*ast.CaseClause) + if len(ancAst.List)+len(ancAst.Body) == len(anc.node.child) { + // All case clause children are collected. + // Split children in condition and body nodes to desambiguify the AST. + nindex = atomic.AddInt64(&interp.nindex, 1) + body := &node{anc: anc.node, interp: interp, index: nindex, pos: pos, kind: caseBody, action: aNop, val: &i, gen: nop} + + if ts := anc.node.anc.anc; ts.kind == typeSwitch && ts.child[1].action == aAssign { + // In type switch clause, if a switch guard is assigned, duplicate the switch guard symbol + // in each clause body, so a different guard type can be set in each clause + name := ts.child[1].child[0].ident + nindex = atomic.AddInt64(&interp.nindex, 1) + gn := &node{anc: body, interp: interp, ident: name, index: nindex, pos: pos, kind: identExpr, action: aNop, val: &i, gen: nop} + body.child = append(body.child, gn) + } + + // Add regular body children + body.child = append(body.child, anc.node.child[len(ancAst.List):]...) + for i := range body.child { + body.child[i].anc = body + } + anc.node.child = append(anc.node.child[:len(ancAst.List)], body) + } + } + } + return n + } + + // Populate our own private AST from Go parser AST. + // A stack of ancestor nodes is used to keep track of current ancestor for each depth level + ast.Inspect(f, func(nod ast.Node) bool { + anc = st.top() + var pos token.Pos + if nod != nil { + pos = nod.Pos() + } + switch a := nod.(type) { + case nil: + anc = st.pop() + + case *ast.ArrayType: + st.push(addChild(&root, anc, pos, arrayType, aNop), nod) + + case *ast.AssignStmt: + var act action + var kind nkind + if len(a.Lhs) > 1 && len(a.Rhs) == 1 { + if a.Tok == token.DEFINE { + kind = defineXStmt + } else { + kind = assignXStmt + } + act = aAssignX + } else { + kind = assignStmt + switch a.Tok { + case token.ASSIGN: + act = aAssign + case token.ADD_ASSIGN: + act = aAddAssign + case token.AND_ASSIGN: + act = aAndAssign + case token.AND_NOT_ASSIGN: + act = aAndNotAssign + case token.DEFINE: + kind = defineStmt + act = aAssign + case token.SHL_ASSIGN: + act = aShlAssign + case token.SHR_ASSIGN: + act = aShrAssign + case token.MUL_ASSIGN: + act = aMulAssign + case token.OR_ASSIGN: + act = aOrAssign + case token.QUO_ASSIGN: + act = aQuoAssign + case token.REM_ASSIGN: + act = aRemAssign + case token.SUB_ASSIGN: + act = aSubAssign + case token.XOR_ASSIGN: + act = aXorAssign + } + } + n := addChild(&root, anc, pos, kind, act) + n.nleft = len(a.Lhs) + n.nright = len(a.Rhs) + st.push(n, nod) + + case *ast.BasicLit: + n := addChild(&root, anc, pos, basicLit, aNop) + n.ident = a.Value + switch a.Kind { + case token.CHAR: + // Char cannot be converted to a const here as we cannot tell the type. + v, _, _, _ := strconv.UnquoteChar(a.Value[1:len(a.Value)-1], '\'') + n.rval = reflect.ValueOf(v) + case token.FLOAT, token.IMAG, token.INT, token.STRING: + v := constant.MakeFromLiteral(a.Value, a.Kind, 0) + n.rval = reflect.ValueOf(v) + } + st.push(n, nod) + + case *ast.BinaryExpr: + kind := binaryExpr + act := aNop + switch a.Op { + case token.ADD: + act = aAdd + case token.AND: + act = aAnd + case token.AND_NOT: + act = aAndNot + case token.EQL: + act = aEqual + case token.GEQ: + act = aGreaterEqual + case token.GTR: + act = aGreater + case token.LAND: + kind = landExpr + act = aLand + case token.LOR: + kind = lorExpr + act = aLor + case token.LEQ: + act = aLowerEqual + case token.LSS: + act = aLower + case token.MUL: + act = aMul + case token.NEQ: + act = aNotEqual + case token.OR: + act = aOr + case token.REM: + act = aRem + case token.SUB: + act = aSub + case token.SHL: + act = aShl + case token.SHR: + act = aShr + case token.QUO: + act = aQuo + case token.XOR: + act = aXor + } + st.push(addChild(&root, anc, pos, kind, act), nod) + + case *ast.BlockStmt: + st.push(addChild(&root, anc, pos, blockStmt, aNop), nod) + + case *ast.BranchStmt: + var kind nkind + switch a.Tok { + case token.BREAK: + kind = breakStmt + case token.CONTINUE: + kind = continueStmt + case token.FALLTHROUGH: + kind = fallthroughtStmt + case token.GOTO: + kind = gotoStmt + } + st.push(addChild(&root, anc, pos, kind, aNop), nod) + + case *ast.CallExpr: + action := aCall + if a.Ellipsis != token.NoPos { + action = aCallSlice + } + + st.push(addChild(&root, anc, pos, callExpr, action), nod) + + case *ast.CaseClause: + st.push(addChild(&root, anc, pos, caseClause, aCase), nod) + + case *ast.ChanType: + switch a.Dir { + case ast.SEND | ast.RECV: + st.push(addChild(&root, anc, pos, chanType, aNop), nod) + case ast.SEND: + st.push(addChild(&root, anc, pos, chanTypeSend, aNop), nod) + case ast.RECV: + st.push(addChild(&root, anc, pos, chanTypeRecv, aNop), nod) + } + + case *ast.CommClause: + kind := commClause + if a.Comm == nil { + kind = commClauseDefault + } + st.push(addChild(&root, anc, pos, kind, aNop), nod) + + case *ast.CommentGroup, *ast.EmptyStmt: + return false + + case *ast.CompositeLit: + st.push(addChild(&root, anc, pos, compositeLitExpr, aCompositeLit), nod) + + case *ast.DeclStmt: + st.push(addChild(&root, anc, pos, declStmt, aNop), nod) + + case *ast.DeferStmt: + st.push(addChild(&root, anc, pos, deferStmt, aNop), nod) + + case *ast.Ellipsis: + st.push(addChild(&root, anc, pos, ellipsisExpr, aNop), nod) + + case *ast.ExprStmt: + st.push(addChild(&root, anc, pos, exprStmt, aNop), nod) + + case *ast.Field: + st.push(addChild(&root, anc, pos, fieldExpr, aNop), nod) + + case *ast.FieldList: + st.push(addChild(&root, anc, pos, fieldList, aNop), nod) + + case *ast.File: + pkgName = a.Name.Name + st.push(addChild(&root, anc, pos, fileStmt, aNop), nod) + + case *ast.ForStmt: + // Disambiguate variants of FOR statements with a node kind per variant + var kind nkind + switch { + case a.Cond == nil && a.Init == nil && a.Post == nil: + kind = forStmt0 + case a.Cond == nil && a.Init != nil && a.Post == nil: + kind = forStmt1 + case a.Cond != nil && a.Init == nil && a.Post == nil: + kind = forStmt2 + case a.Cond != nil && a.Init != nil && a.Post == nil: + kind = forStmt3 + case a.Cond == nil && a.Init == nil && a.Post != nil: + kind = forStmt4 + case a.Cond != nil && a.Init == nil && a.Post != nil: + kind = forStmt5 + case a.Cond == nil && a.Init != nil && a.Post != nil: + kind = forStmt6 + case a.Cond != nil && a.Init != nil && a.Post != nil: + kind = forStmt7 + } + st.push(addChild(&root, anc, pos, kind, aNop), nod) + + case *ast.FuncDecl: + n := addChild(&root, anc, pos, funcDecl, aNop) + n.val = n + if a.Recv == nil { + // Function is not a method, create an empty receiver list. + addChild(&root, astNode{n, nod}, pos, fieldList, aNop) + } + st.push(n, nod) + + case *ast.FuncLit: + n := addChild(&root, anc, pos, funcLit, aGetFunc) + addChild(&root, astNode{n, nod}, pos, fieldList, aNop) + addChild(&root, astNode{n, nod}, pos, undefNode, aNop) + st.push(n, nod) + + case *ast.FuncType: + n := addChild(&root, anc, pos, funcType, aNop) + n.val = n + if a.TypeParams == nil { + // Function has no type parameters, create an empty fied list. + addChild(&root, astNode{n, nod}, pos, fieldList, aNop) + } + st.push(n, nod) + + case *ast.GenDecl: + var kind nkind + switch a.Tok { + case token.CONST: + kind = constDecl + case token.IMPORT: + kind = importDecl + case token.TYPE: + kind = typeDecl + case token.VAR: + kind = varDecl + } + st.push(addChild(&root, anc, pos, kind, aNop), nod) + + case *ast.GoStmt: + st.push(addChild(&root, anc, pos, goStmt, aNop), nod) + + case *ast.Ident: + n := addChild(&root, anc, pos, identExpr, aNop) + n.ident = a.Name + st.push(n, nod) + if n.anc.kind == defineStmt && n.anc.anc.kind == constDecl && n.anc.nright == 0 { + // Implicit assign expression (in a ConstDecl block). + // Clone assign source and type from previous + a := n.anc + pa := a.anc.child[childPos(a)-1] + + if len(pa.child) > pa.nleft+pa.nright { + // duplicate previous type spec + a.child = append(a.child, interp.dup(pa.child[a.nleft], a)) + } + + // duplicate previous assign right hand side + a.child = append(a.child, interp.dup(pa.lastChild(), a)) + a.nright++ + } + + case *ast.IfStmt: + // Disambiguate variants of IF statements with a node kind per variant + var kind nkind + switch { + case a.Init == nil && a.Else == nil: + kind = ifStmt0 + case a.Init == nil && a.Else != nil: + kind = ifStmt1 + case a.Else == nil: + kind = ifStmt2 + default: + kind = ifStmt3 + } + st.push(addChild(&root, anc, pos, kind, aNop), nod) + + case *ast.ImportSpec: + st.push(addChild(&root, anc, pos, importSpec, aNop), nod) + + case *ast.IncDecStmt: + var act action + switch a.Tok { + case token.INC: + act = aInc + case token.DEC: + act = aDec + } + st.push(addChild(&root, anc, pos, incDecStmt, act), nod) + + case *ast.IndexExpr: + st.push(addChild(&root, anc, pos, indexExpr, aGetIndex), nod) + + case *ast.IndexListExpr: + st.push(addChild(&root, anc, pos, indexListExpr, aNop), nod) + + case *ast.InterfaceType: + st.push(addChild(&root, anc, pos, interfaceType, aNop), nod) + + case *ast.KeyValueExpr: + st.push(addChild(&root, anc, pos, keyValueExpr, aNop), nod) + + case *ast.LabeledStmt: + st.push(addChild(&root, anc, pos, labeledStmt, aNop), nod) + + case *ast.MapType: + st.push(addChild(&root, anc, pos, mapType, aNop), nod) + + case *ast.ParenExpr: + st.push(addChild(&root, anc, pos, parenExpr, aNop), nod) + + case *ast.RangeStmt: + // Insert a missing ForRangeStmt for AST correctness + n := addChild(&root, anc, pos, forRangeStmt, aNop) + r := addChild(&root, astNode{n, nod}, pos, rangeStmt, aRange) + st.push(r, nod) + if a.Key == nil { + // range not in an assign expression: insert a "_" key variable to store iteration index + k := addChild(&root, astNode{r, nod}, pos, identExpr, aNop) + k.ident = "_" + } + + case *ast.ReturnStmt: + st.push(addChild(&root, anc, pos, returnStmt, aReturn), nod) + + case *ast.SelectStmt: + st.push(addChild(&root, anc, pos, selectStmt, aNop), nod) + + case *ast.SelectorExpr: + st.push(addChild(&root, anc, pos, selectorExpr, aGetIndex), nod) + + case *ast.SendStmt: + st.push(addChild(&root, anc, pos, sendStmt, aSend), nod) + + case *ast.SliceExpr: + if a.Low == nil { + st.push(addChild(&root, anc, pos, sliceExpr, aSlice0), nod) + } else { + st.push(addChild(&root, anc, pos, sliceExpr, aSlice), nod) + } + + case *ast.StarExpr: + st.push(addChild(&root, anc, pos, starExpr, aStar), nod) + + case *ast.StructType: + st.push(addChild(&root, anc, pos, structType, aNop), nod) + + case *ast.SwitchStmt: + if a.Tag == nil { + st.push(addChild(&root, anc, pos, switchIfStmt, aNop), nod) + } else { + st.push(addChild(&root, anc, pos, switchStmt, aNop), nod) + } + + case *ast.TypeAssertExpr: + st.push(addChild(&root, anc, pos, typeAssertExpr, aTypeAssert), nod) + + case *ast.TypeSpec: + if a.Assign.IsValid() { + st.push(addChild(&root, anc, pos, typeSpecAssign, aNop), nod) + break + } + st.push(addChild(&root, anc, pos, typeSpec, aNop), nod) + + case *ast.TypeSwitchStmt: + n := addChild(&root, anc, pos, typeSwitch, aNop) + st.push(n, nod) + if a.Init == nil { + // add an empty init node to disambiguate AST + addChild(&root, astNode{n, nil}, pos, fieldList, aNop) + } + + case *ast.UnaryExpr: + kind := unaryExpr + var act action + switch a.Op { + case token.ADD: + act = aPos + case token.AND: + kind = addressExpr + act = aAddr + case token.ARROW: + act = aRecv + case token.NOT: + act = aNot + case token.SUB: + act = aNeg + case token.XOR: + act = aBitNot + } + st.push(addChild(&root, anc, pos, kind, act), nod) + + case *ast.ValueSpec: + kind := valueSpec + act := aNop + switch { + case a.Values != nil: + if len(a.Names) > 1 && len(a.Values) == 1 { + if anc.node.kind == constDecl || anc.node.kind == varDecl { + kind = defineXStmt + } else { + kind = assignXStmt + } + act = aAssignX + } else { + if anc.node.kind == constDecl || anc.node.kind == varDecl { + kind = defineStmt + } else { + kind = assignStmt + } + act = aAssign + } + case anc.node.kind == constDecl: + kind, act = defineStmt, aAssign + case anc.node.kind == varDecl && anc.node.anc.kind != fileStmt: + kind, act = defineStmt, aAssign + } + n := addChild(&root, anc, pos, kind, act) + n.nleft = len(a.Names) + n.nright = len(a.Values) + st.push(n, nod) + + default: + err = astError(fmt.Errorf("ast: %T not implemented, line %s", a, interp.fset.Position(pos))) + return false + } + return true + }) + + interp.roots = append(interp.roots, root) + return pkgName, root, err +} + +type astNode struct { + node *node + ast ast.Node +} + +type nodestack []astNode + +func (s *nodestack) push(n *node, a ast.Node) { + *s = append(*s, astNode{n, a}) +} + +func (s *nodestack) pop() astNode { + l := len(*s) - 1 + res := (*s)[l] + *s = (*s)[:l] + return res +} + +func (s *nodestack) top() astNode { + l := len(*s) + if l > 0 { + return (*s)[l-1] + } + return astNode{} +} + +// dup returns a duplicated node subtree. +func (interp *Interpreter) dup(nod, anc *node) *node { + nindex := atomic.AddInt64(&interp.nindex, 1) + n := *nod + n.index = nindex + n.anc = anc + n.start = &n + n.pos = anc.pos + n.child = nil + for _, c := range nod.child { + n.child = append(n.child, interp.dup(c, &n)) + } + return &n +} diff --git a/internal/yaegi/interp/build.go b/internal/yaegi/interp/build.go new file mode 100644 index 00000000..4e5e8871 --- /dev/null +++ b/internal/yaegi/interp/build.go @@ -0,0 +1,203 @@ +package interp + +import ( + "go/ast" + "go/build" + "go/parser" + "path" + "path/filepath" + "strconv" + "strings" +) + +// buildOk returns true if a file or script matches build constraints +// as specified in https://golang.org/pkg/go/build/#hdr-Build_Constraints. +// An error from parser is returned as well. +func (interp *Interpreter) buildOk(ctx *build.Context, name, src string) (bool, error) { + // Extract comments before the first clause + f, err := parser.ParseFile(interp.fset, name, src, parser.PackageClauseOnly|parser.ParseComments) + if err != nil { + return false, err + } + for _, g := range f.Comments { + // in file, evaluate the AND of multiple line build constraints + for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") { + if !buildLineOk(ctx, line) { + return false, nil + } + } + } + setYaegiTags(ctx, f.Comments) + return true, nil +} + +// buildLineOk returns true if line is not a build constraint or +// if build constraint is satisfied. +func buildLineOk(ctx *build.Context, line string) (ok bool) { + if len(line) < 7 || line[:7] != "+build " { + return true + } + // In line, evaluate the OR of space-separated options + options := strings.Split(strings.TrimSpace(line[6:]), " ") + for _, o := range options { + if ok = buildOptionOk(ctx, o); ok { + break + } + } + return ok +} + +// buildOptionOk return true if all comma separated tags match, false otherwise. +func buildOptionOk(ctx *build.Context, tag string) bool { + // in option, evaluate the AND of individual tags + for _, t := range strings.Split(tag, ",") { + if !buildTagOk(ctx, t) { + return false + } + } + return true +} + +// buildTagOk returns true if a build tag matches, false otherwise +// if first character is !, result is negated. +func buildTagOk(ctx *build.Context, s string) (r bool) { + not := s[0] == '!' + if not { + s = s[1:] + } + switch { + case contains(ctx.BuildTags, s): + r = true + case s == ctx.GOOS: + r = true + case s == ctx.GOARCH: + r = true + case len(s) > 4 && s[:4] == "go1.": + if n, err := strconv.Atoi(s[4:]); err != nil { + r = false + } else { + r = goMinorVersion(ctx) >= n + } + } + if not { + r = !r + } + return +} + +// setYaegiTags scans a comment group for "yaegi:tags tag1 tag2 ..." lines +// and adds the corresponding tags to the interpreter build tags. +func setYaegiTags(ctx *build.Context, comments []*ast.CommentGroup) { + for _, g := range comments { + for _, line := range strings.Split(strings.TrimSpace(g.Text()), "\n") { + if len(line) < 11 || line[:11] != "yaegi:tags " { + continue + } + + tags := strings.Split(strings.TrimSpace(line[10:]), " ") + for _, tag := range tags { + if !contains(ctx.BuildTags, tag) { + ctx.BuildTags = append(ctx.BuildTags, tag) + } + } + } + } +} + +func contains(tags []string, tag string) bool { + for _, t := range tags { + if t == tag { + return true + } + } + return false +} + +// goMinorVersion returns the go minor version number. +func goMinorVersion(ctx *build.Context) int { + current := ctx.ReleaseTags[len(ctx.ReleaseTags)-1] + + v := strings.Split(current, ".") + if len(v) < 2 { + panic("unsupported Go version: " + current) + } + + m, err := strconv.Atoi(v[1]) + if err != nil { + panic("unsupported Go version: " + current) + } + return m +} + +// skipFile returns true if file should be skipped. +func skipFile(ctx *build.Context, p string, skipTest bool) bool { + if !strings.HasSuffix(p, ".go") { + return true + } + p = strings.TrimSuffix(path.Base(p), ".go") + if pp := filepath.Base(p); strings.HasPrefix(pp, "_") || strings.HasPrefix(pp, ".") { + return true + } + if skipTest && strings.HasSuffix(p, "_test") { + return true + } + i := strings.Index(p, "_") + if i < 0 { + return false + } + a := strings.Split(p[i+1:], "_") + last := len(a) - 1 + if last-1 >= 0 { + switch x, y := a[last-1], a[last]; { + case x == ctx.GOOS: + if knownArch[y] { + return y != ctx.GOARCH + } + return false + case knownOs[x] && knownArch[y]: + return true + case knownArch[y] && y != ctx.GOARCH: + return true + default: + return false + } + } + if x := a[last]; knownOs[x] && x != ctx.GOOS || knownArch[x] && x != ctx.GOARCH { + return true + } + return false +} + +var knownOs = map[string]bool{ + "aix": true, + "android": true, + "darwin": true, + "dragonfly": true, + "freebsd": true, + "illumos": true, + "ios": true, + "js": true, + "linux": true, + "netbsd": true, + "openbsd": true, + "plan9": true, + "solaris": true, + "wasip1": true, + "windows": true, +} + +var knownArch = map[string]bool{ + "386": true, + "amd64": true, + "arm": true, + "arm64": true, + "loong64": true, + "mips": true, + "mips64": true, + "mips64le": true, + "mipsle": true, + "ppc64": true, + "ppc64le": true, + "s390x": true, + "wasm": true, +} diff --git a/internal/yaegi/interp/cfg.go b/internal/yaegi/interp/cfg.go new file mode 100644 index 00000000..39133a4c --- /dev/null +++ b/internal/yaegi/interp/cfg.go @@ -0,0 +1,3155 @@ +package interp + +import ( + "fmt" + "go/constant" + "log" + "math" + "path/filepath" + "reflect" + "strings" + "unicode" +) + +// A cfgError represents an error during CFG build stage. +type cfgError struct { + *node + error +} + +func (c *cfgError) Error() string { return c.error.Error() } + +var constOp = map[action]func(*node){ + aAdd: addConst, + aSub: subConst, + aMul: mulConst, + aQuo: quoConst, + aRem: remConst, + aAnd: andConst, + aOr: orConst, + aShl: shlConst, + aShr: shrConst, + aAndNot: andNotConst, + aXor: xorConst, + aNot: notConst, + aBitNot: bitNotConst, + aNeg: negConst, + aPos: posConst, +} + +var constBltn = map[string]func(*node){ + bltnComplex: complexConst, + bltnImag: imagConst, + bltnReal: realConst, +} + +const nilIdent = "nil" + +func init() { + // Use init() to avoid initialization cycles for the following constant builtins. + constBltn[bltnAlignof] = alignof + constBltn[bltnOffsetof] = offsetof + constBltn[bltnSizeof] = sizeof +} + +// cfg generates a control flow graph (CFG) from AST (wiring successors in AST) +// and pre-compute frame sizes and indexes for all un-named (temporary) and named +// variables. A list of nodes of init functions is returned. +// Following this pass, the CFG is ready to run. +func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string) ([]*node, error) { + if sc == nil { + sc = interp.initScopePkg(importPath, pkgName) + } + check := typecheck{scope: sc} + var initNodes []*node + var err error + + baseName := filepath.Base(interp.fset.Position(root.pos).Filename) + + root.Walk(func(n *node) bool { + // Pre-order processing + if err != nil { + return false + } + if n.scope == nil { + n.scope = sc + } + switch n.kind { + case binaryExpr, unaryExpr, parenExpr: + if isBoolAction(n) { + break + } + // Gather assigned type if set, to give context for type propagation at post-order. + switch n.anc.kind { + case assignStmt, defineStmt: + a := n.anc + i := childPos(n) - a.nright + if i < 0 { + break + } + if len(a.child) > a.nright+a.nleft { + i-- + } + dest := a.child[i] + if dest.typ == nil { + break + } + if dest.typ.incomplete { + err = n.cfgErrorf("invalid type declaration") + return false + } + if !isInterface(dest.typ) { + // Interface type are not propagated, and will be resolved at post-order. + n.typ = dest.typ + } + case binaryExpr, unaryExpr, parenExpr: + n.typ = n.anc.typ + } + + case defineStmt: + // Determine type of variables initialized at declaration, so it can be propagated. + if n.nleft+n.nright == len(n.child) { + // No type was specified on the left hand side, it will resolved at post-order. + break + } + n.typ, err = nodeType(interp, sc, n.child[n.nleft]) + if err != nil { + break + } + for i := 0; i < n.nleft; i++ { + n.child[i].typ = n.typ + } + + case blockStmt: + if n.anc != nil && n.anc.kind == rangeStmt { + // For range block: ensure that array or map type is propagated to iterators + // prior to process block. We cannot perform this at RangeStmt pre-order because + // type of array like value is not yet known. This could be fixed in ast structure + // by setting array/map node as 1st child of ForRangeStmt instead of 3rd child of + // RangeStmt. The following workaround is less elegant but ok. + c := n.anc.child[1] + if c != nil && c.typ != nil && isSendChan(c.typ) { + err = c.cfgErrorf("invalid operation: range %s receive from send-only channel", c.ident) + return false + } + + if t := sc.rangeChanType(n.anc); t != nil { + // range over channel + e := n.anc.child[0] + index := sc.add(t.val) + sc.sym[e.ident] = &symbol{index: index, kind: varSym, typ: t.val} + e.typ = t.val + e.findex = index + n.anc.gen = rangeChan + } else { + // range over array or map + var ktyp, vtyp *itype + var k, v, o *node + if len(n.anc.child) == 4 { + k, v, o = n.anc.child[0], n.anc.child[1], n.anc.child[2] + } else { + k, o = n.anc.child[0], n.anc.child[1] + } + + switch o.typ.cat { + case valueT, linkedT: + typ := o.typ.rtype + if o.typ.cat == linkedT { + typ = o.typ.val.TypeOf() + } + switch typ.Kind() { + case reflect.Map: + n.anc.gen = rangeMap + ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil))) + sc.add(ityp) + ktyp = valueTOf(typ.Key()) + vtyp = valueTOf(typ.Elem()) + case reflect.String: + sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range + sc.add(sc.getType("int")) // Add a dummy type to store index for range + ktyp = sc.getType("int") + vtyp = sc.getType("rune") + case reflect.Array, reflect.Slice: + sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range + ktyp = sc.getType("int") + vtyp = valueTOf(typ.Elem()) + } + case mapT: + n.anc.gen = rangeMap + ityp := valueTOf(reflect.TypeOf((*reflect.MapIter)(nil))) + sc.add(ityp) + ktyp = o.typ.key + vtyp = o.typ.val + case ptrT: + ktyp = sc.getType("int") + vtyp = o.typ.val + if vtyp.cat == valueT { + vtyp = valueTOf(vtyp.rtype.Elem()) + } else { + vtyp = vtyp.val + } + case stringT: + sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range + sc.add(sc.getType("int")) // Add a dummy type to store index for range + ktyp = sc.getType("int") + vtyp = sc.getType("rune") + case arrayT, sliceT, variadicT: + sc.add(sc.getType("int")) // Add a dummy type to store array shallow copy for range + ktyp = sc.getType("int") + vtyp = o.typ.val + } + + kindex := sc.add(ktyp) + sc.sym[k.ident] = &symbol{index: kindex, kind: varSym, typ: ktyp} + k.typ = ktyp + k.findex = kindex + + if v != nil { + vindex := sc.add(vtyp) + sc.sym[v.ident] = &symbol{index: vindex, kind: varSym, typ: vtyp} + v.typ = vtyp + v.findex = vindex + } + } + } + + n.findex = -1 + n.val = nil + sc = sc.pushBloc() + // Pre-define symbols for labels defined in this block, so we are sure that + // they are already defined when met. + // TODO(marc): labels must be stored outside of symbols to avoid collisions. + for _, c := range n.child { + if c.kind != labeledStmt { + continue + } + label := c.child[0].ident + sym := &symbol{kind: labelSym, node: c, index: -1} + sc.sym[label] = sym + c.sym = sym + } + // If block is the body of a function, get declared variables in current scope. + // This is done in order to add the func signature symbols into sc.sym, + // as we will need them in post-processing. + if n.anc != nil && n.anc.kind == funcDecl { + for k, v := range sc.anc.sym { + sc.sym[k] = v + } + } + + case breakStmt, continueStmt, gotoStmt: + if len(n.child) == 0 { + break + } + // Handle labeled statements. + label := n.child[0].ident + if sym, _, ok := sc.lookup(label); ok { + if sym.kind != labelSym { + err = n.child[0].cfgErrorf("label %s not defined", label) + break + } + n.sym = sym + } else { + n.sym = &symbol{kind: labelSym, index: -1} + sc.sym[label] = n.sym + } + if n.kind == gotoStmt { + n.sym.from = append(n.sym.from, n) // To allow forward goto statements. + } + + case caseClause: + sc = sc.pushBloc() + if sn := n.anc.anc; sn.kind == typeSwitch && sn.child[1].action == aAssign { + // Type switch clause with a var defined in switch guard. + var typ *itype + if len(n.child) == 2 { + // 1 type in clause: define the var with this type in the case clause scope. + switch { + case n.child[0].ident == nilIdent: + typ = sc.getType("interface{}") + case !n.child[0].isType(sc): + err = n.cfgErrorf("%s is not a type", n.child[0].ident) + default: + typ, err = nodeType(interp, sc, n.child[0]) + } + } else { + // Define the var with the type in the switch guard expression. + typ = sn.child[1].child[1].child[0].typ + } + if err != nil { + return false + } + nod := n.lastChild().child[0] + index := sc.add(typ) + sc.sym[nod.ident] = &symbol{index: index, kind: varSym, typ: typ} + nod.findex = index + nod.typ = typ + } + + case commClauseDefault: + sc = sc.pushBloc() + + case commClause: + sc = sc.pushBloc() + if len(n.child) > 0 && n.child[0].action == aAssign { + ch := n.child[0].child[1].child[0] + var typ *itype + if typ, err = nodeType(interp, sc, ch); err != nil { + return false + } + if !isChan(typ) { + err = n.cfgErrorf("invalid operation: receive from non-chan type") + return false + } + elem := chanElement(typ) + assigned := n.child[0].child[0] + index := sc.add(elem) + sc.sym[assigned.ident] = &symbol{index: index, kind: varSym, typ: elem} + assigned.findex = index + assigned.typ = elem + } + + case compositeLitExpr: + if len(n.child) > 0 && n.child[0].isType(sc) { + // Get type from 1st child. + if n.typ, err = nodeType(interp, sc, n.child[0]); err != nil { + return false + } + // Indicate that the first child is the type. + n.nleft = 1 + } else { + // Get type from ancestor (implicit type). + if n.anc.kind == keyValueExpr && n == n.anc.child[0] { + n.typ = n.anc.typ.key + } else if atyp := n.anc.typ; atyp != nil { + if atyp.cat == valueT && hasElem(atyp.rtype) { + n.typ = valueTOf(atyp.rtype.Elem()) + } else { + n.typ = atyp.val + } + } + if n.typ == nil { + // A nil type indicates either an error or a generic type. + // A child indexExpr or indexListExpr is used for type parameters, + // it indicates an instanciated generic. + if n.child[0].kind != indexExpr && n.child[0].kind != indexListExpr { + err = n.cfgErrorf("undefined type") + return false + } + t0, err1 := nodeType(interp, sc, n.child[0].child[0]) + if err1 != nil { + return false + } + if t0.cat != genericT { + err = n.cfgErrorf("undefined type") + return false + } + // We have a composite literal of generic type, instantiate it. + lt := []*itype{} + for _, n1 := range n.child[0].child[1:] { + t1, err1 := nodeType(interp, sc, n1) + if err1 != nil { + return false + } + lt = append(lt, t1) + } + var g *node + g, _, err = genAST(sc, t0.node.anc, lt) + if err != nil { + return false + } + n.child[0] = g.lastChild() + n.typ, err = nodeType(interp, sc, n.child[0]) + if err != nil { + return false + } + // Generate methods if any. + for _, nod := range t0.method { + gm, _, err2 := genAST(nod.scope, nod, lt) + if err2 != nil { + err = err2 + return false + } + gm.typ, err = nodeType(interp, nod.scope, gm.child[2]) + if err != nil { + return false + } + if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil { + return false + } + if err = genRun(gm); err != nil { + return false + } + n.typ.addMethod(gm) + } + n.nleft = 1 // Indictate the type of composite literal. + } + } + + child := n.child + if n.nleft > 0 { + n.child[0].typ = n.typ + child = n.child[1:] + } + // Propagate type to children, to handle implicit types + for _, c := range child { + if isBlank(c) { + err = n.cfgErrorf("cannot use _ as value") + return false + } + switch c.kind { + case binaryExpr, unaryExpr, compositeLitExpr: + // Do not attempt to propagate composite type to operator expressions, + // it breaks constant folding. + case keyValueExpr, typeAssertExpr, indexExpr: + c.typ = n.typ + default: + if c.ident == nilIdent { + c.typ = sc.getType(nilIdent) + continue + } + if c.typ, err = nodeType(interp, sc, c); err != nil { + return false + } + } + } + + case forStmt0, forStmt1, forStmt2, forStmt3, forStmt4, forStmt5, forStmt6, forStmt7, forRangeStmt: + sc = sc.pushBloc() + sc.loop, sc.loopRestart = n, n.lastChild() + + case funcLit: + n.typ = nil // to force nodeType to recompute the type + if n.typ, err = nodeType(interp, sc, n); err != nil { + return false + } + n.findex = sc.add(n.typ) + fallthrough + + case funcDecl: + // Do not allow function declarations without body. + if len(n.child) < 4 { + err = n.cfgErrorf("missing function body") + return false + } + n.val = n + + // Skip substree in case of a generic function. + if len(n.child[2].child[0].child) > 0 { + return false + } + + // Skip subtree if the function is a method with a generic receiver. + if len(n.child[0].child) > 0 { + recvTypeNode := n.child[0].child[0].lastChild() + typ, err := nodeType(interp, sc, recvTypeNode) + if err != nil { + return false + } + if typ.cat == genericT || (typ.val != nil && typ.val.cat == genericT) { + return false + } + if typ.cat == ptrT { + rc0 := recvTypeNode.child[0] + rt0, err := nodeType(interp, sc, rc0) + if err != nil { + return false + } + if rc0.kind == indexExpr && rt0.cat == structT { + return false + } + } + } + + // Compute function type before entering local scope to avoid + // possible collisions with function argument names. + n.child[2].typ, err = nodeType(interp, sc, n.child[2]) + if err != nil { + return false + } + n.typ = n.child[2].typ + + // Add a frame indirection level as we enter in a func. + sc = sc.pushFunc() + sc.def = n + + // Allocate frame space for return values, define output symbols. + if len(n.child[2].child) == 3 { + for _, c := range n.child[2].child[2].child { + var typ *itype + if typ, err = nodeType(interp, sc, c.lastChild()); err != nil { + return false + } + if len(c.child) > 1 { + for _, cc := range c.child[:len(c.child)-1] { + sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ} + } + } else { + sc.add(typ) + } + } + } + + // Define receiver symbol. + if len(n.child[0].child) > 0 { + var typ *itype + fr := n.child[0].child[0] + recvTypeNode := fr.lastChild() + if typ, err = nodeType(interp, sc, recvTypeNode); err != nil { + return false + } + if typ.cat == nilT { + // This may happen when instantiating generic methods. + s2, _, ok := sc.lookup(typ.id()) + if !ok { + err = n.cfgErrorf("type not found: %s", typ.id()) + break + } + typ = s2.typ + if typ.cat == nilT { + err = n.cfgErrorf("nil type: %s", typ.id()) + break + } + } + recvTypeNode.typ = typ + n.child[2].typ.recv = typ + n.typ.recv = typ + index := sc.add(typ) + if len(fr.child) > 1 { + sc.sym[fr.child[0].ident] = &symbol{index: index, kind: varSym, typ: typ} + } + } + + // Define input parameter symbols. + for _, c := range n.child[2].child[1].child { + var typ *itype + if typ, err = nodeType(interp, sc, c.lastChild()); err != nil { + return false + } + for _, cc := range c.child[:len(c.child)-1] { + sc.sym[cc.ident] = &symbol{index: sc.add(typ), kind: varSym, typ: typ} + } + } + + if n.child[1].ident == "init" && len(n.child[0].child) == 0 { + initNodes = append(initNodes, n) + } + + case ifStmt0, ifStmt1, ifStmt2, ifStmt3: + sc = sc.pushBloc() + + case switchStmt, switchIfStmt, typeSwitch: + // Make sure default clause is in last position. + c := n.lastChild().child + if i, l := getDefault(n), len(c)-1; i >= 0 && i != l { + c[i], c[l] = c[l], c[i] + } + sc = sc.pushBloc() + sc.loop = n + + case importSpec: + // Already all done in GTA. + return false + + case typeSpec: + // Processing already done in GTA pass for global types, only parses inlined types. + if sc.def == nil { + return false + } + typeName := n.child[0].ident + var typ *itype + if typ, err = nodeType(interp, sc, n.child[1]); err != nil { + return false + } + if typ.incomplete { + // Type may still be incomplete in case of a local recursive struct declaration. + if typ, err = typ.finalize(); err != nil { + err = n.cfgErrorf("invalid type declaration") + return false + } + } + + switch n.child[1].kind { + case identExpr, selectorExpr: + n.typ = namedOf(typ, pkgName, typeName) + default: + n.typ = typ + n.typ.name = typeName + } + sc.sym[typeName] = &symbol{kind: typeSym, typ: n.typ} + return false + + case constDecl: + // Early parse of constDecl subtrees, to compute all constant + // values which may be used in further declarations. + if !sc.global { + for _, c := range n.child { + if _, err = interp.cfg(c, sc, importPath, pkgName); err != nil { + // No error processing here, to allow recovery in subtree nodes. + err = nil + } + } + } + + case arrayType, basicLit, chanType, chanTypeRecv, chanTypeSend, funcType, interfaceType, mapType, structType: + n.typ, err = nodeType(interp, sc, n) + return false + } + return true + }, func(n *node) { + // Post-order processing + if err != nil { + return + } + + defer func() { + if r := recover(); r != nil { + // Display the exact location in input source which triggered the panic + panic(n.cfgErrorf("CFG post-order panic: %v", r)) + } + }() + + switch n.kind { + case addressExpr: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + break + } + wireChild(n) + + err = check.addressExpr(n) + if err != nil { + break + } + + n.typ = ptrOf(n.child[0].typ) + n.findex = sc.add(n.typ) + + case assignStmt, defineStmt: + if n.anc.kind == typeSwitch && n.anc.child[1] == n { + // type switch guard assignment: assign dest to concrete value of src + n.gen = nop + break + } + + var atyp *itype + if n.nleft+n.nright < len(n.child) { + if atyp, err = nodeType(interp, sc, n.child[n.nleft]); err != nil { + break + } + } + + var sbase int + if n.nright > 0 { + sbase = len(n.child) - n.nright + } + + wireChild(n) + for i := 0; i < n.nleft; i++ { + dest, src := n.child[i], n.child[sbase+i] + updateSym := false + var sym *symbol + var level int + + if dest.rval.IsValid() && isConstType(dest.typ) { + err = n.cfgErrorf("cannot assign to %s (%s constant)", dest.rval, dest.typ.str) + break + } + if isBlank(src) { + err = n.cfgErrorf("cannot use _ as value") + break + } + if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") { + if atyp != nil { + dest.typ = atyp + } else { + if src.typ, err = nodeType(interp, sc, src); err != nil { + return + } + if src.typ.isBinMethod { + dest.typ = valueTOf(src.typ.methodCallType()) + } else { + // In a new definition, propagate the source type to the destination + // type. If the source is an untyped constant, make sure that the + // type matches a default type. + dest.typ = sc.fixType(src.typ) + } + } + if dest.typ.incomplete { + return + } + if sc.global { + // Do not overload existing symbols (defined in GTA) in global scope. + sym, _, _ = sc.lookup(dest.ident) + } + if sym == nil { + sym = &symbol{index: sc.add(dest.typ), kind: varSym, typ: dest.typ} + sc.sym[dest.ident] = sym + } + dest.val = src.val + dest.recv = src.recv + dest.findex = sym.index + updateSym = true + } else { + sym, level, _ = sc.lookup(dest.ident) + } + + err = check.assignExpr(n, dest, src) + if err != nil { + break + } + + if updateSym { + sym.typ = dest.typ + sym.rval = src.rval + // As we are updating the sym type, we need to update the sc.type + // when the sym has an index. + if sym.index >= 0 { + sc.types[sym.index] = sym.typ.frameType() + } + } + n.findex = dest.findex + n.level = dest.level + + // In the following, we attempt to optimize by skipping the assign + // operation and setting the source location directly to the destination + // location in the frame. + // + switch { + case n.action != aAssign: + // Do not skip assign operation if it is combined with another operator. + case src.rval.IsValid(): + // Do not skip assign operation if setting from a constant value. + case isMapEntry(dest): + // Setting a map entry requires an additional step, do not optimize. + // As we only write, skip the default useless getIndexMap dest action. + dest.gen = nop + case isFuncField(dest): + // Setting a struct field of function type requires an extra step. Do not optimize. + case isCall(src) && !isInterfaceSrc(dest.typ) && n.kind != defineStmt: + // Call action may perform the assignment directly. + if dest.typ.id() != src.typ.id() { + // Skip optimitization if returned type doesn't match assigned one. + break + } + n.gen = nop + src.level = level + src.findex = dest.findex + if src.typ.untyped && !dest.typ.untyped { + src.typ = dest.typ + } + case src.action == aRecv: + // Assign by reading from a receiving channel. + n.gen = nop + src.findex = dest.findex // Set recv address to LHS. + dest.typ = src.typ + case src.action == aCompositeLit: + if dest.typ.cat == valueT && dest.typ.rtype.Kind() == reflect.Interface { + // Skip optimisation for assigned interface. + break + } + if dest.action == aGetIndex || dest.action == aStar { + // Skip optimization, as it does not work when assigning to a struct field or a dereferenced pointer. + break + } + n.gen = nop + src.findex = dest.findex + src.level = level + case len(n.child) < 4 && n.kind != defineStmt && isArithmeticAction(src) && !isInterface(dest.typ): + // Optimize single assignments from some arithmetic operations. + src.typ = dest.typ + src.findex = dest.findex + src.level = level + n.gen = nop + case src.kind == basicLit: + // Assign to nil. + src.rval = reflect.New(dest.typ.TypeOf()).Elem() + case n.nright == 0: + n.gen = reset + } + + n.typ = dest.typ + if sym != nil { + sym.typ = n.typ + sym.recv = src.recv + } + + n.level = level + + if n.anc.kind == constDecl { + n.gen = nop + n.findex = notInFrame + if sym, _, ok := sc.lookup(dest.ident); ok { + sym.kind = constSym + } + if childPos(n) == len(n.anc.child)-1 { + sc.iota = 0 + } else { + sc.iota++ + } + } + } + + case incDecStmt: + err = check.unaryExpr(n) + if err != nil { + break + } + wireChild(n) + n.findex = n.child[0].findex + n.level = n.child[0].level + n.typ = n.child[0].typ + if sym, level, ok := sc.lookup(n.child[0].ident); ok { + sym.typ = n.typ + n.level = level + } + + case assignXStmt: + wireChild(n) + l := len(n.child) - 1 + switch lc := n.child[l]; lc.kind { + case callExpr: + if n.child[l-1].isType(sc) { + l-- + } + if r := lc.child[0].typ.numOut(); r != l { + err = n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, lc.child[0].name(), r) + } + if isBinCall(lc, sc) { + n.gen = nop + } else { + // TODO (marc): skip if no conversion or wrapping is needed. + n.gen = assignFromCall + } + case indexExpr: + lc.gen = getIndexMap2 + n.gen = nop + case typeAssertExpr: + if n.child[0].ident == "_" { + lc.gen = typeAssertStatus + } else { + lc.gen = typeAssertLong + } + n.gen = nop + case unaryExpr: + if lc.action == aRecv { + lc.gen = recv2 + n.gen = nop + } + } + + case defineXStmt: + wireChild(n) + if sc.def == nil { + // In global scope, type definition already handled by GTA. + break + } + err = compDefineX(sc, n) + + case binaryExpr: + wireChild(n) + nilSym := interp.universe.sym[nilIdent] + c0, c1 := n.child[0], n.child[1] + + err = check.binaryExpr(n) + if err != nil { + break + } + + switch n.action { + case aRem: + n.typ = c0.typ + case aShl, aShr: + if c0.typ.untyped { + break + } + n.typ = c0.typ + case aEqual, aNotEqual: + n.typ = sc.getType("bool") + if c0.sym == nilSym || c1.sym == nilSym { + if n.action == aEqual { + if c1.sym == nilSym { + n.gen = isNilChild(0) + } else { + n.gen = isNilChild(1) + } + } else { + n.gen = isNotNil + } + } + case aGreater, aGreaterEqual, aLower, aLowerEqual: + n.typ = sc.getType("bool") + } + if err != nil { + break + } + if n.typ == nil { + if n.typ, err = nodeType(interp, sc, n); err != nil { + break + } + } + if c0.rval.IsValid() && c1.rval.IsValid() && (!isInterface(n.typ)) && constOp[n.action] != nil { + n.typ.TypeOf() // Force compute of reflection type. + constOp[n.action](n) // Compute a constant result now rather than during exec. + } + switch { + case n.rval.IsValid(): + // This operation involved constants, and the result is already computed + // by constOp and available in n.rval. Nothing else to do at execution. + n.gen = nop + n.findex = notInFrame + case n.anc.kind == assignStmt && n.anc.action == aAssign && n.anc.nleft == 1: + // To avoid a copy in frame, if the result is to be assigned, store it directly + // at the frame location of destination. + dest := n.anc.child[childPos(n)-n.anc.nright] + n.typ = dest.typ + n.findex = dest.findex + n.level = dest.level + case n.anc.kind == returnStmt: + // To avoid a copy in frame, if the result is to be returned, store it directly + // at the frame location reserved for output arguments. + n.findex = childPos(n) + default: + // Allocate a new location in frame, and store the result here. + n.findex = sc.add(n.typ) + } + + case indexExpr: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + break + } + wireChild(n) + t := n.child[0].typ + for t.cat == linkedT { + t = t.val + } + switch t.cat { + case ptrT: + n.typ = t.val + if t.val.cat == valueT { + n.typ = valueTOf(t.val.rtype.Elem()) + } else { + n.typ = t.val.val + } + case stringT: + n.typ = sc.getType("byte") + case valueT: + if t.rtype.Kind() == reflect.String { + n.typ = sc.getType("byte") + } else { + n.typ = valueTOf(t.rtype.Elem()) + } + case funcT: + // A function indexed by a type means an instantiated generic function. + c1 := n.child[1] + if !c1.isType(sc) { + n.typ = t + return + } + g, found, err := genAST(sc, t.node.anc, []*itype{c1.typ}) + if err != nil { + return + } + if !found { + if _, err = interp.cfg(g, t.node.anc.scope, importPath, pkgName); err != nil { + return + } + // Generate closures for function body. + if err = genRun(g.child[3]); err != nil { + return + } + } + // Replace generic func node by instantiated one. + n.anc.child[childPos(n)] = g + n.typ = g.typ + return + case genericT: + name := t.id() + "[" + n.child[1].typ.id() + "]" + sym, _, ok := sc.lookup(name) + if !ok { + err = n.cfgErrorf("type not found: %s", name) + return + } + n.gen = nop + n.typ = sym.typ + return + case structT: + // A struct indexed by a Type means an instantiated generic struct. + name := t.name + "[" + n.child[1].ident + "]" + sym, _, ok := sc.lookup(name) + if ok { + n.typ = sym.typ + n.findex = sc.add(n.typ) + n.gen = nop + return + } + + default: + n.typ = t.val + } + n.findex = sc.add(n.typ) + typ := t.TypeOf() + if typ.Kind() == reflect.Map { + err = check.assignment(n.child[1], t.key, "map index") + n.gen = getIndexMap + break + } + + l := -1 + switch k := typ.Kind(); k { + case reflect.Array: + l = typ.Len() + fallthrough + case reflect.Slice, reflect.String: + n.gen = getIndexArray + case reflect.Ptr: + if typ2 := typ.Elem(); typ2.Kind() == reflect.Array { + l = typ2.Len() + n.gen = getIndexArray + } else { + err = n.cfgErrorf("type %v does not support indexing", typ) + } + default: + err = n.cfgErrorf("type is not an array, slice, string or map: %v", t.id()) + } + + err = check.index(n.child[1], l) + + case blockStmt: + wireChild(n) + if len(n.child) > 0 { + l := n.lastChild() + n.findex = l.findex + n.level = l.level + n.val = l.val + n.sym = l.sym + n.typ = l.typ + n.rval = l.rval + } + sc = sc.pop() + + case constDecl: + wireChild(n) + + case varDecl: + // Global varDecl do not need to be wired as this + // will be handled after cfg. + if n.anc.kind == fileStmt { + break + } + wireChild(n) + + case sendStmt: + if !isChan(n.child[0].typ) { + err = n.cfgErrorf("invalid operation: cannot send to non-channel %s", n.child[0].typ.id()) + break + } + fallthrough + + case declStmt, exprStmt: + wireChild(n) + l := n.lastChild() + n.findex = l.findex + n.level = l.level + n.val = l.val + n.sym = l.sym + n.typ = l.typ + n.rval = l.rval + + case breakStmt: + if len(n.child) == 0 { + n.tnext = sc.loop + break + } + if !n.hasAnc(n.sym.node) { + err = n.cfgErrorf("invalid break label %s", n.child[0].ident) + break + } + n.tnext = n.sym.node + + case continueStmt: + if len(n.child) == 0 { + n.tnext = sc.loopRestart + break + } + if !n.hasAnc(n.sym.node) { + err = n.cfgErrorf("invalid continue label %s", n.child[0].ident) + break + } + n.tnext = n.sym.node.child[1].lastChild().start + + case gotoStmt: + if n.sym.node == nil { + // It can be only due to a forward goto, to be resolved at labeledStmt. + // Invalid goto labels are catched at AST parsing. + break + } + n.tnext = n.sym.node.start + + case labeledStmt: + wireChild(n) + if len(n.child) > 1 { + n.start = n.child[1].start + } + for _, c := range n.sym.from { + c.tnext = n.start // Resolve forward goto. + } + + case callExpr: + for _, c := range n.child { + if isBlank(c) { + err = n.cfgErrorf("cannot use _ as value") + return + } + } + wireChild(n) + switch c0 := n.child[0]; { + case c0.kind == indexListExpr: + // Instantiate a generic function then call it. + fun := c0.child[0].sym.node + lt := []*itype{} + for _, c := range c0.child[1:] { + lt = append(lt, c.typ) + } + g, found, err := genAST(sc, fun, lt) + if err != nil { + return + } + if !found { + _, err = interp.cfg(g, fun.scope, importPath, pkgName) + if err != nil { + return + } + err = genRun(g.child[3]) // Generate closures for function body. + if err != nil { + return + } + } + n.child[0] = g + c0 = n.child[0] + wireChild(n) + if typ := c0.typ; len(typ.ret) > 0 { + n.typ = typ.ret[0] + if n.anc.kind == returnStmt && n.typ.id() == sc.def.typ.ret[0].id() { + // Store the result directly to the return value area of frame. + // It can be done only if no type conversion at return is involved. + n.findex = childPos(n) + } else { + n.findex = sc.add(n.typ) + for _, t := range typ.ret[1:] { + sc.add(t) + } + } + } else { + n.findex = notInFrame + } + + case isBuiltinCall(n, sc): + bname := c0.ident + err = check.builtin(bname, n, n.child[1:], n.action == aCallSlice) + if err != nil { + break + } + + n.gen = c0.sym.builtin + c0.typ = &itype{cat: builtinT, name: bname} + if n.typ, err = nodeType(interp, sc, n); err != nil { + return + } + switch { + case n.typ.cat == builtinT: + n.findex = notInFrame + n.val = nil + switch bname { + case "unsafe.alignOf", "unsafe.Offsetof", "unsafe.Sizeof": + n.gen = nop + } + case n.anc.kind == returnStmt: + // Store result directly to frame output location, to avoid a frame copy. + n.findex = 0 + case bname == "cap" && isInConstOrTypeDecl(n): + t := n.child[1].typ.TypeOf() + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + switch t.Kind() { + case reflect.Array, reflect.Chan: + capConst(n) + default: + err = n.cfgErrorf("cap argument is not an array or channel") + } + n.findex = notInFrame + n.gen = nop + case bname == "len" && isInConstOrTypeDecl(n): + t := n.child[1].typ.TypeOf() + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + switch t.Kind() { + case reflect.Array, reflect.Chan, reflect.String: + lenConst(n) + default: + err = n.cfgErrorf("len argument is not an array, channel or string") + } + n.findex = notInFrame + n.gen = nop + default: + n.findex = sc.add(n.typ) + } + if op, ok := constBltn[bname]; ok { + op(n) + } + + case c0.isType(sc): + // Type conversion expression + c1 := n.child[1] + switch len(n.child) { + case 1: + err = n.cfgErrorf("missing argument in conversion to %s", c0.typ.id()) + case 2: + err = check.conversion(c1, c0.typ) + default: + err = n.cfgErrorf("too many arguments in conversion to %s", c0.typ.id()) + } + if err != nil { + break + } + + n.action = aConvert + switch { + case isInterface(c0.typ) && !c1.isNil(): + // Convert to interface: just check that all required methods are defined by concrete type. + if !c1.typ.implements(c0.typ) { + err = n.cfgErrorf("type %v does not implement interface %v", c1.typ.id(), c0.typ.id()) + } + // Convert type to interface while keeping a reference to the original concrete type. + // besides type, the node value remains preserved. + n.gen = nop + t := *c0.typ + n.typ = &t + n.typ.val = c1.typ + n.findex = c1.findex + n.level = c1.level + n.val = c1.val + n.rval = c1.rval + case c1.rval.IsValid() && isConstType(c0.typ): + n.gen = nop + n.findex = notInFrame + n.typ = c0.typ + if c, ok := c1.rval.Interface().(constant.Value); ok { + i, _ := constant.Int64Val(constant.ToInt(c)) + n.rval = reflect.ValueOf(i).Convert(c0.typ.rtype) + } else { + n.rval = c1.rval.Convert(c0.typ.rtype) + } + default: + n.gen = convert + n.typ = c0.typ + n.findex = sc.add(n.typ) + } + + case isBinCall(n, sc): + err = check.arguments(n, n.child[1:], c0, n.action == aCallSlice) + if err != nil { + break + } + + n.gen = callBin + typ := c0.typ.rtype + if typ.NumOut() > 0 { + if funcType := c0.typ.val; funcType != nil { + // Use the original unwrapped function type, to allow future field and + // methods resolutions, otherwise impossible on the opaque bin type. + n.typ = funcType.ret[0] + n.findex = sc.add(n.typ) + for i := 1; i < len(funcType.ret); i++ { + sc.add(funcType.ret[i]) + } + } else { + n.typ = valueTOf(typ.Out(0)) + if n.anc.kind == returnStmt { + n.findex = childPos(n) + } else { + n.findex = sc.add(n.typ) + for i := 1; i < typ.NumOut(); i++ { + sc.add(valueTOf(typ.Out(i))) + } + } + } + } + + default: + // The call may be on a generic function. In that case, replace the + // generic function AST by an instantiated one before going further. + if isGeneric(c0.typ) { + fun := c0.typ.node.anc + var g *node + var types []*itype + var found bool + + // Infer type parameter from function call arguments. + if types, err = inferTypesFromCall(sc, fun, n.child[1:]); err != nil { + break + } + // Generate an instantiated AST from the generic function one. + if g, found, err = genAST(sc, fun, types); err != nil { + break + } + if !found { + // Compile the generated function AST, so it becomes part of the scope. + if _, err = interp.cfg(g, fun.scope, importPath, pkgName); err != nil { + break + } + // AST compilation part 2: Generate closures for function body. + if err = genRun(g.child[3]); err != nil { + break + } + } + n.child[0] = g + c0 = n.child[0] + } + + err = check.arguments(n, n.child[1:], c0, n.action == aCallSlice) + if err != nil { + break + } + + if c0.action == aGetFunc { + // Allocate a frame entry to store the anonymous function definition. + sc.add(c0.typ) + } + if typ := c0.typ; len(typ.ret) > 0 { + n.typ = typ.ret[0] + if n.anc.kind == returnStmt && n.typ.id() == sc.def.typ.ret[0].id() { + // Store the result directly to the return value area of frame. + // It can be done only if no type conversion at return is involved. + n.findex = childPos(n) + } else { + n.findex = sc.add(n.typ) + for _, t := range typ.ret[1:] { + sc.add(t) + } + } + } else { + n.findex = notInFrame + } + } + + case caseBody: + wireChild(n) + switch { + case typeSwichAssign(n) && len(n.child) > 1: + n.start = n.child[1].start + case len(n.child) == 0: + // Empty case body: jump to switch node (exit node). + n.start = n.anc.anc.anc + default: + n.start = n.child[0].start + } + + case caseClause: + sc = sc.pop() + + case commClauseDefault: + wireChild(n) + sc = sc.pop() + if len(n.child) == 0 { + return + } + n.start = n.child[0].start + n.lastChild().tnext = n.anc.anc // exit node is selectStmt + + case commClause: + wireChild(n) + sc = sc.pop() + if len(n.child) == 0 { + return + } + if len(n.child) > 1 { + n.start = n.child[1].start // Skip chan operation, performed by select + } + n.lastChild().tnext = n.anc.anc // exit node is selectStmt + + case compositeLitExpr: + wireChild(n) + + child := n.child + if n.nleft > 0 { + child = child[1:] + } + + switch n.typ.cat { + case arrayT, sliceT: + err = check.arrayLitExpr(child, n.typ) + case mapT: + err = check.mapLitExpr(child, n.typ.key, n.typ.val) + case structT: + err = check.structLitExpr(child, n.typ) + case valueT: + rtype := n.typ.rtype + switch rtype.Kind() { + case reflect.Struct: + err = check.structBinLitExpr(child, rtype) + case reflect.Map: + ktyp := valueTOf(rtype.Key()) + vtyp := valueTOf(rtype.Elem()) + err = check.mapLitExpr(child, ktyp, vtyp) + } + } + if err != nil { + break + } + + n.findex = sc.add(n.typ) + // TODO: Check that composite literal expr matches corresponding type + n.gen = compositeGenerator(n, n.typ, nil) + + case fallthroughtStmt: + if n.anc.kind != caseBody { + err = n.cfgErrorf("fallthrough statement out of place") + } + + case fileStmt: + wireChild(n, varDecl) + sc = sc.pop() + n.findex = notInFrame + + case forStmt0: // for {} + body := n.child[0] + n.start = body.start + body.tnext = n.start + sc = sc.pop() + + case forStmt1: // for init; ; {} + init, body := n.child[0], n.child[1] + n.start = init.start + init.tnext = body.start + body.tnext = n.start + sc = sc.pop() + + case forStmt2: // for cond {} + cond, body := n.child[0], n.child[1] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as for condition") + } + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + n.start = body.start + body.tnext = body.start + } + } else { + n.start = cond.start + cond.tnext = body.start + body.tnext = cond.start + } + setFNext(cond, n) + sc = sc.pop() + + case forStmt3: // for init; cond; {} + init, cond, body := n.child[0], n.child[1], n.child[2] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as for condition") + } + n.start = init.start + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + init.tnext = body.start + body.tnext = body.start + } else { + init.tnext = n + } + } else { + init.tnext = cond.start + body.tnext = cond.start + } + cond.tnext = body.start + setFNext(cond, n) + sc = sc.pop() + + case forStmt4: // for ; ; post {} + post, body := n.child[0], n.child[1] + n.start = body.start + post.tnext = body.start + body.tnext = post.start + sc = sc.pop() + + case forStmt5: // for ; cond; post {} + cond, post, body := n.child[0], n.child[1], n.child[2] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as for condition") + } + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + n.start = body.start + post.tnext = body.start + } + } else { + n.start = cond.start + post.tnext = cond.start + } + cond.tnext = body.start + setFNext(cond, n) + body.tnext = post.start + sc = sc.pop() + + case forStmt6: // for init; ; post {} + init, post, body := n.child[0], n.child[1], n.child[2] + n.start = init.start + init.tnext = body.start + body.tnext = post.start + post.tnext = body.start + sc = sc.pop() + + case forStmt7: // for init; cond; post {} + init, cond, post, body := n.child[0], n.child[1], n.child[2], n.child[3] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as for condition") + } + n.start = init.start + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + init.tnext = body.start + post.tnext = body.start + } else { + init.tnext = n + } + } else { + init.tnext = cond.start + post.tnext = cond.start + } + cond.tnext = body.start + setFNext(cond, n) + body.tnext = post.start + sc = sc.pop() + + case forRangeStmt: + n.start = n.child[0].start + setFNext(n.child[0], n) + sc = sc.pop() + + case funcDecl: + n.start = n.child[3].start + n.types, n.scope = sc.types, sc + sc = sc.pop() + funcName := n.child[1].ident + if sym := sc.sym[funcName]; !isMethod(n) && sym != nil && !isGeneric(sym.typ) { + sym.index = -1 // to force value to n.val + sym.typ = n.typ + sym.kind = funcSym + sym.node = n + } + + case funcLit: + n.types, n.scope = sc.types, sc + sc = sc.pop() + err = genRun(n) + + case deferStmt, goStmt: + wireChild(n) + + case identExpr: + if isKey(n) || isNewDefine(n, sc) { + break + } + if n.anc.kind == funcDecl && n.anc.child[1] == n { + // Dont process a function name identExpr. + break + } + + sym, level, found := sc.lookup(n.ident) + if !found { + if n.typ != nil { + // Node is a generic instance with an already populated type. + break + } + // retry with the filename, in case ident is a package name. + sym, level, found = sc.lookup(filepath.Join(n.ident, baseName)) + if !found { + err = n.cfgErrorf("undefined: %s", n.ident) + break + } + } + // Found symbol, populate node info + n.sym, n.typ, n.findex, n.level = sym, sym.typ, sym.index, level + if n.findex < 0 { + n.val = sym.node + } else { + switch { + case sym.kind == constSym && sym.rval.IsValid(): + n.rval = sym.rval + n.kind = basicLit + case n.ident == "iota": + n.rval = reflect.ValueOf(constant.MakeInt64(int64(sc.iota))) + n.kind = basicLit + case n.ident == nilIdent: + n.kind = basicLit + case sym.kind == binSym: + n.typ = sym.typ + n.rval = sym.rval + case sym.kind == bltnSym: + if n.anc.kind != callExpr { + err = n.cfgErrorf("use of builtin %s not in function call", n.ident) + } + } + } + if n.sym != nil { + n.recv = n.sym.recv + } + + case ifStmt0: // if cond {} + cond, tbody := n.child[0], n.child[1] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as if condition") + } + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + n.start = tbody.start + } + } else { + n.start = cond.start + cond.tnext = tbody.start + } + setFNext(cond, n) + tbody.tnext = n + sc = sc.pop() + + case ifStmt1: // if cond {} else {} + cond, tbody, fbody := n.child[0], n.child[1], n.child[2] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as if condition") + } + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test and the useless branch. + if cond.rval.Bool() { + n.start = tbody.start + } else { + n.start = fbody.start + } + } else { + n.start = cond.start + cond.tnext = tbody.start + setFNext(cond, fbody.start) + } + tbody.tnext = n + fbody.tnext = n + sc = sc.pop() + + case ifStmt2: // if init; cond {} + init, cond, tbody := n.child[0], n.child[1], n.child[2] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as if condition") + } + n.start = init.start + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + init.tnext = tbody.start + } else { + init.tnext = n + } + } else { + init.tnext = cond.start + cond.tnext = tbody.start + } + tbody.tnext = n + setFNext(cond, n) + sc = sc.pop() + + case ifStmt3: // if init; cond {} else {} + init, cond, tbody, fbody := n.child[0], n.child[1], n.child[2], n.child[3] + if !isBool(cond.typ) { + err = cond.cfgErrorf("non-bool used as if condition") + } + n.start = init.start + if cond.rval.IsValid() { + // Condition is known at compile time, bypass test. + if cond.rval.Bool() { + init.tnext = tbody.start + } else { + init.tnext = fbody.start + } + } else { + init.tnext = cond.start + cond.tnext = tbody.start + setFNext(cond, fbody.start) + } + tbody.tnext = n + fbody.tnext = n + sc = sc.pop() + + case keyValueExpr: + if isBlank(n.child[1]) { + err = n.cfgErrorf("cannot use _ as value") + break + } + wireChild(n) + + case landExpr: + if isBlank(n.child[0]) || isBlank(n.child[1]) { + err = n.cfgErrorf("cannot use _ as value") + break + } + n.start = n.child[0].start + n.child[0].tnext = n.child[1].start + setFNext(n.child[0], n) + n.child[1].tnext = n + n.typ = n.child[0].typ + n.findex = sc.add(n.typ) + if n.start.action == aNop { + n.start.gen = branch + } + + case lorExpr: + if isBlank(n.child[0]) || isBlank(n.child[1]) { + err = n.cfgErrorf("cannot use _ as value") + break + } + n.start = n.child[0].start + n.child[0].tnext = n + setFNext(n.child[0], n.child[1].start) + n.child[1].tnext = n + n.typ = n.child[0].typ + n.findex = sc.add(n.typ) + if n.start.action == aNop { + n.start.gen = branch + } + + case parenExpr: + wireChild(n) + c := n.lastChild() + n.findex = c.findex + n.level = c.level + n.typ = c.typ + n.rval = c.rval + + case rangeStmt: + if sc.rangeChanType(n) != nil { + n.start = n.child[1].start // Get chan + n.child[1].tnext = n // then go to range function + n.tnext = n.child[2].start // then go to range body + n.child[2].tnext = n // then body go to range function (loop) + n.child[0].gen = empty + } else { + var k, o, body *node + if len(n.child) == 4 { + k, o, body = n.child[0], n.child[2], n.child[3] + } else { + k, o, body = n.child[0], n.child[1], n.child[2] + } + n.start = o.start // Get array or map object + o.tnext = k.start // then go to iterator init + k.tnext = n // then go to range function + n.tnext = body.start // then go to range body + body.tnext = n // then body go to range function (loop) + k.gen = empty // init filled later by generator + } + + case returnStmt: + if len(n.child) > sc.def.typ.numOut() { + err = n.cfgErrorf("too many arguments to return") + break + } + for _, c := range n.child { + if isBlank(c) { + err = n.cfgErrorf("cannot use _ as value") + return + } + } + returnSig := sc.def.child[2] + if mustReturnValue(returnSig) { + nret := len(n.child) + if nret == 1 && isCall(n.child[0]) { + nret = n.child[0].child[0].typ.numOut() + } + if nret < sc.def.typ.numOut() { + err = n.cfgErrorf("not enough arguments to return") + break + } + } + wireChild(n) + n.tnext = nil + n.val = sc.def + for i, c := range n.child { + var typ *itype + typ, err = nodeType(interp, sc.upperLevel(), returnSig.child[2].fieldType(i)) + if err != nil { + return + } + // TODO(mpl): move any of that code to typecheck? + c.typ.node = c + if !c.typ.assignableTo(typ) { + err = c.cfgErrorf("cannot use %v (type %v) as type %v in return argument", c.ident, c.typ.cat, typ.cat) + return + } + if c.typ.cat == nilT { + // nil: Set node value to zero of return type + c.rval = reflect.New(typ.TypeOf()).Elem() + } + } + + case selectorExpr: + wireChild(n) + n.typ = n.child[0].typ + n.recv = n.child[0].recv + if n.typ == nil { + err = n.cfgErrorf("undefined type") + break + } + switch { + case n.typ.cat == binPkgT: + // Resolve binary package symbol: a type or a value + name := n.child[1].ident + pkg := n.child[0].sym.typ.path + if s, ok := interp.binPkg[pkg][name]; ok { + if isBinType(s) { + n.typ = valueTOf(s.Type().Elem()) + } else { + n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s))) + n.rval = s + if pkg == "unsafe" && (name == "AlignOf" || name == "Offsetof" || name == "Sizeof") { + n.sym = &symbol{kind: bltnSym, node: n, rval: s} + n.ident = pkg + "." + name + } + } + n.action = aGetSym + n.gen = nop + } else { + err = n.cfgErrorf("package %s \"%s\" has no symbol %s", n.child[0].ident, pkg, name) + } + case n.typ.cat == srcPkgT: + pkg, name := n.child[0].sym.typ.path, n.child[1].ident + // Resolve source package symbol + if sym, ok := interp.srcPkg[pkg][name]; ok { + n.findex = sym.index + if sym.global { + n.level = globalFrame + } + n.val = sym.node + n.gen = nop + n.action = aGetSym + n.typ = sym.typ + n.sym = sym + n.recv = sym.recv + n.rval = sym.rval + } else { + err = n.cfgErrorf("undefined selector: %s.%s", pkg, name) + } + case isStruct(n.typ) || isInterfaceSrc(n.typ): + // Find a matching field. + if ti := n.typ.lookupField(n.child[1].ident); len(ti) > 0 { + if isStruct(n.typ) { + // If a method of the same name exists, use it if it is shallower than the struct field. + // if method's depth is the same as field's, this is an error. + d := n.typ.methodDepth(n.child[1].ident) + if d >= 0 && d < len(ti) { + goto tryMethods + } + if d == len(ti) { + err = n.cfgErrorf("ambiguous selector: %s", n.child[1].ident) + break + } + } + n.val = ti + switch { + case isInterfaceSrc(n.typ): + n.typ = n.typ.fieldSeq(ti) + n.gen = getMethodByName + n.action = aMethod + case n.typ.cat == ptrT: + n.typ = n.typ.fieldSeq(ti) + n.gen = getPtrIndexSeq + if n.typ.cat == funcT { + // Function in a struct field is always wrapped in reflect.Value. + n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ) + } + default: + n.gen = getIndexSeq + n.typ = n.typ.fieldSeq(ti) + if n.typ.cat == funcT { + // Function in a struct field is always wrapped in reflect.Value. + n.typ = wrapperValueTOf(n.typ.TypeOf(), n.typ) + } + } + break + } + if s, lind, ok := n.typ.lookupBinField(n.child[1].ident); ok { + // Handle an embedded binary field into a struct field. + n.gen = getIndexSeqField + lind = append(lind, s.Index...) + if isStruct(n.typ) { + // If a method of the same name exists, use it if it is shallower than the struct field. + // if method's depth is the same as field's, this is an error. + d := n.typ.methodDepth(n.child[1].ident) + if d >= 0 && d < len(lind) { + goto tryMethods + } + if d == len(lind) { + err = n.cfgErrorf("ambiguous selector: %s", n.child[1].ident) + break + } + } + n.val = lind + n.typ = valueTOf(s.Type) + break + } + // No field (embedded or not) matched. Try to match a method. + tryMethods: + fallthrough + default: + err = matchSelectorMethod(sc, n) + } + if err == nil && n.findex != -1 && n.typ.cat != genericT { + n.findex = sc.add(n.typ) + } + + case selectStmt: + wireChild(n) + // Move action to block statement, so select node can be an exit point. + n.child[0].gen = _select + // Chain channel init actions in commClauses prior to invoking select. + var cur *node + for _, c := range n.child[0].child { + if c.kind == commClauseDefault { + // No channel init in this case. + continue + } + var an, pn *node // channel init action nodes + if len(c.child) > 0 { + switch c0 := c.child[0]; { + case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv: + an = c0.child[0].child[0] + pn = an + case c0.action == aAssign: + an = c0.lastChild().child[0] + pn = an + case c0.kind == sendStmt: + an = c0.child[0] + pn = c0.child[1] + } + } + if an == nil { + continue + } + if cur == nil { + // First channel init action, the entry point for the select block. + n.start = an.start + } else { + // Chain channel init action to the previous one. + cur.tnext = an.start + } + if pn != nil { + // Chain channect init action to send data init action. + // (already done by wireChild, but let's be explicit). + an.tnext = pn + cur = pn + } + } + if cur == nil { + // There is no channel init action, call select directly. + n.start = n.child[0] + } else { + // Select is called after the last channel init action. + cur.tnext = n.child[0] + } + + case starExpr: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + break + } + switch { + case n.anc.kind == defineStmt && len(n.anc.child) == 3 && n.anc.child[1] == n: + // pointer type expression in a var definition + n.gen = nop + case n.anc.kind == valueSpec && n.anc.lastChild() == n: + // pointer type expression in a value spec + n.gen = nop + case n.anc.kind == fieldExpr: + // pointer type expression in a field expression (arg or struct field) + n.gen = nop + case n.child[0].isType(sc): + // pointer type expression + n.gen = nop + n.typ = ptrOf(n.child[0].typ) + default: + // dereference expression + wireChild(n) + + err = check.starExpr(n.child[0]) + if err != nil { + break + } + + if c0 := n.child[0]; c0.typ.cat == valueT { + n.typ = valueTOf(c0.typ.rtype.Elem()) + } else { + n.typ = c0.typ.val + } + n.findex = sc.add(n.typ) + } + + case typeSwitch: + // Check that cases expressions are all different + usedCase := map[string]bool{} + for _, c := range n.lastChild().child { + for _, t := range c.child[:len(c.child)-1] { + tid := t.typ.id() + if usedCase[tid] { + err = c.cfgErrorf("duplicate case %s in type switch", t.ident) + return + } + usedCase[tid] = true + } + } + fallthrough + + case switchStmt: + sc = sc.pop() + sbn := n.lastChild() // switch block node + clauses := sbn.child + l := len(clauses) + if l == 0 { + // Switch is empty + break + } + // Chain case clauses. + for i := l - 1; i >= 0; i-- { + c := clauses[i] + if len(c.child) == 0 { + c.tnext = n // Clause body is empty, exit. + } else { + body := c.lastChild() + c.tnext = body.start + c.child[0].tnext = c + c.start = c.child[0].start + + if i < l-1 && len(body.child) > 0 && body.lastChild().kind == fallthroughtStmt { + if n.kind == typeSwitch { + err = body.lastChild().cfgErrorf("cannot fallthrough in type switch") + } + if len(clauses[i+1].child) == 0 { + body.tnext = n // Fallthrough to next with empty body, just exit. + } else { + body.tnext = clauses[i+1].lastChild().start + } + } else { + body.tnext = n // Exit switch at end of clause body. + } + } + + if i == l-1 { + setFNext(clauses[i], n) + continue + } + if len(clauses[i+1].child) > 1 { + setFNext(c, clauses[i+1].start) + } else { + setFNext(c, clauses[i+1]) + } + } + sbn.start = clauses[0].start + n.start = n.child[0].start + if n.kind == typeSwitch { + // Handle the typeSwitch init (the type assert expression). + init := n.child[1].lastChild().child[0] + init.tnext = sbn.start + n.child[0].tnext = init.start + } else { + n.child[0].tnext = sbn.start + } + + case switchIfStmt: // like an if-else chain + sc = sc.pop() + sbn := n.lastChild() // switch block node + clauses := sbn.child + l := len(clauses) + if l == 0 { + // Switch is empty + break + } + // Wire case clauses in reverse order so the next start node is already resolved when used. + for i := l - 1; i >= 0; i-- { + c := clauses[i] + c.gen = nop + if len(c.child) == 0 { + c.tnext = n + c.fnext = n + } else { + body := c.lastChild() + if len(c.child) > 1 { + cond := c.child[0] + cond.tnext = body.start + if i == l-1 { + setFNext(cond, n) + } else { + setFNext(cond, clauses[i+1].start) + } + c.start = cond.start + } else { + c.start = body.start + } + // If last case body statement is a fallthrough, then jump to next case body + if i < l-1 && len(body.child) > 0 && body.lastChild().kind == fallthroughtStmt { + body.tnext = clauses[i+1].lastChild().start + } else { + body.tnext = n + } + } + } + sbn.start = clauses[0].start + n.start = n.child[0].start + n.child[0].tnext = sbn.start + + case typeAssertExpr: + if len(n.child) == 1 { + // The "o.(type)" is handled by typeSwitch. + n.gen = nop + break + } + + wireChild(n) + c0, c1 := n.child[0], n.child[1] + if isBlank(c0) || isBlank(c1) { + err = n.cfgErrorf("cannot use _ as value") + break + } + if c1.typ == nil { + if c1.typ, err = nodeType(interp, sc, c1); err != nil { + return + } + } + + err = check.typeAssertionExpr(c0, c1.typ) + if err != nil { + break + } + + if n.anc.action != aAssignX { + if c0.typ.cat == valueT && isFunc(c1.typ) { + // Avoid special wrapping of interfaces and func types. + n.typ = valueTOf(c1.typ.TypeOf()) + } else { + n.typ = c1.typ + } + n.findex = sc.add(n.typ) + } + + case sliceExpr: + wireChild(n) + + err = check.sliceExpr(n) + if err != nil { + break + } + + if n.typ, err = nodeType(interp, sc, n); err != nil { + return + } + n.findex = sc.add(n.typ) + + case unaryExpr: + wireChild(n) + + err = check.unaryExpr(n) + if err != nil { + break + } + + n.typ = n.child[0].typ + if n.action == aRecv { + // Channel receive operation: set type to the channel data type + if n.typ.cat == valueT { + n.typ = valueTOf(n.typ.rtype.Elem()) + } else { + n.typ = n.typ.val + } + } + if n.typ == nil { + if n.typ, err = nodeType(interp, sc, n); err != nil { + return + } + } + + // TODO: Optimisation: avoid allocation if boolean branch op (i.e. '!' in an 'if' expr) + if n.child[0].rval.IsValid() && !isInterface(n.typ) && constOp[n.action] != nil { + n.typ.TypeOf() // init reflect type + constOp[n.action](n) + } + switch { + case n.rval.IsValid(): + n.gen = nop + n.findex = notInFrame + case n.anc.kind == assignStmt && n.anc.action == aAssign && n.anc.nright == 1: + dest := n.anc.child[childPos(n)-n.anc.nright] + n.typ = dest.typ + n.findex = dest.findex + n.level = dest.level + case n.anc.kind == returnStmt: + pos := childPos(n) + n.typ = sc.def.typ.ret[pos] + n.findex = pos + default: + n.findex = sc.add(n.typ) + } + + case valueSpec: + n.gen = reset + l := len(n.child) - 1 + if n.typ = n.child[l].typ; n.typ == nil { + if n.typ, err = nodeType(interp, sc, n.child[l]); err != nil { + return + } + } + + for _, c := range n.child[:l] { + var index int + if sc.global { + // Global object allocation is already performed in GTA. + index = sc.sym[c.ident].index + c.level = globalFrame + } else { + index = sc.add(n.typ) + sc.sym[c.ident] = &symbol{index: index, kind: varSym, typ: n.typ} + } + c.typ = n.typ + c.findex = index + } + } + }) + + if sc != interp.universe { + sc.pop() + } + return initNodes, err +} + +func compDefineX(sc *scope, n *node) error { + l := len(n.child) - 1 + types := []*itype{} + + switch src := n.child[l]; src.kind { + case callExpr: + funtype, err := nodeType(n.interp, sc, src.child[0]) + if err != nil { + return err + } + for funtype.cat == valueT && funtype.val != nil { + // Retrieve original interpreter type from a wrapped function. + // Struct fields of function types are always wrapped in valueT to ensure + // their possible use in runtime. In that case, the val field retains the + // original interpreter type, which is used now. + funtype = funtype.val + } + if funtype.cat == valueT { + // Handle functions imported from runtime. + for i := 0; i < funtype.rtype.NumOut(); i++ { + types = append(types, valueTOf(funtype.rtype.Out(i))) + } + } else { + types = funtype.ret + } + if n.anc.kind == varDecl && n.child[l-1].isType(sc) { + l-- + } + if len(types) != l { + return n.cfgErrorf("assignment mismatch: %d variables but %s returns %d values", l, src.child[0].name(), len(types)) + } + if isBinCall(src, sc) { + n.gen = nop + } else { + // TODO (marc): skip if no conversion or wrapping is needed. + n.gen = assignFromCall + } + + case indexExpr: + types = append(types, src.typ, sc.getType("bool")) + n.child[l].gen = getIndexMap2 + n.gen = nop + + case typeAssertExpr: + if n.child[0].ident == "_" { + n.child[l].gen = typeAssertStatus + } else { + n.child[l].gen = typeAssertLong + } + types = append(types, n.child[l].child[1].typ, sc.getType("bool")) + n.gen = nop + + case unaryExpr: + if n.child[l].action == aRecv { + types = append(types, src.typ, sc.getType("bool")) + n.child[l].gen = recv2 + n.gen = nop + } + + default: + return n.cfgErrorf("unsupported assign expression") + } + + // Handle redeclarations: find out new symbols vs existing ones. + symIsNew := map[string]bool{} + hasNewSymbol := false + for i := range types { + id := n.child[i].ident + if id == "_" || id == "" { + continue + } + if _, found := symIsNew[id]; found { + return n.cfgErrorf("%s repeated on left side of :=", id) + } + // A new symbol doesn't exist in current scope. Upper scopes are not + // taken into accout here, as a new symbol can shadow an existing one. + if _, found := sc.sym[id]; found { + symIsNew[id] = false + } else { + symIsNew[id] = true + hasNewSymbol = true + } + } + + for i, t := range types { + var index int + id := n.child[i].ident + // A variable can be redeclared if at least one other not blank variable is created. + // The redeclared variable must be of same type (it is reassigned, not created). + // Careful to not reuse a variable which has been shadowed (it must not be a newSym). + sym, level, ok := sc.lookup(id) + canRedeclare := hasNewSymbol && len(symIsNew) > 1 && !symIsNew[id] && ok + if canRedeclare && level == n.child[i].level && sym.kind == varSym && sym.typ.id() == t.id() { + index = sym.index + n.child[i].redeclared = true + } else { + index = sc.add(t) + sc.sym[id] = &symbol{index: index, kind: varSym, typ: t} + } + n.child[i].typ = t + n.child[i].findex = index + } + return nil +} + +// TODO used for allocation optimization, temporarily disabled +// func isAncBranch(n *node) bool { +// switch n.anc.kind { +// case If0, If1, If2, If3: +// return true +// } +// return false +// } + +func childPos(n *node) int { + for i, c := range n.anc.child { + if n == c { + return i + } + } + return -1 +} + +func (n *node) cfgErrorf(format string, a ...interface{}) *cfgError { + pos := n.interp.fset.Position(n.pos) + posString := n.interp.fset.Position(n.pos).String() + if pos.Filename == DefaultSourceName { + posString = strings.TrimPrefix(posString, DefaultSourceName+":") + } + a = append([]interface{}{posString}, a...) + return &cfgError{n, fmt.Errorf("%s: "+format, a...)} +} + +func genRun(nod *node) error { + var err error + seen := map[*node]bool{} + + nod.Walk(func(n *node) bool { + if err != nil || seen[n] { + return false + } + seen[n] = true + switch n.kind { + case funcType: + if len(n.anc.child) == 4 { + // function body entry point + setExec(n.anc.child[3].start) + } + // continue in function body as there may be inner function definitions + case constDecl, varDecl: + setExec(n.start) + return false + } + return true + }, nil) + + return err +} + +func genGlobalVars(roots []*node, sc *scope) (*node, error) { + var vars []*node + for _, n := range roots { + vars = append(vars, getVars(n)...) + } + + if len(vars) == 0 { + return nil, nil + } + + varNode, err := genGlobalVarDecl(vars, sc) + if err != nil { + return nil, err + } + setExec(varNode.start) + return varNode, nil +} + +func getVars(n *node) (vars []*node) { + for _, child := range n.child { + if child.kind == varDecl { + vars = append(vars, child.child...) + } + } + return vars +} + +func genGlobalVarDecl(nodes []*node, sc *scope) (*node, error) { + varNode := &node{kind: varDecl, action: aNop, gen: nop} + + deps := map[*node][]*node{} + for _, n := range nodes { + deps[n] = getVarDependencies(n, sc) + } + + inited := map[*node]bool{} + revisit := []*node{} + for { + for _, n := range nodes { + canInit := true + for _, d := range deps[n] { + if !inited[d] { + canInit = false + } + } + if !canInit { + revisit = append(revisit, n) + continue + } + + varNode.child = append(varNode.child, n) + inited[n] = true + } + + if len(revisit) == 0 || equalNodes(nodes, revisit) { + break + } + + nodes = revisit + revisit = []*node{} + } + + if len(revisit) > 0 { + return nil, revisit[0].cfgErrorf("variable definition loop") + } + wireChild(varNode) + return varNode, nil +} + +func getVarDependencies(nod *node, sc *scope) (deps []*node) { + nod.Walk(func(n *node) bool { + if n.kind != identExpr { + return true + } + // Process ident nodes, and avoid false dependencies. + if n.anc.kind == selectorExpr && childPos(n) == 1 { + return false + } + sym, _, ok := sc.lookup(n.ident) + if !ok { + return false + } + if sym.kind != varSym || !sym.global || sym.node == nod { + return false + } + deps = append(deps, sym.node) + return false + }, nil) + return deps +} + +// setFnext sets the cond fnext field to next, propagates it for parenthesis blocks +// and sets the action to branch. +func setFNext(cond, next *node) { + if cond.action == aNop { + cond.action = aBranch + cond.gen = branch + cond.fnext = next + } + if cond.kind == parenExpr { + setFNext(cond.lastChild(), next) + return + } + cond.fnext = next +} + +// GetDefault return the index of default case clause in a switch statement, or -1. +func getDefault(n *node) int { + for i, c := range n.lastChild().child { + switch len(c.child) { + case 0: + return i + case 1: + if c.child[0].kind == caseBody { + return i + } + } + } + return -1 +} + +func isBinType(v reflect.Value) bool { return v.IsValid() && v.Kind() == reflect.Ptr && v.IsNil() } + +// isType returns true if node refers to a type definition, false otherwise. +func (n *node) isType(sc *scope) bool { + switch n.kind { + case arrayType, chanType, chanTypeRecv, chanTypeSend, funcType, interfaceType, mapType, structType: + return true + case parenExpr, starExpr: + if len(n.child) == 1 { + return n.child[0].isType(sc) + } + case selectorExpr: + pkg, name := n.child[0].ident, n.child[1].ident + baseName := filepath.Base(n.interp.fset.Position(n.pos).Filename) + suffixedPkg := filepath.Join(pkg, baseName) + sym, _, ok := sc.lookup(suffixedPkg) + if !ok { + sym, _, ok = sc.lookup(pkg) + if !ok { + return false + } + } + if sym.kind != pkgSym { + return false + } + path := sym.typ.path + if p, ok := n.interp.binPkg[path]; ok && isBinType(p[name]) { + return true // Imported binary type + } + if p, ok := n.interp.srcPkg[path]; ok && p[name] != nil && p[name].kind == typeSym { + return true // Imported source type + } + case identExpr: + return sc.getType(n.ident) != nil + case indexExpr: + // Maybe a generic type. + sym, _, ok := sc.lookup(n.child[0].ident) + return ok && sym.kind == typeSym + } + return false +} + +// wireChild wires AST nodes for CFG in subtree. +func wireChild(n *node, exclude ...nkind) { + child := excludeNodeKind(n.child, exclude) + + // Set start node, in subtree (propagated to ancestors by post-order processing) + for _, c := range child { + switch c.kind { + case arrayType, chanType, chanTypeRecv, chanTypeSend, funcDecl, importDecl, mapType, basicLit, identExpr, typeDecl: + continue + default: + n.start = c.start + } + break + } + + // Chain sequential operations inside a block (next is right sibling) + for i := 1; i < len(child); i++ { + switch child[i].kind { + case funcDecl: + child[i-1].tnext = child[i] + default: + switch child[i-1].kind { + case breakStmt, continueStmt, gotoStmt, returnStmt: + // tnext is already computed, no change + default: + child[i-1].tnext = child[i].start + } + } + } + + // Chain subtree next to self + for i := len(child) - 1; i >= 0; i-- { + switch child[i].kind { + case arrayType, chanType, chanTypeRecv, chanTypeSend, importDecl, mapType, funcDecl, basicLit, identExpr, typeDecl: + continue + case breakStmt, continueStmt, gotoStmt, returnStmt: + // tnext is already computed, no change + default: + child[i].tnext = n + } + break + } +} + +func excludeNodeKind(child []*node, kinds []nkind) []*node { + if len(kinds) == 0 { + return child + } + var res []*node + for _, c := range child { + exclude := false + for _, k := range kinds { + if c.kind == k { + exclude = true + } + } + if !exclude { + res = append(res, c) + } + } + return res +} + +func (n *node) name() (s string) { + switch { + case n.ident != "": + s = n.ident + case n.action == aGetSym: + s = n.child[0].ident + "." + n.child[1].ident + } + return s +} + +// isNatural returns true if node type is natural, false otherwise. +func (n *node) isNatural() bool { + if isUint(n.typ.TypeOf()) { + return true + } + if n.rval.IsValid() { + t := n.rval.Type() + if isUint(t) { + return true + } + if isInt(t) && n.rval.Int() >= 0 { + // positive untyped integer constant is ok + return true + } + if isFloat(t) { + // positive untyped float constant with null decimal part is ok + f := n.rval.Float() + if f == math.Trunc(f) && f >= 0 { + n.rval = reflect.ValueOf(uint(f)) + n.typ.rtype = n.rval.Type() + return true + } + } + if isConstantValue(t) { + c := n.rval.Interface().(constant.Value) + switch c.Kind() { + case constant.Int: + i, _ := constant.Int64Val(c) + if i >= 0 { + return true + } + case constant.Float: + f, _ := constant.Float64Val(c) + if f == math.Trunc(f) { + n.rval = reflect.ValueOf(constant.ToInt(c)) + n.typ.rtype = n.rval.Type() + return true + } + } + } + } + return false +} + +// isNil returns true if node is a literal nil value, false otherwise. +func (n *node) isNil() bool { return n.kind == basicLit && !n.rval.IsValid() } + +// fieldType returns the nth parameter field node (type) of a fieldList node. +func (n *node) fieldType(m int) *node { + k := 0 + l := len(n.child) + for i := 0; i < l; i++ { + cl := len(n.child[i].child) + if cl < 2 { + if k == m { + return n.child[i].lastChild() + } + k++ + continue + } + for j := 0; j < cl-1; j++ { + if k == m { + return n.child[i].lastChild() + } + k++ + } + } + return nil +} + +// lastChild returns the last child of a node. +func (n *node) lastChild() *node { return n.child[len(n.child)-1] } + +func (n *node) hasAnc(nod *node) bool { + for a := n.anc; a != nil; a = a.anc { + if a == nod { + return true + } + } + return false +} + +func isKey(n *node) bool { + return n.anc.kind == fileStmt || + (n.anc.kind == selectorExpr && n.anc.child[0] != n) || + (n.anc.kind == funcDecl && isMethod(n.anc)) || + (n.anc.kind == keyValueExpr && isStruct(n.anc.typ) && n.anc.child[0] == n) || + (n.anc.kind == fieldExpr && len(n.anc.child) > 1 && n.anc.child[0] == n) +} + +func isField(n *node) bool { + return n.kind == selectorExpr && len(n.child) > 0 && n.child[0].typ != nil && isStruct(n.child[0].typ) +} + +func isInInterfaceType(n *node) bool { + anc := n.anc + for anc != nil { + if anc.kind == interfaceType { + return true + } + anc = anc.anc + } + return false +} + +func isInConstOrTypeDecl(n *node) bool { + anc := n.anc + for anc != nil { + switch anc.kind { + case constDecl, typeDecl, arrayType, chanType: + return true + case varDecl, funcDecl: + return false + } + anc = anc.anc + } + return false +} + +// isNewDefine returns true if node refers to a new definition. +func isNewDefine(n *node, sc *scope) bool { + if n.ident == "_" { + return true + } + if (n.anc.kind == defineXStmt || n.anc.kind == defineStmt || n.anc.kind == valueSpec) && childPos(n) < n.anc.nleft { + return true + } + if n.anc.kind == rangeStmt { + if n.anc.child[0] == n { + return true // array or map key, or chan element + } + if sc.rangeChanType(n.anc) == nil && n.anc.child[1] == n && len(n.anc.child) == 4 { + return true // array or map value + } + return false // array, map or channel are always pre-defined in range expression + } + return false +} + +func isMethod(n *node) bool { + return len(n.child[0].child) > 0 // receiver defined +} + +func isFuncField(n *node) bool { + return isField(n) && isFunc(n.typ) +} + +func isMapEntry(n *node) bool { + return n.action == aGetIndex && isMap(n.child[0].typ) +} + +func isCall(n *node) bool { + return n.action == aCall || n.action == aCallSlice +} + +func isBinCall(n *node, sc *scope) bool { + if !isCall(n) || len(n.child) == 0 { + return false + } + c0 := n.child[0] + if c0.typ == nil { + // If called early in parsing, child type may not be known yet. + c0.typ, _ = nodeType(n.interp, sc, c0) + if c0.typ == nil { + return false + } + } + return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func +} + +func mustReturnValue(n *node) bool { + if len(n.child) < 3 { + return false + } + for _, f := range n.child[2].child { + if len(f.child) > 1 { + return false + } + } + return true +} + +func isRegularCall(n *node) bool { + return isCall(n) && n.child[0].typ.cat == funcT +} + +func variadicPos(n *node) int { + if len(n.child[0].typ.arg) == 0 { + return -1 + } + last := len(n.child[0].typ.arg) - 1 + if n.child[0].typ.arg[last].cat == variadicT { + return last + } + return -1 +} + +func canExport(name string) bool { + if r := []rune(name); len(r) > 0 && unicode.IsUpper(r[0]) { + return true + } + return false +} + +func getExec(n *node) bltn { + if n == nil { + return nil + } + if n.exec == nil { + setExec(n) + } + return n.exec +} + +// setExec recursively sets the node exec builtin function by walking the CFG +// from the entry point (first node to exec). +func setExec(n *node) { + if n.exec != nil { + return + } + seen := map[*node]bool{} + var set func(n *node) + + set = func(n *node) { + if n == nil || n.exec != nil { + return + } + seen[n] = true + if n.tnext != nil && n.tnext.exec == nil { + if seen[n.tnext] { + m := n.tnext + n.tnext.exec = func(f *frame) bltn { return m.exec(f) } + } else { + set(n.tnext) + } + } + if n.fnext != nil && n.fnext.exec == nil { + if seen[n.fnext] { + m := n.fnext + n.fnext.exec = func(f *frame) bltn { return m.exec(f) } + } else { + set(n.fnext) + } + } + n.gen(n) + } + + set(n) +} + +func typeSwichAssign(n *node) bool { + ts := n.anc.anc.anc + return ts.kind == typeSwitch && ts.child[1].action == aAssign +} + +func compositeGenerator(n *node, typ *itype, rtyp reflect.Type) (gen bltnGenerator) { + switch typ.cat { + case linkedT, ptrT: + gen = compositeGenerator(n, typ.val, rtyp) + case arrayT, sliceT: + gen = arrayLit + case mapT: + gen = mapLit + case structT: + switch { + case len(n.child) == 0: + gen = compositeLitNotype + case n.lastChild().kind == keyValueExpr: + if n.nleft == 1 { + gen = compositeLitKeyed + } else { + gen = compositeLitKeyedNotype + } + default: + if n.nleft == 1 { + gen = compositeLit + } else { + gen = compositeLitNotype + } + } + case valueT: + if rtyp == nil { + rtyp = n.typ.TypeOf() + } + switch k := rtyp.Kind(); k { + case reflect.Struct: + if n.nleft == 1 { + gen = compositeBinStruct + } else { + gen = compositeBinStructNotype + } + case reflect.Map: + // TODO(mpl): maybe needs a NoType version too + gen = compositeBinMap + case reflect.Ptr: + gen = compositeGenerator(n, typ, n.typ.val.rtype) + case reflect.Slice, reflect.Array: + gen = compositeBinSlice + default: + log.Panic(n.cfgErrorf("compositeGenerator not implemented for type kind: %s", k)) + } + } + return gen +} + +// matchSelectorMethod, given that n represents a selector for a method, tries +// to find the corresponding method, and populates n accordingly. +func matchSelectorMethod(sc *scope, n *node) (err error) { + name := n.child[1].ident + if n.typ.cat == valueT || n.typ.cat == errorT { + switch method, ok := n.typ.rtype.MethodByName(name); { + case ok: + hasRecvType := n.typ.TypeOf().Kind() != reflect.Interface + n.val = method.Index + n.gen = getIndexBinMethod + n.action = aGetMethod + n.recv = &receiver{node: n.child[0]} + n.typ = valueTOf(method.Type, isBinMethod()) + if hasRecvType { + n.typ.recv = n.typ + } + case n.typ.TypeOf().Kind() == reflect.Ptr: + if field, ok := n.typ.rtype.Elem().FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getPtrIndexSeq + break + } + err = n.cfgErrorf("undefined method: %s", name) + case n.typ.TypeOf().Kind() == reflect.Struct: + if field, ok := n.typ.rtype.FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getIndexSeq + break + } + fallthrough + default: + // method lookup failed on type, now lookup on pointer to type + pt := reflect.PtrTo(n.typ.rtype) + if m2, ok2 := pt.MethodByName(name); ok2 { + n.val = m2.Index + n.gen = getIndexBinPtrMethod + n.typ = valueTOf(m2.Type, isBinMethod(), withRecv(valueTOf(pt))) + n.recv = &receiver{node: n.child[0]} + n.action = aGetMethod + break + } + err = n.cfgErrorf("undefined method: %s", name) + } + return err + } + + if n.typ.cat == ptrT && (n.typ.val.cat == valueT || n.typ.val.cat == errorT) { + // Handle pointer on object defined in runtime + if method, ok := n.typ.val.rtype.MethodByName(name); ok { + n.val = method.Index + n.typ = valueTOf(method.Type, isBinMethod(), withRecv(n.typ)) + n.recv = &receiver{node: n.child[0]} + n.gen = getIndexBinElemMethod + n.action = aGetMethod + } else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(name); ok { + n.val = method.Index + n.gen = getIndexBinMethod + n.typ = valueTOf(method.Type, withRecv(valueTOf(reflect.PtrTo(n.typ.val.rtype), isBinMethod()))) + n.recv = &receiver{node: n.child[0]} + n.action = aGetMethod + } else if field, ok := n.typ.val.rtype.FieldByName(name); ok { + n.typ = valueTOf(field.Type) + n.val = field.Index + n.gen = getPtrIndexSeq + } else { + err = n.cfgErrorf("undefined selector: %s", name) + } + return err + } + + if m, lind := n.typ.lookupMethod(name); m != nil { + n.action = aGetMethod + if n.child[0].isType(sc) { + // Handle method as a function with receiver in 1st argument. + n.val = m + n.findex = notInFrame + n.gen = nop + n.typ = &itype{} + *n.typ = *m.typ + n.typ.arg = append([]*itype{n.child[0].typ}, m.typ.arg...) + } else { + // Handle method with receiver. + n.gen = getMethod + n.val = m + n.typ = m.typ + n.recv = &receiver{node: n.child[0], index: lind} + } + return nil + } + + if m, lind, isPtr, ok := n.typ.lookupBinMethod(name); ok { + n.action = aGetMethod + switch { + case isPtr && n.typ.fieldSeq(lind).cat != ptrT: + n.gen = getIndexSeqPtrMethod + case isInterfaceSrc(n.typ): + n.gen = getMethodByName + default: + n.gen = getIndexSeqMethod + } + n.recv = &receiver{node: n.child[0], index: lind} + n.val = append([]int{m.Index}, lind...) + n.typ = valueTOf(m.Type, isBinMethod(), withRecv(n.child[0].typ)) + return nil + } + + if typ := n.typ.interfaceMethod(name); typ != nil { + n.typ = typ + n.action = aGetMethod + n.gen = getMethodByName + return nil + } + + return n.cfgErrorf("undefined selector: %s", name) +} + +// arrayTypeLen returns the node's array length. If the expression is an +// array variable it is determined from the value's type, otherwise it is +// computed from the source definition. +func arrayTypeLen(n *node, sc *scope) (int, error) { + if n.typ != nil && n.typ.cat == arrayT { + return n.typ.length, nil + } + max := -1 + for _, c := range n.child[1:] { + var r int + + if c.kind != keyValueExpr { + r = max + 1 + max = r + continue + } + + c0 := c.child[0] + v := c0.rval + if v.IsValid() { + r = int(v.Int()) + } else { + // Resolve array key value as a constant. + if c0.kind == identExpr { + // Key is defined by a symbol which must be a constant integer. + sym, _, ok := sc.lookup(c0.ident) + if !ok { + return 0, c0.cfgErrorf("undefined: %s", c0.ident) + } + if sym.kind != constSym { + return 0, c0.cfgErrorf("non-constant array bound %q", c0.ident) + } + r = int(vInt(sym.rval)) + } else { + // Key is defined by a numeric constant expression. + if _, err := c0.interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil { + return 0, err + } + cv, ok := c0.rval.Interface().(constant.Value) + if !ok { + return 0, c0.cfgErrorf("non-constant expression") + } + r = constToInt(cv) + } + } + + if r > max { + max = r + } + } + return max + 1, nil +} + +// isValueUntyped returns true if value is untyped. +func isValueUntyped(v reflect.Value) bool { + // Consider only constant values. + if v.CanSet() { + return false + } + return v.Type().Implements(constVal) +} + +// isArithmeticAction returns true if the node action is an arithmetic operator. +func isArithmeticAction(n *node) bool { + switch n.action { + case aAdd, aAnd, aAndNot, aBitNot, aMul, aNeg, aOr, aPos, aQuo, aRem, aShl, aShr, aSub, aXor: + return true + } + return false +} + +func isBoolAction(n *node) bool { + switch n.action { + case aEqual, aGreater, aGreaterEqual, aLand, aLor, aLower, aLowerEqual, aNot, aNotEqual: + return true + } + return false +} + +func isBlank(n *node) bool { + if n.kind == parenExpr && len(n.child) > 0 { + return isBlank(n.child[0]) + } + return n.ident == "_" +} + +func alignof(n *node) { + n.gen = nop + n.typ = n.scope.getType("uintptr") + n.rval = reflect.ValueOf(uintptr(n.child[1].typ.TypeOf().Align())) +} + +func offsetof(n *node) { + n.gen = nop + n.typ = n.scope.getType("uintptr") + c1 := n.child[1] + if field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident); ok { + n.rval = reflect.ValueOf(field.Offset) + } +} + +func sizeof(n *node) { + n.gen = nop + n.typ = n.scope.getType("uintptr") + n.rval = reflect.ValueOf(n.child[1].typ.TypeOf().Size()) +} diff --git a/internal/yaegi/interp/debugger.go b/internal/yaegi/interp/debugger.go new file mode 100644 index 00000000..b6d8a9f0 --- /dev/null +++ b/internal/yaegi/interp/debugger.go @@ -0,0 +1,726 @@ +package interp + +import ( + "context" + "errors" + "fmt" + "go/token" + "reflect" + "sort" + "sync" +) + +var ( + // ErrNotLive indicates that the specified ID does not refer to a (live) Go + // routine. + ErrNotLive = errors.New("not live") + + // ErrRunning indicates that the specified Go routine is running. + ErrRunning = errors.New("running") + + // ErrNotRunning indicates that the specified Go routine is running. + ErrNotRunning = errors.New("not running") +) + +var rNodeType = reflect.TypeOf((*node)(nil)).Elem() + +// A Debugger can be used to debug a Yaegi program. +type Debugger struct { + interp *Interpreter + events func(*DebugEvent) + context context.Context + cancel context.CancelFunc + + gWait *sync.WaitGroup + gLock *sync.Mutex + gID int + gLive map[int]*debugRoutine + + result reflect.Value + err error +} + +// go routine debug state. +type debugRoutine struct { + id int + + mode DebugEventReason + running bool + resume chan struct{} + + fDepth int + fStep int +} + +// node debug state. +type nodeDebugData struct { + program *Program + breakOnLine bool + breakOnCall bool +} + +// frame debug state. +type frameDebugData struct { + g *debugRoutine + node *node + name string + kind frameKind + scope *scope +} + +// frame kind. +type frameKind int + +const ( + // interpreter root frame. + frameRoot frameKind = iota + 1 + + // function call frame. + frameCall + + // closure capture frame. + frameClosure +) + +// DebugOptions are the debugger options. +type DebugOptions struct { + // If true, Go routine IDs start at 1 instead of 0. + GoRoutineStartAt1 bool +} + +// A DebugEvent is an event generated by a debugger. +type DebugEvent struct { + debugger *Debugger + reason DebugEventReason + frame *frame +} + +// DebugFrame provides access to stack frame information while debugging a +// program. +type DebugFrame struct { + event *DebugEvent + frames []*frame +} + +// DebugFrameScope provides access to scoped variables while debugging a +// program. +type DebugFrameScope struct { + frame *frame +} + +// DebugVariable is the name and value of a variable from a debug session. +type DebugVariable struct { + Name string + Value reflect.Value +} + +// DebugGoRoutine provides access to information about a Go routine while +// debugging a program. +type DebugGoRoutine struct { + id int +} + +// Breakpoint is the result of attempting to set a breakpoint. +type Breakpoint struct { + // Valid indicates whether the breakpoint was successfully set. + Valid bool + + // Position indicates the source position of the breakpoint. + Position token.Position +} + +// DebugEventReason is the reason a debug event occurred. +type DebugEventReason int + +const ( + // continue execution normally. + debugRun DebugEventReason = iota + + // DebugPause is emitted when a pause request is completed. Can be used with + // Interrupt to request a pause. + DebugPause + + // DebugBreak is emitted when a debug target hits a breakpoint. + DebugBreak + + // DebugEntry is emitted when a debug target starts executing. Can be used + // with Step to produce a corresponding event when execution starts. + DebugEntry + + // DebugStepInto is emitted when a stepInto request is completed. Can be + // used with Step or Interrupt to request a stepInto. + DebugStepInto + + // DebugStepOver is emitted when a stepOver request is completed. Can be + // used with Step or Interrupt to request a stepOver. + DebugStepOver + + // DebugStepOut is emitted when a stepOut request is completed. Can be used + // with Step or Interrupt to request a stepOut. + DebugStepOut + + // DebugTerminate is emitted when a debug target terminates. Can be used + // with Interrupt to attempt to terminate the program. + DebugTerminate + + // DebugEnterGoRoutine is emitted when a Go routine is entered. + DebugEnterGoRoutine + + // DebugExitGoRoutine is emitted when a Go routine is exited. + DebugExitGoRoutine +) + +// Debug initializes a debugger for the given program. +// +// The program will not start running until Step or Continue has been called. If +// Step is called with DebugEntry, an entry event will be generated before the +// first statement is executed. Otherwise, the debugger will behave as usual. +func (interp *Interpreter) Debug(ctx context.Context, prog *Program, events func(*DebugEvent), opts *DebugOptions) *Debugger { + dbg := new(Debugger) + dbg.interp = interp + dbg.events = events + dbg.context, dbg.cancel = context.WithCancel(ctx) + dbg.gWait = new(sync.WaitGroup) + dbg.gLock = new(sync.Mutex) + dbg.gLive = make(map[int]*debugRoutine, 1) + + if opts == nil { + opts = new(DebugOptions) + } + if opts.GoRoutineStartAt1 { + dbg.gID = 1 + } + + mainG := dbg.enterGoRoutine() + mainG.mode = DebugEntry + + interp.debugger = dbg + interp.frame.debug = &frameDebugData{kind: frameRoot, g: mainG} + + prog.root.Walk(func(n *node) bool { + n.setProgram(prog) + return true + }, nil) + + go func() { + defer func() { interp.debugger = nil }() + defer events(&DebugEvent{reason: DebugTerminate}) + defer dbg.cancel() + + <-mainG.resume + dbg.events(&DebugEvent{dbg, DebugEnterGoRoutine, interp.frame}) + dbg.result, dbg.err = interp.ExecuteWithContext(ctx, prog) + dbg.exitGoRoutine(mainG) + dbg.events(&DebugEvent{dbg, DebugExitGoRoutine, interp.frame}) + dbg.gWait.Wait() + }() + + return dbg +} + +// Wait blocks until all Go routines launched by the program have terminated. +// Wait returns the results of `(*Interpreter).Execute`. +func (dbg *Debugger) Wait() (reflect.Value, error) { + <-dbg.context.Done() + return dbg.result, dbg.err +} + +// mark entry into a go routine. +func (dbg *Debugger) enterGoRoutine() *debugRoutine { + g := new(debugRoutine) + g.resume = make(chan struct{}) + + dbg.gWait.Add(1) + + dbg.gLock.Lock() + g.id = dbg.gID + dbg.gID++ + dbg.gLive[g.id] = g + dbg.gLock.Unlock() + + return g +} + +// mark exit from a go routine. +func (dbg *Debugger) exitGoRoutine(g *debugRoutine) { + dbg.gLock.Lock() + delete(dbg.gLive, g.id) + dbg.gLock.Unlock() + + dbg.gWait.Done() +} + +// get the state for a given go routine, if it's live. +func (dbg *Debugger) getGoRoutine(id int) (*debugRoutine, bool) { + dbg.gLock.Lock() + g, ok := dbg.gLive[id] + dbg.gLock.Unlock() + return g, ok +} + +// mark entry into a function call. +func (dbg *Debugger) enterCall(nFunc, nCall *node, f *frame) { + if f.debug != nil { + f.debug.g.fDepth++ + return + } + + f.debug = new(frameDebugData) + f.debug.g = f.anc.debug.g + f.debug.scope = nFunc.scope + + switch nFunc.kind { + case funcLit: + f.debug.kind = frameCall + + case funcDecl: + f.debug.kind = frameCall + f.debug.name = nFunc.child[1].ident + } + + if nCall != nil && nCall.anc.kind == goStmt { + f.debug.g = dbg.enterGoRoutine() + dbg.events(&DebugEvent{dbg, DebugEnterGoRoutine, f}) + } + + f.debug.g.fDepth++ +} + +// mark exit from a function call. +func (dbg *Debugger) exitCall(nFunc, nCall *node, f *frame) { + _ = nFunc // ignore unused, so exitCall can have the same signature as enterCall + + f.debug.g.fDepth-- + + if nCall != nil && nCall.anc.kind == goStmt { + dbg.exitGoRoutine(f.debug.g) + dbg.events(&DebugEvent{dbg, DebugExitGoRoutine, f}) + } +} + +// called by the interpreter prior to executing the node. +func (dbg *Debugger) exec(n *node, f *frame) (stop bool) { + f.debug.node = n + + if n != nil && n.pos == token.NoPos { + return false + } + + g := f.debug.g + defer func() { g.running = true }() + + e := &DebugEvent{dbg, g.mode, f} + switch { + case g.mode == DebugTerminate: + dbg.cancel() + return true + + case n.shouldBreak(): + e.reason = DebugBreak + + case g.mode == debugRun: + return false + + case g.mode == DebugStepOut: + if g.fDepth >= g.fStep { + return false + } + + case g.mode == DebugStepOver: + if g.fDepth > g.fStep { + return false + } + } + dbg.events(e) + + g.running = false + select { + case <-g.resume: + return false + case <-dbg.context.Done(): + return true + } +} + +// Continue continues execution of the specified Go routine. Continue returns +// ErrNotLive if there is no Go routine with the corresponding ID, or if it is not +// live. +func (dbg *Debugger) Continue(id int) error { + g, ok := dbg.getGoRoutine(id) + if !ok { + return ErrNotLive + } + + g.mode = debugRun + g.resume <- struct{}{} + return nil +} + +// update the exec mode of this routine. +func (g *debugRoutine) setMode(reason DebugEventReason) { + if g.mode == DebugTerminate { + return + } + + if g.mode == DebugEntry && reason == DebugEntry { + return + } + + switch reason { + case DebugStepInto, DebugStepOver, DebugStepOut: + g.mode, g.fStep = reason, g.fDepth + default: + g.mode = DebugPause + } +} + +// Step issues a stepInto, stepOver, or stepOut request to a stopped Go routine. +// Step returns ErrRunning if the Go routine is running. Step returns ErrNotLive +// if there is no Go routine with the corresponding ID, or if it is not live. +func (dbg *Debugger) Step(id int, reason DebugEventReason) error { + g, ok := dbg.getGoRoutine(id) + if !ok { + return ErrNotLive + } + + if g.running { + return ErrRunning + } + + g.setMode(reason) + g.resume <- struct{}{} + return nil +} + +// Interrupt issues a stepInto, stepOver, or stepOut request to a running Go +// routine. Interrupt returns ErrRunning if the Go routine is running. Interrupt +// returns ErrNotLive if there is no Go routine with the corresponding ID, or if +// it is not live. +func (dbg *Debugger) Interrupt(id int, reason DebugEventReason) bool { + g, ok := dbg.getGoRoutine(id) + if !ok { + return false + } + + g.setMode(reason) + return true +} + +// Terminate attempts to terminate the program. +func (dbg *Debugger) Terminate() { + dbg.gLock.Lock() + g := dbg.gLive + dbg.gLive = nil + dbg.gLock.Unlock() + + for _, g := range g { + g.mode = DebugTerminate + close(g.resume) + } +} + +// BreakpointTarget is the target of a request to set breakpoints. +type BreakpointTarget func(*Debugger, func(*node)) + +// PathBreakpointTarget is used to set breapoints on compiled code by path. This +// can be used to set breakpoints on code compiled with EvalPath, or source +// packages loaded by Yaegi. +func PathBreakpointTarget(path string) BreakpointTarget { + return func(dbg *Debugger, cb func(*node)) { + for _, r := range dbg.interp.roots { + f := dbg.interp.fset.File(r.pos) + if f != nil && f.Name() == path { + cb(r) + return + } + } + } +} + +// ProgramBreakpointTarget is used to set breakpoints on a Program. +func ProgramBreakpointTarget(prog *Program) BreakpointTarget { + return func(_ *Debugger, cb func(*node)) { + cb(prog.root) + } +} + +// AllBreakpointTarget is used to set breakpoints on all compiled code. Do not +// use with LineBreakpoint. +func AllBreakpointTarget() BreakpointTarget { + return func(dbg *Debugger, cb func(*node)) { + for _, r := range dbg.interp.roots { + cb(r) + } + } +} + +type breakpointSetup struct { + roots []*node + lines map[int]int + funcs map[string]int +} + +// BreakpointRequest is a request to set a breakpoint. +type BreakpointRequest func(*breakpointSetup, int) + +// LineBreakpoint requests a breakpoint on the given line. +func LineBreakpoint(line int) BreakpointRequest { + return func(b *breakpointSetup, i int) { + b.lines[line] = i + } +} + +// FunctionBreakpoint requests a breakpoint on the named function. +func FunctionBreakpoint(name string) BreakpointRequest { + return func(b *breakpointSetup, i int) { + b.funcs[name] = i + } +} + +// SetBreakpoints sets breakpoints for the given target. The returned array has +// an entry for every request, in order. If a given breakpoint request cannot be +// satisfied, the corresponding entry will be marked invalid. If the target +// cannot be found, all entries will be marked invalid. +func (dbg *Debugger) SetBreakpoints(target BreakpointTarget, requests ...BreakpointRequest) []Breakpoint { + // start with all breakpoints unverified + results := make([]Breakpoint, len(requests)) + + // prepare all the requests + setup := new(breakpointSetup) + target(dbg, func(root *node) { + setup.roots = append(setup.roots, root) + setup.lines = make(map[int]int, len(requests)) + setup.funcs = make(map[string]int, len(requests)) + for i, rq := range requests { + rq(setup, i) + } + }) + + // find breakpoints + for _, root := range setup.roots { + root.Walk(func(n *node) bool { + // function breakpoints + if len(setup.funcs) > 0 && n.kind == funcDecl { + // reset stale breakpoints + n.start.setBreakOnCall(false) + + if i, ok := setup.funcs[n.child[1].ident]; ok && !results[i].Valid { + results[i].Valid = true + results[i].Position = dbg.interp.fset.Position(n.start.pos) + n.start.setBreakOnCall(true) + return true + } + } + + // line breakpoints + if len(setup.lines) > 0 && n.pos.IsValid() && n.action != aNop && getExec(n) != nil { + // reset stale breakpoints + n.setBreakOnLine(false) + + pos := dbg.interp.fset.Position(n.pos) + if i, ok := setup.lines[pos.Line]; ok && !results[i].Valid { + results[i].Valid = true + results[i].Position = pos + n.setBreakOnLine(true) + return true + } + } + + return true + }, nil) + } + + return results +} + +// GoRoutines returns an array of live Go routines. +func (dbg *Debugger) GoRoutines() []*DebugGoRoutine { + dbg.gLock.Lock() + r := make([]*DebugGoRoutine, 0, len(dbg.gLive)) + for id := range dbg.gLive { + r = append(r, &DebugGoRoutine{id}) + } + dbg.gLock.Unlock() + sort.Slice(r, func(i, j int) bool { return r[i].id < r[j].id }) + return r +} + +// ID returns the ID of the Go routine. +func (r *DebugGoRoutine) ID() int { return r.id } + +// Name returns "Goroutine {ID}". +func (r *DebugGoRoutine) Name() string { return fmt.Sprintf("Goroutine %d", r.id) } + +// GoRoutine returns the ID of the Go routine that generated the event. +func (evt *DebugEvent) GoRoutine() int { + if evt.frame.debug == nil { + return 0 + } + return evt.frame.debug.g.id +} + +// Reason returns the reason for the event. +func (evt *DebugEvent) Reason() DebugEventReason { + return evt.reason +} + +// Walk the stack trace frames. The root frame is included if and only if it is +// the only frame. Closure frames are rolled up into the following call frame. +func (evt *DebugEvent) walkFrames(fn func([]*frame) bool) { + if evt.frame == evt.frame.root { + fn([]*frame{evt.frame}) + return + } + + var g *debugRoutine + if evt.frame.debug != nil { + g = evt.frame.debug.g + } + + var frames []*frame + for f := evt.frame; f != nil && f != f.root && (f.debug == nil || f.debug.g == g); f = f.anc { + if f.debug == nil || f.debug.kind != frameCall { + frames = append(frames, f) + continue + } + + if len(frames) > 0 { + if !fn(frames) { + return + } + } + + frames = frames[:0] + frames = append(frames, f) + } + + if len(frames) > 0 { + fn(frames) + } +} + +// FrameDepth returns the number of call frames in the stack trace. +func (evt *DebugEvent) FrameDepth() int { + if evt.frame == evt.frame.root { + return 1 + } + + var n int + evt.walkFrames(func([]*frame) bool { n++; return true }) + return n +} + +// Frames returns the call frames in the range [start, end). +func (evt *DebugEvent) Frames(start, end int) []*DebugFrame { + count := end - start + if count < 0 { + return nil + } + + frames := []*DebugFrame{} + evt.walkFrames(func(f []*frame) bool { + df := &DebugFrame{evt, make([]*frame, len(f))} + copy(df.frames, f) + frames = append(frames, df) + return len(frames) < count + }) + return frames +} + +// Name returns the name of the stack frame. For function calls to named +// functions, this is the function name. +func (f *DebugFrame) Name() string { + d := f.frames[0].debug + if d == nil { + return "" + } + switch d.kind { + case frameRoot: + return "" + case frameClosure: + return "" + case frameCall: + if d.name == "" { + return "" + } + return d.name + default: + return "" + } +} + +// Position returns the current position of the frame. This is effectively the +// program counter/link register. May return `Position{}`. +func (f *DebugFrame) Position() token.Position { + d := f.frames[0].debug + if d == nil || d.node == nil { + return token.Position{} + } + return f.event.debugger.interp.fset.Position(d.node.pos) +} + +// Program returns the program associated with the current position of the +// frame. May return nil. +func (f *DebugFrame) Program() *Program { + d := f.frames[0].debug + if d == nil || d.node == nil { + return nil + } + + return d.node.debug.program +} + +// Scopes returns the variable scopes of the frame. +func (f *DebugFrame) Scopes() []*DebugFrameScope { + s := make([]*DebugFrameScope, len(f.frames)) + for i, f := range f.frames { + s[i] = &DebugFrameScope{f} + } + return s +} + +// IsClosure returns true if this is the capture scope of a closure. +func (f *DebugFrameScope) IsClosure() bool { + return f.frame.debug != nil && f.frame.debug.kind == frameClosure +} + +// Variables returns the names and values of the variables of the scope. +func (f *DebugFrameScope) Variables() []*DebugVariable { + d := f.frame.debug + if d == nil || d.scope == nil { + return nil + } + + index := map[int]string{} + scanScope(d.scope, index) + + m := make([]*DebugVariable, 0, len(f.frame.data)) + for i, v := range f.frame.data { + if typ := v.Type(); typ.AssignableTo(rNodeType) || typ.Kind() == reflect.Ptr && typ.Elem().AssignableTo(rNodeType) { + continue + } + name, ok := index[i] + if !ok { + continue + } + + m = append(m, &DebugVariable{name, v}) + } + return m +} + +func scanScope(sc *scope, index map[int]string) { + for name, sym := range sc.sym { + if _, ok := index[sym.index]; ok { + continue + } + index[sym.index] = name + } + + for _, ch := range sc.child { + if ch.def != sc.def { + continue + } + scanScope(ch, index) + } +} diff --git a/internal/yaegi/interp/doc.go b/internal/yaegi/interp/doc.go new file mode 100644 index 00000000..74c83d59 --- /dev/null +++ b/internal/yaegi/interp/doc.go @@ -0,0 +1,48 @@ +/* +Package interp provides a complete Go interpreter. + +For the Go language itself, refer to the official Go specification +https://golang.org/ref/spec. + +# Importing packages + +Packages can be imported in source or binary form, using the standard +Go import statement. In source form, packages are searched first in the +vendor directory, the preferred way to store source dependencies. If not +found in vendor, sources modules will be searched in GOPATH. Go modules +are not supported yet by yaegi. + +Binary form packages are compiled and linked with the interpreter +executable, and exposed to scripts with the Use method. The extract +subcommand of yaegi can be used to generate package wrappers. + +# Custom build tags + +Custom build tags allow to control which files in imported source +packages are interpreted, in the same way as the "-tags" option of the +"go build" command. Setting a custom build tag spans globally for all +future imports of the session. + +A build tag is a line comment that begins + + // yaegi:tags + +that lists the build constraints to be satisfied by the further +imports of source packages. + +For example the following custom build tag + + // yaegi:tags noasm + +Will ensure that an import of a package will exclude files containing + + // +build !noasm + +And include files containing + + // +build noasm +*/ +package interp + +// BUG(marc): Support for recursive types is incomplete. +// BUG(marc): Support of types implementing multiple interfaces is incomplete. diff --git a/internal/yaegi/interp/dot.go b/internal/yaegi/interp/dot.go new file mode 100644 index 00000000..d24f0745 --- /dev/null +++ b/internal/yaegi/interp/dot.go @@ -0,0 +1,94 @@ +package interp + +import ( + "fmt" + "io" + "log" + "os/exec" + "path/filepath" + "strings" +) + +// astDot displays an AST in graphviz dot(1) format using dotty(1) co-process. +func (n *node) astDot(out io.Writer, name string) { + fmt.Fprintf(out, "digraph ast {\n") + fmt.Fprintf(out, "labelloc=\"t\"\n") + fmt.Fprintf(out, "label=\"%s\"\n", name) + n.Walk(func(n *node) bool { + var label string + switch n.kind { + case basicLit, identExpr: + label = strings.ReplaceAll(n.ident, "\"", "\\\"") + default: + if n.action != aNop { + label = n.action.String() + } else { + label = n.kind.String() + } + } + fmt.Fprintf(out, "%d [label=\"%d: %s\"]\n", n.index, n.index, label) + if n.anc != nil { + fmt.Fprintf(out, "%d -> %d\n", n.anc.index, n.index) + } + return true + }, nil) + fmt.Fprintf(out, "}\n") +} + +// cfgDot displays a CFG in graphviz dot(1) format using dotty(1) co-process. +func (n *node) cfgDot(out io.Writer) { + fmt.Fprintf(out, "digraph cfg {\n") + n.Walk(nil, func(n *node) { + if n.kind == basicLit || n.tnext == nil { + return + } + var label string + if n.action == aNop { + label = "nop: end_" + n.kind.String() + } else { + label = n.action.String() + } + fmt.Fprintf(out, "%d [label=\"%d: %v %d\"]\n", n.index, n.index, label, n.findex) + if n.fnext != nil { + fmt.Fprintf(out, "%d -> %d [color=green]\n", n.index, n.tnext.index) + fmt.Fprintf(out, "%d -> %d [color=red]\n", n.index, n.fnext.index) + } else if n.tnext != nil { + fmt.Fprintf(out, "%d -> %d\n", n.index, n.tnext.index) + } + }) + fmt.Fprintf(out, "}\n") +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { return nil } + +// dotWriter returns an output stream to a dot(1) co-process where to write data in .dot format. +func dotWriter(dotCmd string) io.WriteCloser { + if dotCmd == "" { + return nopCloser{io.Discard} + } + fields := strings.Fields(dotCmd) + cmd := exec.Command(fields[0], fields[1:]...) + dotin, err := cmd.StdinPipe() + if err != nil { + log.Fatal(err) + } + if err = cmd.Start(); err != nil { + log.Fatal(err) + } + return dotin +} + +func defaultDotCmd(filePath, prefix string) string { + dir, fileName := filepath.Split(filePath) + ext := filepath.Ext(fileName) + if ext == "" { + fileName += ".dot" + } else { + fileName = strings.Replace(fileName, ext, ".dot", 1) + } + return "dot -Tdot -o" + dir + prefix + fileName +} diff --git a/internal/yaegi/interp/generic.go b/internal/yaegi/interp/generic.go new file mode 100644 index 00000000..e1b06d0e --- /dev/null +++ b/internal/yaegi/interp/generic.go @@ -0,0 +1,319 @@ +package interp + +import ( + "strings" + "sync/atomic" +) + +// adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only. +// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } + +// genAST returns a new AST where generic types are replaced by instantiated types. +func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { + typeParam := map[string]*node{} + pindex := 0 + tname := "" + rtname := "" + recvrPtr := false + fixNodes := []*node{} + var gtree func(*node, *node) (*node, error) + sname := root.child[0].ident + "[" + if root.kind == funcDecl { + sname = root.child[1].ident + "[" + } + + // Input type parameters must be resolved prior AST generation, as compilation + // of generated AST may occur in a different scope. + for _, t := range types { + sname += t.id() + "," + } + sname = strings.TrimSuffix(sname, ",") + "]" + + gtree = func(n, anc *node) (*node, error) { + nod := copyNode(n, anc, false) + switch n.kind { + case funcDecl, funcType: + nod.val = nod + + case identExpr: + // Replace generic type by instantiated one. + nt, ok := typeParam[n.ident] + if !ok { + break + } + nod = copyNode(nt, anc, true) + nod.typ = nt.typ + + case indexExpr: + // Catch a possible recursive generic type definition + if root.kind != typeSpec { + break + } + if root.child[0].ident != n.child[0].ident { + break + } + nod := copyNode(n.child[0], anc, false) + fixNodes = append(fixNodes, nod) + return nod, nil + + case fieldList: + // Node is the type parameters list of a generic function. + if root.kind == funcDecl && n.anc == root.child[2] && childPos(n) == 0 { + // Fill the types lookup table used for type substitution. + for _, c := range n.child { + l := len(c.child) - 1 + for _, cc := range c.child[:l] { + if pindex >= len(types) { + return nil, cc.cfgErrorf("undefined type for %s", cc.ident) + } + t, err := nodeType(c.interp, sc, c.child[l]) + if err != nil { + return nil, err + } + if err := checkConstraint(types[pindex], t); err != nil { + return nil, err + } + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = types[pindex].id() + typeParam[cc.ident].typ = types[pindex] + pindex++ + } + } + // Skip type parameters specification, so generated func doesn't look generic. + return nod, nil + } + + // Node is the receiver of a generic method. + if root.kind == funcDecl && n.anc == root && childPos(n) == 0 && len(n.child) > 0 { + rtn := n.child[0].child[1] + // Method receiver is a generic type if it takes some type parameters. + if rtn.kind == indexExpr || rtn.kind == indexListExpr || (rtn.kind == starExpr && (rtn.child[0].kind == indexExpr || rtn.child[0].kind == indexListExpr)) { + if rtn.kind == starExpr { + // Method receiver is a pointer on a generic type. + rtn = rtn.child[0] + recvrPtr = true + } + rtname = rtn.child[0].ident + "[" + for _, cc := range rtn.child[1:] { + if pindex >= len(types) { + return nil, cc.cfgErrorf("undefined type for %s", cc.ident) + } + it := types[pindex] + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = it.id() + typeParam[cc.ident].typ = it + rtname += it.id() + "," + pindex++ + } + rtname = strings.TrimSuffix(rtname, ",") + "]" + } + } + + // Node is the type parameters list of a generic type. + if root.kind == typeSpec && n.anc == root && childPos(n) == 1 { + // Fill the types lookup table used for type substitution. + tname = n.anc.child[0].ident + "[" + for _, c := range n.child { + l := len(c.child) - 1 + for _, cc := range c.child[:l] { + if pindex >= len(types) { + return nil, cc.cfgErrorf("undefined type for %s", cc.ident) + } + it := types[pindex] + t, err := nodeType(c.interp, sc, c.child[l]) + if err != nil { + return nil, err + } + if err := checkConstraint(types[pindex], t); err != nil { + return nil, err + } + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = it.id() + typeParam[cc.ident].typ = it + tname += it.id() + "," + pindex++ + } + } + tname = strings.TrimSuffix(tname, ",") + "]" + return nod, nil + } + } + + for _, c := range n.child { + gn, err := gtree(c, nod) + if err != nil { + return nil, err + } + nod.child = append(nod.child, gn) + } + return nod, nil + } + + if nod, found := root.interp.generic[sname]; found { + return nod, true, nil + } + + r, err := gtree(root, root.anc) + if err != nil { + return nil, false, err + } + root.interp.generic[sname] = r + r.param = append(r.param, types...) + if tname != "" { + for _, nod := range fixNodes { + nod.ident = tname + } + r.child[0].ident = tname + } + if rtname != "" { + // Replace method receiver type by synthetized ident. + nod := r.child[0].child[0].child[1] + if recvrPtr { + nod = nod.child[0] + } + nod.kind = identExpr + nod.ident = rtname + nod.child = nil + } + // r.adot() // Used for debugging only. + return r, false, nil +} + +func copyNode(n, anc *node, recursive bool) *node { + var i interface{} + nindex := atomic.AddInt64(&n.interp.nindex, 1) + nod := &node{ + debug: n.debug, + anc: anc, + interp: n.interp, + index: nindex, + level: n.level, + nleft: n.nleft, + nright: n.nright, + kind: n.kind, + pos: n.pos, + action: n.action, + gen: n.gen, + val: &i, + rval: n.rval, + ident: n.ident, + meta: n.meta, + } + nod.start = nod + if recursive { + for _, c := range n.child { + nod.child = append(nod.child, copyNode(c, nod, true)) + } + } + return nod +} + +func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*itype, error) { + ftn := fun.typ.node + // Fill the map of parameter types, indexed by type param ident. + paramTypes := map[string]*itype{} + for _, c := range ftn.child[0].child { + typ, err := nodeType(fun.interp, sc, c.lastChild()) + if err != nil { + return nil, err + } + for _, cc := range c.child[:len(c.child)-1] { + paramTypes[cc.ident] = typ + } + } + + var inferTypes func(*itype, *itype) ([]*itype, error) + inferTypes = func(param, input *itype) ([]*itype, error) { + switch param.cat { + case chanT, ptrT, sliceT: + return inferTypes(param.val, input.val) + + case mapT: + k, err := inferTypes(param.key, input.key) + if err != nil { + return nil, err + } + v, err := inferTypes(param.val, input.val) + if err != nil { + return nil, err + } + return append(k, v...), nil + + case structT: + lt := []*itype{} + for i, f := range param.field { + nl, err := inferTypes(f.typ, input.field[i].typ) + if err != nil { + return nil, err + } + lt = append(lt, nl...) + } + return lt, nil + + case funcT: + lt := []*itype{} + for i, t := range param.arg { + if i >= len(input.arg) { + break + } + nl, err := inferTypes(t, input.arg[i]) + if err != nil { + return nil, err + } + lt = append(lt, nl...) + } + for i, t := range param.ret { + if i >= len(input.ret) { + break + } + nl, err := inferTypes(t, input.ret[i]) + if err != nil { + return nil, err + } + lt = append(lt, nl...) + } + return lt, nil + + case nilT: + if paramTypes[param.name] != nil { + return []*itype{input}, nil + } + + case genericT: + return []*itype{input}, nil + } + return nil, nil + } + + types := []*itype{} + for i, c := range ftn.child[1].child { + typ, err := nodeType(fun.interp, sc, c.lastChild()) + if err != nil { + return nil, err + } + lt, err := inferTypes(typ, args[i].typ) + if err != nil { + return nil, err + } + types = append(types, lt...) + } + + return types, nil +} + +func checkConstraint(it, ct *itype) error { + if len(ct.constraint) == 0 && len(ct.ulconstraint) == 0 { + return nil + } + for _, c := range ct.constraint { + if it.equals(c) || it.matchDefault(c) { + return nil + } + } + for _, c := range ct.ulconstraint { + if it.underlying().equals(c) || it.matchDefault(c) { + return nil + } + } + return it.node.cfgErrorf("%s does not implement %s", it.id(), ct.id()) +} diff --git a/internal/yaegi/interp/gta.go b/internal/yaegi/interp/gta.go new file mode 100644 index 00000000..28f84aee --- /dev/null +++ b/internal/yaegi/interp/gta.go @@ -0,0 +1,475 @@ +package interp + +import ( + "path" + "path/filepath" +) + +// gta performs a global types analysis on the AST, registering types, +// variables and functions symbols at package level, prior to CFG. +// All function bodies are skipped. GTA is necessary to handle out of +// order declarations and multiple source files packages. +// rpath is the relative path to the directory containing the source for the package. +func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([]*node, error) { + sc := interp.initScopePkg(importPath, pkgName) + var err error + var revisit []*node + + baseName := filepath.Base(interp.fset.Position(root.pos).Filename) + + root.Walk(func(n *node) bool { + if err != nil { + return false + } + if n.scope == nil { + n.scope = sc + } + switch n.kind { + case constDecl: + // Early parse of constDecl subtree, to compute all constant + // values which may be used in further declarations. + if _, err = interp.cfg(n, sc, importPath, pkgName); err != nil { + // No error processing here, to allow recovery in subtree nodes. + // TODO(marc): check for a non recoverable error and return it for better diagnostic. + err = nil + } + + case blockStmt: + if n != root { + return false // skip statement block if not the entry point + } + + case defineStmt: + var ( + atyp *itype + err2 error + ) + if n.nleft+n.nright < len(n.child) { + // Type is declared explicitly in the assign expression. + if atyp, err2 = nodeType(interp, sc, n.child[n.nleft]); err2 != nil { + // The type does not exist yet, stash the error and come back + // when the type is known. + n.meta = err2 + revisit = append(revisit, n) + return false + } + } + + var sbase int + if n.nright > 0 { + sbase = len(n.child) - n.nright + } + + for i := 0; i < n.nleft; i++ { + dest, src := n.child[i], n.child[sbase+i] + if isBlank(src) { + err = n.cfgErrorf("cannot use _ as value") + } + val := src.rval + if n.anc.kind == constDecl { + if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil { + // Constant value can not be computed yet. + // Come back when child dependencies are known. + revisit = append(revisit, n) + return false + } + } + typ := atyp + if typ == nil { + if typ, err2 = nodeType(interp, sc, src); err2 != nil || typ == nil { + // The type does is not known yet, stash the error and come back + // when the type is known. + n.meta = err2 + revisit = append(revisit, n) + return false + } + val = src.rval + } + if !typ.isComplete() { + // Come back when type is known. + revisit = append(revisit, n) + return false + } + if typ.cat == nilT { + err = n.cfgErrorf("use of untyped nil") + return false + } + if typ.isBinMethod { + typ = valueTOf(typ.methodCallType(), isBinMethod(), withScope(sc)) + } + sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n} + if n.anc.kind == constDecl { + sc.sym[dest.ident].kind = constSym + if childPos(n) == len(n.anc.child)-1 { + sc.iota = 0 + } else { + sc.iota++ + } + } + } + return false + + case defineXStmt: + err = compDefineX(sc, n) + + case valueSpec: + l := len(n.child) - 1 + if n.typ = n.child[l].typ; n.typ == nil { + if n.typ, err = nodeType(interp, sc, n.child[l]); err != nil { + return false + } + if !n.typ.isComplete() { + // Come back when type is known. + revisit = append(revisit, n) + return false + } + } + for _, c := range n.child[:l] { + asImportName := filepath.Join(c.ident, baseName) + sym, exists := sc.sym[asImportName] + if !exists { + sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n} + continue + } + c.level = globalFrame + + // redeclaration error + if sym.typ.node != nil && sym.typ.node.anc != nil { + prevDecl := n.interp.fset.Position(sym.typ.node.anc.pos) + err = n.cfgErrorf("%s redeclared in this block\n\tprevious declaration at %v", c.ident, prevDecl) + return false + } + err = n.cfgErrorf("%s redeclared in this block", c.ident) + return false + } + + case funcDecl: + if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil { + return false + } + genericMethod := false + ident := n.child[1].ident + switch { + case isMethod(n): + // Add a method symbol in the receiver type name space + var rcvrtype *itype + n.ident = ident + rcvr := n.child[0].child[0] + rtn := rcvr.lastChild() + typName, typPtr := rtn.ident, false + // Identifies the receiver type name. It could be an ident, a + // generic type (indexExpr), or a pointer on either lasts. + if typName == "" { + typName = rtn.child[0].ident + switch rtn.kind { + case starExpr: + typPtr = true + switch c := rtn.child[0]; c.kind { + case indexExpr, indexListExpr: + typName = c.child[0].ident + genericMethod = true + } + case indexExpr, indexListExpr: + genericMethod = true + } + } + sym, _, found := sc.lookup(typName) + if !found { + n.meta = n.cfgErrorf("undefined: %s", typName) + revisit = append(revisit, n) + return false + } + if sym.typ.path != pkgName { + err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id()) + return false + } + rcvrtype = sym.typ + if typPtr { + elementType := sym.typ + rcvrtype = ptrOf(elementType, withNode(rtn), withScope(sc)) + rcvrtype.incomplete = elementType.incomplete + elementType.addMethod(n) + } + rcvrtype.addMethod(n) + rtn.typ = rcvrtype + if rcvrtype.cat == genericT { + // generate methods for already instantiated receivers + for _, it := range rcvrtype.instance { + if err = genMethod(interp, sc, it, n, it.node.anc.param); err != nil { + return false + } + } + } + case ident == "init": + // init functions do not get declared as per the Go spec. + default: + asImportName := filepath.Join(ident, baseName) + if _, exists := sc.sym[asImportName]; exists { + // redeclaration error + err = n.cfgErrorf("%s redeclared in this block", ident) + return false + } + // Add a function symbol in the package name space except for init + sc.sym[ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1} + } + if !n.typ.isComplete() && !genericMethod { + revisit = append(revisit, n) + } + return false + + case importSpec: + var name, ipath string + if len(n.child) == 2 { + ipath = constToString(n.child[1].rval) + name = n.child[0].ident + } else { + ipath = constToString(n.child[0].rval) + } + // Try to import a binary package first, or a source package + var pkgName string + if packageName := path.Base(ipath); path.Dir(ipath) == packageName { + ipath = packageName + } + if pkg := interp.binPkg[ipath]; pkg != nil { + switch name { + case "_": // no import of symbols + case ".": // import symbols in current scope + for n, v := range pkg { + typ := v.Type() + kind := binSym + if isBinType(v) { + typ = typ.Elem() + kind = typeSym + } + sc.sym[n] = &symbol{kind: kind, typ: valueTOf(typ, withScope(sc)), rval: v} + } + default: // import symbols in package namespace + if name == "" { + name = interp.pkgNames[ipath] + } + + // If an incomplete type exists, delete it + if sym, exists := sc.sym[name]; exists && sym.kind == typeSym && sym.typ.incomplete { + delete(sc.sym, name) + } + + // Imports of a same package are all mapped in the same scope, so we cannot just + // map them by their names, otherwise we could have collisions from same-name + // imports in different source files of the same package. Therefore, we suffix + // the key with the basename of the source file. + name = filepath.Join(name, baseName) + if sym, exists := sc.sym[name]; !exists { + sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath, scope: sc}} + break + } else if sym.kind == pkgSym && sym.typ.cat == srcPkgT && sym.typ.path == ipath { + // ignore re-import of identical package + break + } + + // redeclaration error. Not caught by the parser. + err = n.cfgErrorf("%s redeclared in this block", name) + return false + } + } else if pkgName, err = interp.importSrc(rpath, ipath, NoTest); err == nil { + sc.types = interp.universe.types + switch name { + case "_": // no import of symbols + case ".": // import symbols in current namespace + for k, v := range interp.srcPkg[ipath] { + if canExport(k) { + sc.sym[k] = v + } + } + default: // import symbols in package namespace + if name == "" { + name = pkgName + } + name = filepath.Join(name, baseName) + if sym, exists := sc.sym[name]; !exists { + sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath, scope: sc}} + break + } else if sym.kind == pkgSym && sym.typ.cat == srcPkgT && sym.typ.path == ipath { + // ignore re-import of identical package + break + } + + // redeclaration error + err = n.cfgErrorf("%s redeclared as imported package name", name) + return false + } + } else { + err = n.cfgErrorf("import %q error: %v", ipath, err) + } + + case typeSpec, typeSpecAssign: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + return false + } + typeName := n.child[0].ident + if len(n.child) > 2 { + // Handle a generic type: skip definition as parameter is not instantiated yet. + n.typ = genericOf(nil, typeName, pkgName, withNode(n.child[0]), withScope(sc)) + if _, exists := sc.sym[typeName]; !exists { + sc.sym[typeName] = &symbol{kind: typeSym, node: n} + } + sc.sym[typeName].typ = n.typ + return false + } + var typ *itype + if typ, err = nodeType(interp, sc, n.child[1]); err != nil { + err = nil + revisit = append(revisit, n) + return false + } + + if n.kind == typeSpecAssign { + // Create an aliased type in the current scope + sc.sym[typeName] = &symbol{kind: typeSym, node: n, typ: typ} + n.typ = typ + break + } + + // else we are not an alias (typeSpec) + + switch n.child[1].kind { + case identExpr, selectorExpr: + n.typ = namedOf(typ, pkgName, typeName, withNode(n.child[0]), withScope(sc)) + n.typ.incomplete = typ.incomplete + n.typ.field = typ.field + copy(n.typ.method, typ.method) + default: + n.typ = typ + n.typ.name = typeName + n.typ.path = pkgName + } + n.typ.str = n.typ.path + "." + n.typ.name + + asImportName := filepath.Join(typeName, baseName) + if _, exists := sc.sym[asImportName]; exists { + // redeclaration error + err = n.cfgErrorf("%s redeclared in this block", typeName) + return false + } + sym, exists := sc.sym[typeName] + if !exists { + sym = &symbol{kind: typeSym, node: n} + sc.sym[typeName] = sym + } else if sym.typ != nil && (len(sym.typ.method) > 0) { + // Type has already been seen as a receiver in a method function + for _, m := range sym.typ.method { + n.typ.addMethod(m) + } + } + sym.typ = n.typ + if !n.typ.isComplete() { + revisit = append(revisit, n) + } + return false + } + return true + }, nil) + + if sc != interp.universe { + sc.pop() + } + return revisit, err +} + +func baseType(t *itype) *itype { + for { + switch t.cat { + case ptrT, linkedT: + t = t.val + default: + return t + } + } +} + +// gtaRetry (re)applies gta until all global constants and types are defined. +func (interp *Interpreter) gtaRetry(nodes []*node, importPath, pkgName string) error { + revisit := []*node{} + for { + for _, n := range nodes { + list, err := interp.gta(n, importPath, importPath, pkgName) + if err != nil { + return err + } + revisit = append(revisit, list...) + } + + if len(revisit) == 0 || equalNodes(nodes, revisit) { + break + } + + nodes = revisit + revisit = []*node{} + } + + if len(revisit) > 0 { + n := revisit[0] + switch n.kind { + case typeSpec, typeSpecAssign: + if err := definedType(n.typ); err != nil { + return err + } + case defineStmt, funcDecl: + if err, ok := n.meta.(error); ok { + return err + } + } + return n.cfgErrorf("constant definition loop") + } + return nil +} + +func definedType(typ *itype) error { + if !typ.incomplete { + return nil + } + switch typ.cat { + case interfaceT, structT: + for _, f := range typ.field { + if err := definedType(f.typ); err != nil { + return err + } + } + case funcT: + for _, t := range typ.arg { + if err := definedType(t); err != nil { + return err + } + } + for _, t := range typ.ret { + if err := definedType(t); err != nil { + return err + } + } + case mapT: + if err := definedType(typ.key); err != nil { + return err + } + fallthrough + case linkedT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT: + if err := definedType(typ.val); err != nil { + return err + } + case nilT: + return typ.node.cfgErrorf("undefined: %s", typ.node.ident) + } + return nil +} + +// equalNodes returns true if two slices of nodes are identical. +func equalNodes(a, b []*node) bool { + if len(a) != len(b) { + return false + } + for i, n := range a { + if n != b[i] { + return false + } + } + return true +} diff --git a/internal/yaegi/interp/hooks.go b/internal/yaegi/interp/hooks.go new file mode 100644 index 00000000..30c538ba --- /dev/null +++ b/internal/yaegi/interp/hooks.go @@ -0,0 +1,28 @@ +package interp + +import "reflect" + +// convertFn is the signature of a symbol converter. +type convertFn func(from, to reflect.Type) func(src, dest reflect.Value) + +// hooks are external symbol bindings. +type hooks struct { + convert []convertFn +} + +func (h *hooks) Parse(m map[string]reflect.Value) { + if con, ok := getConvertFn(m["convert"]); ok { + h.convert = append(h.convert, con) + } +} + +func getConvertFn(v reflect.Value) (convertFn, bool) { + if !v.IsValid() { + return nil, false + } + fn, ok := v.Interface().(func(from, to reflect.Type) func(src, dest reflect.Value)) + if !ok { + return nil, false + } + return fn, true +} diff --git a/internal/yaegi/interp/interp.go b/internal/yaegi/interp/interp.go new file mode 100644 index 00000000..6c3183c9 --- /dev/null +++ b/internal/yaegi/interp/interp.go @@ -0,0 +1,761 @@ +package interp + +import ( + "bufio" + "context" + "errors" + "fmt" + "go/build" + "go/scanner" + "go/token" + "io" + "io/fs" + "os" + "os/signal" + "path" + "path/filepath" + "reflect" + "runtime" + "runtime/debug" + "strconv" + "strings" + "sync" + "sync/atomic" +) + +// Interpreter node structure for AST and CFG. +type node struct { + debug *nodeDebugData // debug info + child []*node // child subtrees (AST) + anc *node // ancestor (AST) + param []*itype // generic parameter nodes (AST) + start *node // entry point in subtree (CFG) + tnext *node // true branch successor (CFG) + fnext *node // false branch successor (CFG) + interp *Interpreter // interpreter context + index int64 // node index (dot display) + findex int // index of value in frame or frame size (func def, type def) + level int // number of frame indirections to access value + nleft int // number of children in left part (assign) or indicates preceding type (compositeLit) + nright int // number of children in right part (assign) + kind nkind // kind of node + pos token.Pos // position in source code, relative to fset + sym *symbol // associated symbol + typ *itype // type of value in frame, or nil + recv *receiver // method receiver node for call, or nil + types []reflect.Type // frame types, used by function literals only + scope *scope // frame scope + action action // action + exec bltn // generated function to execute + gen bltnGenerator // generator function to produce above bltn + val interface{} // static generic value (CFG execution) + rval reflect.Value // reflection value to let runtime access interpreter (CFG) + ident string // set if node is a var or func + redeclared bool // set if node is a redeclared variable (CFG) + meta interface{} // meta stores meta information between gta runs, like errors +} + +func (n *node) shouldBreak() bool { + if n == nil || n.debug == nil { + return false + } + + if n.debug.breakOnLine || n.debug.breakOnCall { + return true + } + + return false +} + +func (n *node) setProgram(p *Program) { + if n.debug == nil { + n.debug = new(nodeDebugData) + } + n.debug.program = p +} + +func (n *node) setBreakOnCall(v bool) { + if n.debug == nil { + if !v { + return + } + n.debug = new(nodeDebugData) + } + n.debug.breakOnCall = v +} + +func (n *node) setBreakOnLine(v bool) { + if n.debug == nil { + if !v { + return + } + n.debug = new(nodeDebugData) + } + n.debug.breakOnLine = v +} + +// receiver stores method receiver object access path. +type receiver struct { + node *node // receiver value for alias and struct types + val reflect.Value // receiver value for interface type and value type + index []int // path in receiver value for interface or value type +} + +// frame contains values for the current execution level (a function context). +type frame struct { + // id is an atomic counter used for cancellation, only accessed + // via newFrame/runid/setrunid/clone. + // Located at start of struct to ensure proper alignment. + id uint64 + + debug *frameDebugData + + root *frame // global space + anc *frame // ancestor frame (caller space) + data []reflect.Value // values + + mutex sync.RWMutex + deferred [][]reflect.Value // defer stack + recovered interface{} // to handle panic recover + done reflect.SelectCase // for cancellation of channel operations +} + +func newFrame(anc *frame, length int, id uint64) *frame { + f := &frame{ + anc: anc, + data: make([]reflect.Value, length), + id: id, + } + if anc == nil { + f.root = f + } else { + f.done = anc.done + f.root = anc.root + } + return f +} + +func (f *frame) runid() uint64 { return atomic.LoadUint64(&f.id) } +func (f *frame) setrunid(id uint64) { atomic.StoreUint64(&f.id, id) } +func (f *frame) clone() *frame { + f.mutex.RLock() + defer f.mutex.RUnlock() + nf := &frame{ + anc: f.anc, + root: f.root, + deferred: f.deferred, + recovered: f.recovered, + id: f.runid(), + done: f.done, + debug: f.debug, + } + nf.data = make([]reflect.Value, len(f.data)) + copy(nf.data, f.data) + return nf +} + +// Exports stores the map of binary packages per package path. +// The package path is the path joined from the import path and the package name +// as specified in source files by the "package" statement. +type Exports map[string]map[string]reflect.Value + +// imports stores the map of source packages per package path. +type imports map[string]map[string]*symbol + +// opt stores interpreter options. +type opt struct { + // dotCmd is the command to process the dot graph produced when astDot and/or + // cfgDot is enabled. It defaults to 'dot -Tdot -o .dot'. + dotCmd string + context build.Context // build context: GOPATH, build constraints + stdin io.Reader // standard input + stdout io.Writer // standard output + stderr io.Writer // standard error + args []string // cmdline args + env map[string]string // environment of interpreter, entries in form of "key=value" + filesystem fs.FS // filesystem containing sources + astDot bool // display AST graph (debug) + cfgDot bool // display CFG graph (debug) + noRun bool // compile, but do not run + fastChan bool // disable cancellable chan operations + specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors + unrestricted bool // allow use of non-sandboxed symbols +} + +// Interpreter contains global resources and state. +type Interpreter struct { + // id is an atomic counter used for run cancellation, + // only accessed via runid/stop + // Located at start of struct to ensure proper alignment on 32-bit + // architectures. + id uint64 + + // nindex is a node number incremented for each new node. + // It is used for debug (AST and CFG graphs). As it is atomically + // incremented, keep it aligned on 64 bits boundary. + nindex int64 + + name string // name of the input source file (or main) + + opt // user settable options + cancelChan bool // enables cancellable chan operations + fset *token.FileSet // fileset to locate node in source code + binPkg Exports // binary packages used in interpreter, indexed by path + rdir map[string]bool // for src import cycle detection + mapTypes map[reflect.Value][]reflect.Type // special interfaces mapping for wrappers + + mutex sync.RWMutex + frame *frame // program data storage during execution + universe *scope // interpreter global level scope + scopes map[string]*scope // package level scopes, indexed by import path + srcPkg imports // source packages used in interpreter, indexed by path + pkgNames map[string]string // package names, indexed by import path + done chan struct{} // for cancellation of channel operations + roots []*node + generic map[string]*node + + hooks *hooks // symbol hooks + + debugger *Debugger +} + +const ( + mainID = "main" + selfPrefix = "github.com/traefik/yaegi" + selfPath = selfPrefix + "/interp/interp" + // DefaultSourceName is the name used by default when the name of the input + // source file has not been specified for an Eval. + // TODO(mpl): something even more special as a name? + DefaultSourceName = "_.go" + + // Test is the value to pass to EvalPath to activate evaluation of test functions. + Test = false + // NoTest is the value to pass to EvalPath to skip evaluation of test functions. + NoTest = true +) + +// Self points to the current interpreter if accessed from within itself, or is nil. +var Self *Interpreter + +// Symbols exposes interpreter values. +var Symbols = Exports{ + selfPath: map[string]reflect.Value{ + "New": reflect.ValueOf(New), + + "Interpreter": reflect.ValueOf((*Interpreter)(nil)), + "Options": reflect.ValueOf((*Options)(nil)), + "Panic": reflect.ValueOf((*Panic)(nil)), + }, +} + +func init() { Symbols[selfPath]["Symbols"] = reflect.ValueOf(Symbols) } + +// _error is a wrapper of error interface type. +type _error struct { + IValue interface{} + WError func() string +} + +func (w _error) Error() string { return w.WError() } + +// Panic is an error recovered from a panic call in interpreted code. +type Panic struct { + // Value is the recovered value of a call to panic. + Value interface{} + + // Callers is the call stack obtained from the recover call. + // It may be used as the parameter to runtime.CallersFrames. + Callers []uintptr + + // Stack is the call stack buffer for debug. + Stack []byte +} + +// TODO: Capture interpreter stack frames also and remove +// fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic")) in runCfg. + +func (e Panic) Error() string { return fmt.Sprint(e.Value) } + +// Walk traverses AST n in depth first order, call cbin function +// at node entry and cbout function at node exit. +func (n *node) Walk(in func(n *node) bool, out func(n *node)) { + if in != nil && !in(n) { + return + } + for _, child := range n.child { + child.Walk(in, out) + } + if out != nil { + out(n) + } +} + +// Options are the interpreter options. +type Options struct { + // GoPath sets GOPATH for the interpreter. + GoPath string + + // BuildTags sets build constraints for the interpreter. + BuildTags []string + + // Standard input, output and error streams. + // They default to os.Stdin, os.Stdout and os.Stderr respectively. + Stdin io.Reader + Stdout, Stderr io.Writer + + // Cmdline args, defaults to os.Args. + Args []string + + // Environment of interpreter. Entries are in the form "key=values". + Env []string + + // SourcecodeFilesystem is where the _sourcecode_ is loaded from and does + // NOT affect the filesystem of scripts when they run. + // It can be any fs.FS compliant filesystem (e.g. embed.FS, or fstest.MapFS for testing) + // See example/fs/fs_test.go for an example. + SourcecodeFilesystem fs.FS + + // Unrestricted allows to run non sandboxed stdlib symbols such as os/exec and environment + Unrestricted bool +} + +// New returns a new interpreter. +func New(options Options) *Interpreter { + i := Interpreter{ + opt: opt{context: build.Default, filesystem: &realFS{}, env: map[string]string{}}, + frame: newFrame(nil, 0, 0), + fset: token.NewFileSet(), + universe: initUniverse(), + scopes: map[string]*scope{}, + binPkg: Exports{"": map[string]reflect.Value{"_error": reflect.ValueOf((*_error)(nil))}}, + mapTypes: map[reflect.Value][]reflect.Type{}, + srcPkg: imports{}, + pkgNames: map[string]string{}, + rdir: map[string]bool{}, + hooks: &hooks{}, + generic: map[string]*node{}, + } + + if i.opt.stdin = options.Stdin; i.opt.stdin == nil { + i.opt.stdin = os.Stdin + } + + if i.opt.stdout = options.Stdout; i.opt.stdout == nil { + i.opt.stdout = os.Stdout + } + + if i.opt.stderr = options.Stderr; i.opt.stderr == nil { + i.opt.stderr = os.Stderr + } + + if i.opt.args = options.Args; i.opt.args == nil { + i.opt.args = os.Args + } + + // unrestricted allows to use non sandboxed stdlib symbols and env. + if options.Unrestricted { + i.opt.unrestricted = true + } else { + for _, e := range options.Env { + a := strings.SplitN(e, "=", 2) + if len(a) == 2 { + i.opt.env[a[0]] = a[1] + } else { + i.opt.env[a[0]] = "" + } + } + } + + if options.SourcecodeFilesystem != nil { + i.opt.filesystem = options.SourcecodeFilesystem + } + + i.opt.context.GOPATH = options.GoPath + if len(options.BuildTags) > 0 { + i.opt.context.BuildTags = options.BuildTags + } + + // astDot activates AST graph display for the interpreter + i.opt.astDot, _ = strconv.ParseBool(os.Getenv("YAEGI_AST_DOT")) + + // cfgDot activates CFG graph display for the interpreter + i.opt.cfgDot, _ = strconv.ParseBool(os.Getenv("YAEGI_CFG_DOT")) + + // dotCmd defines how to process the dot code generated whenever astDot and/or + // cfgDot is enabled. It defaults to 'dot -Tdot -o.dot' where filename + // is context dependent. + i.opt.dotCmd = os.Getenv("YAEGI_DOT_CMD") + + // noRun disables the execution (but not the compilation) in the interpreter + i.opt.noRun, _ = strconv.ParseBool(os.Getenv("YAEGI_NO_RUN")) + + // fastChan disables the cancellable version of channel operations in evalWithContext + i.opt.fastChan, _ = strconv.ParseBool(os.Getenv("YAEGI_FAST_CHAN")) + + // specialStdio allows to assign directly io.Writer and io.Reader to os.Stdxxx, + // even if they are not file descriptors. + i.opt.specialStdio, _ = strconv.ParseBool(os.Getenv("YAEGI_SPECIAL_STDIO")) + + return &i +} + +const ( + bltnAlignof = "unsafe.Alignof" + bltnAppend = "append" + bltnCap = "cap" + bltnClose = "close" + bltnComplex = "complex" + bltnImag = "imag" + bltnCopy = "copy" + bltnDelete = "delete" + bltnLen = "len" + bltnMake = "make" + bltnNew = "new" + bltnOffsetof = "unsafe.Offsetof" + bltnPanic = "panic" + bltnPrint = "print" + bltnPrintln = "println" + bltnReal = "real" + bltnRecover = "recover" + bltnSizeof = "unsafe.Sizeof" +) + +func initUniverse() *scope { + sc := &scope{global: true, sym: map[string]*symbol{ + // predefined Go types + "any": {kind: typeSym, typ: &itype{cat: interfaceT, str: "any"}}, + "bool": {kind: typeSym, typ: &itype{cat: boolT, name: "bool", str: "bool"}}, + "byte": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}}, + "comparable": {kind: typeSym, typ: &itype{cat: comparableT, name: "comparable", str: "comparable"}}, + "complex64": {kind: typeSym, typ: &itype{cat: complex64T, name: "complex64", str: "complex64"}}, + "complex128": {kind: typeSym, typ: &itype{cat: complex128T, name: "complex128", str: "complex128"}}, + "error": {kind: typeSym, typ: &itype{cat: errorT, name: "error", str: "error"}}, + "float32": {kind: typeSym, typ: &itype{cat: float32T, name: "float32", str: "float32"}}, + "float64": {kind: typeSym, typ: &itype{cat: float64T, name: "float64", str: "float64"}}, + "int": {kind: typeSym, typ: &itype{cat: intT, name: "int", str: "int"}}, + "int8": {kind: typeSym, typ: &itype{cat: int8T, name: "int8", str: "int8"}}, + "int16": {kind: typeSym, typ: &itype{cat: int16T, name: "int16", str: "int16"}}, + "int32": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}}, + "int64": {kind: typeSym, typ: &itype{cat: int64T, name: "int64", str: "int64"}}, + "interface{}": {kind: typeSym, typ: &itype{cat: interfaceT, str: "interface{}"}}, + "rune": {kind: typeSym, typ: &itype{cat: int32T, name: "int32", str: "int32"}}, + "string": {kind: typeSym, typ: &itype{cat: stringT, name: "string", str: "string"}}, + "uint": {kind: typeSym, typ: &itype{cat: uintT, name: "uint", str: "uint"}}, + "uint8": {kind: typeSym, typ: &itype{cat: uint8T, name: "uint8", str: "uint8"}}, + "uint16": {kind: typeSym, typ: &itype{cat: uint16T, name: "uint16", str: "uint16"}}, + "uint32": {kind: typeSym, typ: &itype{cat: uint32T, name: "uint32", str: "uint32"}}, + "uint64": {kind: typeSym, typ: &itype{cat: uint64T, name: "uint64", str: "uint64"}}, + "uintptr": {kind: typeSym, typ: &itype{cat: uintptrT, name: "uintptr", str: "uintptr"}}, + + // predefined Go constants + "false": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(false)}, + "true": {kind: constSym, typ: untypedBool(nil), rval: reflect.ValueOf(true)}, + "iota": {kind: constSym, typ: untypedInt(nil)}, + + // predefined Go zero value + "nil": {typ: &itype{cat: nilT, untyped: true, str: "nil"}}, + + // predefined Go builtins + bltnAppend: {kind: bltnSym, builtin: _append}, + bltnCap: {kind: bltnSym, builtin: _cap}, + bltnClose: {kind: bltnSym, builtin: _close}, + bltnComplex: {kind: bltnSym, builtin: _complex}, + bltnImag: {kind: bltnSym, builtin: _imag}, + bltnCopy: {kind: bltnSym, builtin: _copy}, + bltnDelete: {kind: bltnSym, builtin: _delete}, + bltnLen: {kind: bltnSym, builtin: _len}, + bltnMake: {kind: bltnSym, builtin: _make}, + bltnNew: {kind: bltnSym, builtin: _new}, + bltnPanic: {kind: bltnSym, builtin: _panic}, + bltnPrint: {kind: bltnSym, builtin: _print}, + bltnPrintln: {kind: bltnSym, builtin: _println}, + bltnReal: {kind: bltnSym, builtin: _real}, + bltnRecover: {kind: bltnSym, builtin: _recover}, + }} + return sc +} + +// resizeFrame resizes the global frame of interpreter. +func (interp *Interpreter) resizeFrame() { + l := len(interp.universe.types) + b := len(interp.frame.data) + if l-b <= 0 { + return + } + data := make([]reflect.Value, l) + copy(data, interp.frame.data) + for j, t := range interp.universe.types[b:] { + data[b+j] = reflect.New(t).Elem() + } + interp.frame.data = data +} + +// Eval evaluates Go code represented as a string. Eval returns the last result +// computed by the interpreter, and a non nil error in case of failure. +func (interp *Interpreter) Eval(src string) (res reflect.Value, err error) { + return interp.eval(src, "", true) +} + +// EvalPath evaluates Go code located at path and returns the last result computed +// by the interpreter, and a non nil error in case of failure. +// The main function of the main package is executed if present. +func (interp *Interpreter) EvalPath(path string) (res reflect.Value, err error) { + if !isFile(interp.opt.filesystem, path) { + _, err := interp.importSrc(mainID, path, NoTest) + return res, err + } + + b, err := fs.ReadFile(interp.filesystem, path) + if err != nil { + return res, err + } + return interp.eval(string(b), path, false) +} + +// EvalPathWithContext evaluates Go code located at path and returns the last +// result computed by the interpreter, and a non nil error in case of failure. +// The main function of the main package is executed if present. +func (interp *Interpreter) EvalPathWithContext(ctx context.Context, path string) (res reflect.Value, err error) { + interp.mutex.Lock() + interp.done = make(chan struct{}) + interp.cancelChan = !interp.opt.fastChan + interp.mutex.Unlock() + + done := make(chan struct{}) + go func() { + defer close(done) + res, err = interp.EvalPath(path) + }() + + select { + case <-ctx.Done(): + interp.stop() + return reflect.Value{}, ctx.Err() + case <-done: + } + return res, err +} + +// EvalTest evaluates Go code located at path, including test files with "_test.go" suffix. +// A non nil error is returned in case of failure. +// The main function, test functions and benchmark functions are internally compiled but not +// executed. Test functions can be retrieved using the Symbol() method. +func (interp *Interpreter) EvalTest(path string) error { + _, err := interp.importSrc(mainID, path, Test) + return err +} + +func isFile(filesystem fs.FS, path string) bool { + fi, err := fs.Stat(filesystem, path) + return err == nil && fi.Mode().IsRegular() +} + +func (interp *Interpreter) eval(src, name string, inc bool) (res reflect.Value, err error) { + prog, err := interp.compileSrc(src, name, inc) + if err != nil { + return res, err + } + + if interp.noRun { + return res, err + } + + return interp.Execute(prog) +} + +// EvalWithContext evaluates Go code represented as a string. It returns +// a map on current interpreted package exported symbols. +func (interp *Interpreter) EvalWithContext(ctx context.Context, src string) (reflect.Value, error) { + var v reflect.Value + var err error + + interp.mutex.Lock() + interp.done = make(chan struct{}) + interp.cancelChan = !interp.opt.fastChan + interp.mutex.Unlock() + + done := make(chan struct{}) + go func() { + defer func() { + if r := recover(); r != nil { + var pc [64]uintptr + n := runtime.Callers(1, pc[:]) + err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()} + } + close(done) + }() + v, err = interp.Eval(src) + }() + + select { + case <-ctx.Done(): + interp.stop() + return reflect.Value{}, ctx.Err() + case <-done: + } + return v, err +} + +// stop sends a semaphore to all running frames and closes the chan +// operation short circuit channel. stop may only be called once per +// invocation of EvalWithContext. +func (interp *Interpreter) stop() { + atomic.AddUint64(&interp.id, 1) + close(interp.done) +} + +func (interp *Interpreter) runid() uint64 { return atomic.LoadUint64(&interp.id) } + +// ignoreScannerError returns true if the error from Go scanner can be safely ignored +// to let the caller grab one more line before retrying to parse its input. +func ignoreScannerError(e *scanner.Error, s string) bool { + msg := e.Msg + if strings.HasSuffix(msg, "found 'EOF'") { + return true + } + if msg == "raw string literal not terminated" { + return true + } + if strings.HasPrefix(msg, "expected operand, found '}'") && !strings.HasSuffix(s, "}") { + return true + } + return false +} + +// ImportUsed automatically imports pre-compiled packages included by Use(). +// This is mainly useful for REPLs, or single command lines. In case of an ambiguous default +// package name, for example "rand" for crypto/rand and math/rand, the package name is +// constructed by replacing the last "/" by a "_", producing crypto_rand and math_rand. +// ImportUsed should not be called more than once, and not after a first Eval, as it may +// rename packages. +func (interp *Interpreter) ImportUsed() { + sc := interp.universe + for k := range interp.binPkg { + // By construction, the package name is the last path element of the key. + name := path.Base(k) + if sym, ok := sc.sym[name]; ok { + // Handle collision by renaming old and new entries. + name2 := key2name(fixKey(sym.typ.path)) + sc.sym[name2] = sym + if name2 != name { + delete(sc.sym, name) + } + name = key2name(fixKey(k)) + } + sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: k, scope: sc}} + } +} + +func key2name(name string) string { + return filepath.Join(name, DefaultSourceName) +} + +func fixKey(k string) string { + i := strings.LastIndex(k, "/") + if i >= 0 { + k = k[:i] + "_" + k[i+1:] + } + return k +} + +// REPL performs a Read-Eval-Print-Loop on input reader. +// Results are printed to the output writer of the Interpreter, provided as option +// at creation time. Errors are printed to the similarly defined errors writer. +// The last interpreter result value and error are returned. +func (interp *Interpreter) REPL() (reflect.Value, error) { + in, out, errs := interp.stdin, interp.stdout, interp.stderr + ctx, cancel := context.WithCancel(context.Background()) + end := make(chan struct{}) // channel to terminate the REPL + sig := make(chan os.Signal, 1) // channel to trap interrupt signal (Ctrl-C) + lines := make(chan string) // channel to read REPL input lines + prompt := getPrompt(in, out) // prompt activated on tty like IO stream + s := bufio.NewScanner(in) // read input stream line by line + var v reflect.Value // result value from eval + var err error // error from eval + src := "" // source string to evaluate + + signal.Notify(sig, os.Interrupt) + defer signal.Stop(sig) + prompt(v) + + go func() { + defer close(end) + for s.Scan() { + lines <- s.Text() + } + if e := s.Err(); e != nil { + fmt.Fprintln(errs, e) + } + }() + + go func() { + for { + select { + case <-sig: + cancel() + lines <- "" + case <-end: + return + } + } + }() + + for { + var line string + + select { + case <-end: + cancel() + return v, err + case line = <-lines: + src += line + "\n" + } + + v, err = interp.EvalWithContext(ctx, src) + if err != nil { + switch e := err.(type) { + case scanner.ErrorList: + if len(e) > 0 && ignoreScannerError(e[0], line) { + continue + } + fmt.Fprintln(errs, strings.TrimPrefix(e[0].Error(), DefaultSourceName+":")) + case Panic: + fmt.Fprintln(errs, e.Value) + fmt.Fprintln(errs, string(e.Stack)) + default: + fmt.Fprintln(errs, err) + } + } + if errors.Is(err, context.Canceled) { + ctx, cancel = context.WithCancel(context.Background()) + } + src = "" + prompt(v) + } +} + +func doPrompt(out io.Writer) func(v reflect.Value) { + return func(v reflect.Value) { + if v.IsValid() { + fmt.Fprintln(out, ":", v) + } + fmt.Fprint(out, "> ") + } +} + +// getPrompt returns a function which prints a prompt only if input is a terminal. +func getPrompt(in io.Reader, out io.Writer) func(reflect.Value) { + forcePrompt, _ := strconv.ParseBool(os.Getenv("YAEGI_PROMPT")) + if forcePrompt { + return doPrompt(out) + } + s, ok := in.(interface{ Stat() (os.FileInfo, error) }) + if !ok { + return func(reflect.Value) {} + } + stat, err := s.Stat() + if err == nil && stat.Mode()&os.ModeCharDevice != 0 { + return doPrompt(out) + } + return func(reflect.Value) {} +} diff --git a/internal/yaegi/interp/op.go b/internal/yaegi/interp/op.go new file mode 100644 index 00000000..6b1f4479 --- /dev/null +++ b/internal/yaegi/interp/op.go @@ -0,0 +1,5131 @@ +package interp + +// Code generated by 'go run ../internal/cmd/genop/genop.go'. DO NOT EDIT. + +import ( + "go/constant" + "go/token" + "reflect" +) + +// Arithmetic operators + +func add(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.String: + switch { + case isInterface: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(v0(f).String() + v1(f).String()).Convert(typ)) + return next + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + dest(f).SetString(s0 + v1(f).String()) + return next + } + case c1.rval.IsValid(): + v0 := genValue(c0) + s1 := vString(c1.rval) + n.exec = func(f *frame) bltn { + dest(f).SetString(v0(f).String() + s1) + return next + } + default: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + dest(f).SetString(v0(f).String() + v1(f).String()) + return next + } + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i + j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i + j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i + j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i + j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i + j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i + j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i + j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i + j) + return next + } + } + case reflect.Float32, reflect.Float64: + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i + j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vFloat(c0.rval) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetFloat(i + j) + return next + } + case c1.rval.IsValid(): + j := vFloat(c1.rval) + v0 := genValueFloat(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetFloat(i + j) + return next + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetFloat(i + j) + return next + } + } + case reflect.Complex64, reflect.Complex128: + switch { + case isInterface: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(v0(f) + v1(f)).Convert(typ)) + return next + } + case c0.rval.IsValid(): + r0 := vComplex(c0.rval) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(r0 + v1(f)) + return next + } + case c1.rval.IsValid(): + r1 := vComplex(c1.rval) + v0 := genComplex(c0) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) + r1) + return next + } + default: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) + v1(f)) + return next + } + } + } +} + +func addConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(vConstantValue(v0), token.ADD, vConstantValue(v1)) + n.rval.Set(reflect.ValueOf(v)) + case isString(t): + n.rval.SetString(vString(v0) + vString(v1)) + case isComplex(t): + n.rval.SetComplex(vComplex(v0) + vComplex(v1)) + case isFloat(t): + n.rval.SetFloat(vFloat(v0) + vFloat(v1)) + case isUint(t): + n.rval.SetUint(vUint(v0) + vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) + vInt(v1)) + } +} + +func and(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i & j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i & j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i & j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i & j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i & j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i & j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i & j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i & j) + return next + } + } + } +} + +func andConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(constant.ToInt(vConstantValue(v0)), token.AND, constant.ToInt(vConstantValue(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) & vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) & vInt(v1)) + } +} + +func andNot(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i &^ j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i &^ j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i &^ j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i &^ j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i &^ j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i &^ j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i &^ j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i &^ j) + return next + } + } + } +} + +func andNotConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(constant.ToInt(vConstantValue(v0)), token.AND_NOT, constant.ToInt(vConstantValue(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) &^ vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) &^ vInt(v1)) + } +} + +func mul(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i * j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i * j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i * j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i * j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i * j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i * j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i * j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i * j) + return next + } + } + case reflect.Float32, reflect.Float64: + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i * j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vFloat(c0.rval) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetFloat(i * j) + return next + } + case c1.rval.IsValid(): + j := vFloat(c1.rval) + v0 := genValueFloat(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetFloat(i * j) + return next + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetFloat(i * j) + return next + } + } + case reflect.Complex64, reflect.Complex128: + switch { + case isInterface: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(v0(f) * v1(f)).Convert(typ)) + return next + } + case c0.rval.IsValid(): + r0 := vComplex(c0.rval) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(r0 * v1(f)) + return next + } + case c1.rval.IsValid(): + r1 := vComplex(c1.rval) + v0 := genComplex(c0) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) * r1) + return next + } + default: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) * v1(f)) + return next + } + } + } +} + +func mulConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(vConstantValue(v0), token.MUL, vConstantValue(v1)) + n.rval.Set(reflect.ValueOf(v)) + case isComplex(t): + n.rval.SetComplex(vComplex(v0) * vComplex(v1)) + case isFloat(t): + n.rval.SetFloat(vFloat(v0) * vFloat(v1)) + case isUint(t): + n.rval.SetUint(vUint(v0) * vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) * vInt(v1)) + } +} + +func or(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i | j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i | j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i | j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i | j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i | j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i | j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i | j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i | j) + return next + } + } + } +} + +func orConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(constant.ToInt(vConstantValue(v0)), token.OR, constant.ToInt(vConstantValue(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) | vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) | vInt(v1)) + } +} + +func quo(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i / j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i / j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i / j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i / j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i / j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i / j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i / j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i / j) + return next + } + } + case reflect.Float32, reflect.Float64: + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i / j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vFloat(c0.rval) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetFloat(i / j) + return next + } + case c1.rval.IsValid(): + j := vFloat(c1.rval) + v0 := genValueFloat(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetFloat(i / j) + return next + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetFloat(i / j) + return next + } + } + case reflect.Complex64, reflect.Complex128: + switch { + case isInterface: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(v0(f) / v1(f)).Convert(typ)) + return next + } + case c0.rval.IsValid(): + r0 := vComplex(c0.rval) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(r0 / v1(f)) + return next + } + case c1.rval.IsValid(): + r1 := vComplex(c1.rval) + v0 := genComplex(c0) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) / r1) + return next + } + default: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) / v1(f)) + return next + } + } + } +} + +func quoConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + var operator token.Token + // When the result of the operation is expected to be an int (because both + // operands are ints), we want to force the type of the whole expression to be an + // int (and not a float), which is achieved by using the QUO_ASSIGN operator. + if n.typ.untyped && isInt(n.typ.rtype) { + operator = token.QUO_ASSIGN + } else { + operator = token.QUO + } + v := constant.BinaryOp(vConstantValue(v0), operator, vConstantValue(v1)) + n.rval.Set(reflect.ValueOf(v)) + case isComplex(t): + n.rval.SetComplex(vComplex(v0) / vComplex(v1)) + case isFloat(t): + n.rval.SetFloat(vFloat(v0) / vFloat(v1)) + case isUint(t): + n.rval.SetUint(vUint(v0) / vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) / vInt(v1)) + } +} + +func rem(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i % j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i % j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i % j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i % j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i % j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i % j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i % j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i % j) + return next + } + } + } +} + +func remConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(constant.ToInt(vConstantValue(v0)), token.REM, constant.ToInt(vConstantValue(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) % vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) % vInt(v1)) + } +} + +func shl(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i << j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i << j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i << j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i << j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i << j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i << j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i << j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i << j) + return next + } + } + } +} + +func shlConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.Shift(vConstantValue(v0), token.SHL, uint(vUint(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) << vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) << vUint(v1)) + } +} + +func shr(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i >> j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i >> j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i >> j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i >> j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i >> j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i >> j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i >> j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i >> j) + return next + } + } + } +} + +func shrConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.Shift(vConstantValue(v0), token.SHR, uint(vUint(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) >> vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) >> vUint(v1)) + } +} + +func sub(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i - j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i - j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i - j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i - j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i - j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i - j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i - j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i - j) + return next + } + } + case reflect.Float32, reflect.Float64: + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i - j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vFloat(c0.rval) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetFloat(i - j) + return next + } + case c1.rval.IsValid(): + j := vFloat(c1.rval) + v0 := genValueFloat(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetFloat(i - j) + return next + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetFloat(i - j) + return next + } + } + case reflect.Complex64, reflect.Complex128: + switch { + case isInterface: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(v0(f) - v1(f)).Convert(typ)) + return next + } + case c0.rval.IsValid(): + r0 := vComplex(c0.rval) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(r0 - v1(f)) + return next + } + case c1.rval.IsValid(): + r1 := vComplex(c1.rval) + v0 := genComplex(c0) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) - r1) + return next + } + default: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + dest(f).SetComplex(v0(f) - v1(f)) + return next + } + } + } +} + +func subConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(vConstantValue(v0), token.SUB, vConstantValue(v1)) + n.rval.Set(reflect.ValueOf(v)) + case isComplex(t): + n.rval.SetComplex(vComplex(v0) - vComplex(v1)) + case isFloat(t): + n.rval.SetFloat(vFloat(v0) - vFloat(v1)) + case isUint(t): + n.rval.SetUint(vUint(v0) - vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) - vInt(v1)) + } +} + +func xor(n *node) { + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + dest := genValueOutput(n, typ) + c0, c1 := n.child[0], n.child[1] + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i ^ j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vInt(c0.rval) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetInt(i ^ j) + return next + } + case c1.rval.IsValid(): + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetInt(i ^ j) + return next + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetInt(i ^ j) + return next + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).Set(reflect.ValueOf(i ^ j).Convert(typ)) + return next + } + case c0.rval.IsValid(): + i := vUint(c0.rval) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, j := v1(f) + dest(f).SetUint(i ^ j) + return next + } + case c1.rval.IsValid(): + j := vUint(c1.rval) + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + _, i := v0(f) + dest(f).SetUint(i ^ j) + return next + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, i := v0(f) + _, j := v1(f) + dest(f).SetUint(i ^ j) + return next + } + } + } +} + +func xorConst(n *node) { + v0, v1 := n.child[0].rval, n.child[1].rval + isConst := (v0.IsValid() && isConstantValue(v0.Type())) && (v1.IsValid() && isConstantValue(v1.Type())) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.BinaryOp(constant.ToInt(vConstantValue(v0)), token.XOR, constant.ToInt(vConstantValue(v1))) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(vUint(v0) ^ vUint(v1)) + case isInt(t): + n.rval.SetInt(vInt(v0) ^ vInt(v1)) + } +} + +// Assign operators + +func addAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.String: + v0 := genValueString(c0) + v1 := vString(c1.rval) + n.exec = func(f *frame) bltn { + v, s := v0(f) + v.SetString(s + v1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i + j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i + j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + j := vFloat(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetFloat(i + j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := vComplex(c1.rval) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() + v1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.String: + v0 := genValueString(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + v, s := v0(f) + v.SetString(s + v1(f).String()) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i + j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i + j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetFloat(i + j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() + v1(f).Complex()) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func andAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i & j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i & j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i & j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i & j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func andNotAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i &^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i &^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i &^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i &^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func mulAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i * j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i * j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + j := vFloat(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetFloat(i * j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := vComplex(c1.rval) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() * v1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i * j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i * j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetFloat(i * j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() * v1(f).Complex()) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func orAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i | j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i | j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i | j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i | j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func quoAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i / j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i / j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + j := vFloat(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetFloat(i / j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := vComplex(c1.rval) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() / v1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i / j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i / j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetFloat(i / j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() / v1(f).Complex()) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func remAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i % j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i % j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i % j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i % j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func shlAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i << j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i << j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i << j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i << j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func shrAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i >> j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i >> j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i >> j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i >> j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func subAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i - j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i - j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + j := vFloat(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetFloat(i - j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := vComplex(c1.rval) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() - v1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i - j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i - j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetFloat(i - j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() - v1(f).Complex()) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func xorAssign(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0, c1 := n.child[0], n.child[1] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + if c1.rval.IsValid() { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + j := vInt(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i ^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + j := vUint(c1.rval) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i ^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } else { + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetInt(i ^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + v, i := v0(f) + _, j := v1(f) + v.SetUint(i ^ j) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } + } +} + +func dec(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0 := n.child[0] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i - 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i - 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetFloat(i - 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() - 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } +} + +func inc(n *node) { + next := getExec(n.tnext) + typ := n.typ.TypeOf() + c0 := n.child[0] + setMap := isMapEntry(c0) + var mapValue, indexValue func(*frame) reflect.Value + + if setMap { + mapValue = genValue(c0.child[0]) + indexValue = genValue(c0.child[1]) + } + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v0 := genValueInt(c0) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetInt(i + 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v0 := genValueUint(c0) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetUint(i + 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Float32, reflect.Float64: + v0 := genValueFloat(c0) + n.exec = func(f *frame) bltn { + v, i := v0(f) + v.SetFloat(i + 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + case reflect.Complex64, reflect.Complex128: + v0 := genValue(c0) + n.exec = func(f *frame) bltn { + v := v0(f) + v.SetComplex(v.Complex() + 1) + if setMap { + mapValue(f).SetMapIndex(indexValue(f), v) + } + return next + } + } +} + +func bitNotConst(n *node) { + v0 := n.child[0].rval + isConst := v0.IsValid() && isConstantValue(v0.Type()) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.UnaryOp(token.XOR, vConstantValue(v0), 0) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(^v0.Uint()) + case isInt(t): + n.rval.SetInt(^v0.Int()) + } +} + +func negConst(n *node) { + v0 := n.child[0].rval + isConst := v0.IsValid() && isConstantValue(v0.Type()) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.UnaryOp(token.SUB, vConstantValue(v0), 0) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(-v0.Uint()) + case isInt(t): + n.rval.SetInt(-v0.Int()) + case isFloat(t): + n.rval.SetFloat(-v0.Float()) + case isComplex(t): + n.rval.SetComplex(-v0.Complex()) + } +} + +func notConst(n *node) { + v0 := n.child[0].rval + isConst := v0.IsValid() && isConstantValue(v0.Type()) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + if isConst { + v := constant.UnaryOp(token.NOT, vConstantValue(v0), 0) + n.rval.Set(reflect.ValueOf(v)) + } else { + n.rval.SetBool(!v0.Bool()) + } +} + +func posConst(n *node) { + v0 := n.child[0].rval + isConst := v0.IsValid() && isConstantValue(v0.Type()) + t := n.typ.rtype + if isConst { + t = constVal + } + n.rval = reflect.New(t).Elem() + switch { + case isConst: + v := constant.UnaryOp(token.ADD, vConstantValue(v0), 0) + n.rval.Set(reflect.ValueOf(v)) + case isUint(t): + n.rval.SetUint(+v0.Uint()) + case isInt(t): + n.rval.SetInt(+v0.Int()) + case isFloat(t): + n.rval.SetFloat(+v0.Float()) + case isComplex(t): + n.rval.SetComplex(+v0.Complex()) + } +} + +func equal(n *node) { + tnext := getExec(n.tnext) + dest := genValueOutput(n, reflect.TypeOf(true)) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + if c0.typ.cat == linkedT || c1.typ.cat == linkedT { + switch { + case isInterface: + v0 := genValue(c0) + v1 := genValue(c1) + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).Set(reflect.ValueOf(i0 == i1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + i0 := c0.rval.Interface() + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + case c1.rval.IsValid(): + i1 := c1.rval.Interface() + v0 := genValue(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + default: + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + } + return + } + + // Do not attempt to optimize '==' or '!=' if an operand is an interface. + // This will preserve proper dynamic type checking at runtime. For static types, + // type checks are already performed, so bypass them if possible. + if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface { + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + return + } + + switch { + case isString(t0) || isString(t1): + switch { + case isInterface: + v0 := genValueString(c0) + v1 := genValueString(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 == s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vString(c1.rval) + v0 := genValueString(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + default: + v0 := genValueString(c0) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + } + case isFloat(t0) || isFloat(t1): + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 == s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vFloat(c0.rval) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vFloat(c1.rval) + v0 := genValueFloat(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + } + case isUint(t0) || isUint(t1): + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 == s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vUint(c0.rval) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vUint(c1.rval) + v0 := genValueUint(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + } + case isInt(t0) || isInt(t1): + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 == s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vInt(c0.rval) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vInt(c1.rval) + v0 := genValueInt(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + } + case isComplex(t0) || isComplex(t1): + switch { + case isInterface: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + s0 := v0(f) + s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 == s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vComplex(c0.rval) + v1 := genComplex(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vComplex(c1.rval) + v0 := genComplex(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + s0 := v0(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + s0 := v0(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + default: + v0 := genComplex(c0) + v1 := genComplex(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + s0 := v0(f) + s1 := v1(f) + if s0 == s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + s0 := v0(f) + s1 := v1(f) + dest(f).SetBool(s0 == s1) + return tnext + } + } + } + default: + switch { + case isInterface: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).Set(reflect.ValueOf(i0 == i1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + i0 := c0.rval.Interface() + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + case c1.rval.IsValid(): + i1 := c1.rval.Interface() + v0 := genValue(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + default: + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + } + } +} + +func greater(n *node) { + tnext := getExec(n.tnext) + dest := genValueOutput(n, reflect.TypeOf(true)) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + switch { + case isString(t0) || isString(t1): + switch { + case isInterface: + v0 := genValueString(c0) + v1 := genValueString(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 > s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vString(c1.rval) + v0 := genValueString(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + default: + v0 := genValueString(c0) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + } + case isFloat(t0) || isFloat(t1): + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 > s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vFloat(c0.rval) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vFloat(c1.rval) + v0 := genValueFloat(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + } + case isUint(t0) || isUint(t1): + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 > s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vUint(c0.rval) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vUint(c1.rval) + v0 := genValueUint(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + } + case isInt(t0) || isInt(t1): + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 > s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vInt(c0.rval) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vInt(c1.rval) + v0 := genValueInt(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 > s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 > s1) + return tnext + } + } + } + } +} + +func greaterEqual(n *node) { + tnext := getExec(n.tnext) + dest := genValueOutput(n, reflect.TypeOf(true)) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + switch { + case isString(t0) || isString(t1): + switch { + case isInterface: + v0 := genValueString(c0) + v1 := genValueString(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 >= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vString(c1.rval) + v0 := genValueString(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + default: + v0 := genValueString(c0) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + } + case isFloat(t0) || isFloat(t1): + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 >= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vFloat(c0.rval) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vFloat(c1.rval) + v0 := genValueFloat(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + } + case isUint(t0) || isUint(t1): + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 >= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vUint(c0.rval) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vUint(c1.rval) + v0 := genValueUint(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + } + case isInt(t0) || isInt(t1): + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 >= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vInt(c0.rval) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vInt(c1.rval) + v0 := genValueInt(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 >= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 >= s1) + return tnext + } + } + } + } +} + +func lower(n *node) { + tnext := getExec(n.tnext) + dest := genValueOutput(n, reflect.TypeOf(true)) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + switch { + case isString(t0) || isString(t1): + switch { + case isInterface: + v0 := genValueString(c0) + v1 := genValueString(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 < s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vString(c1.rval) + v0 := genValueString(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + default: + v0 := genValueString(c0) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + } + case isFloat(t0) || isFloat(t1): + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 < s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vFloat(c0.rval) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vFloat(c1.rval) + v0 := genValueFloat(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + } + case isUint(t0) || isUint(t1): + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 < s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vUint(c0.rval) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vUint(c1.rval) + v0 := genValueUint(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + } + case isInt(t0) || isInt(t1): + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 < s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vInt(c0.rval) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vInt(c1.rval) + v0 := genValueInt(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 < s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 < s1) + return tnext + } + } + } + } +} + +func lowerEqual(n *node) { + tnext := getExec(n.tnext) + dest := genValueOutput(n, reflect.TypeOf(true)) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + switch { + case isString(t0) || isString(t1): + switch { + case isInterface: + v0 := genValueString(c0) + v1 := genValueString(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 <= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vString(c1.rval) + v0 := genValueString(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + default: + v0 := genValueString(c0) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + } + case isFloat(t0) || isFloat(t1): + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 <= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vFloat(c0.rval) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vFloat(c1.rval) + v0 := genValueFloat(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + } + case isUint(t0) || isUint(t1): + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 <= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vUint(c0.rval) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vUint(c1.rval) + v0 := genValueUint(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + } + case isInt(t0) || isInt(t1): + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 <= s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vInt(c0.rval) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vInt(c1.rval) + v0 := genValueInt(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 <= s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 <= s1) + return tnext + } + } + } + } +} + +func notEqual(n *node) { + tnext := getExec(n.tnext) + dest := genValueOutput(n, reflect.TypeOf(true)) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + if c0.typ.cat == linkedT || c1.typ.cat == linkedT { + switch { + case isInterface: + v0 := genValue(c0) + v1 := genValue(c1) + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).Set(reflect.ValueOf(i0 != i1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + i0 := c0.rval.Interface() + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + case c1.rval.IsValid(): + i1 := c1.rval.Interface() + v0 := genValue(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + default: + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + } + return + } + + // Do not attempt to optimize '==' or '!=' if an operand is an interface. + // This will preserve proper dynamic type checking at runtime. For static types, + // type checks are already performed, so bypass them if possible. + if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface { + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + return + } + + switch { + case isString(t0) || isString(t1): + switch { + case isInterface: + v0 := genValueString(c0) + v1 := genValueString(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 != s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vString(c0.rval) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vString(c1.rval) + v0 := genValueString(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + default: + v0 := genValueString(c0) + v1 := genValueString(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + } + case isFloat(t0) || isFloat(t1): + switch { + case isInterface: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 != s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vFloat(c0.rval) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vFloat(c1.rval) + v0 := genValueFloat(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + default: + v0 := genValueFloat(c0) + v1 := genValueFloat(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + } + case isUint(t0) || isUint(t1): + switch { + case isInterface: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 != s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vUint(c0.rval) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vUint(c1.rval) + v0 := genValueUint(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + default: + v0 := genValueUint(c0) + v1 := genValueUint(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + } + case isInt(t0) || isInt(t1): + switch { + case isInterface: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 != s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vInt(c0.rval) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vInt(c1.rval) + v0 := genValueInt(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + default: + v0 := genValueInt(c0) + v1 := genValueInt(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + _, s0 := v0(f) + _, s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + } + case isComplex(t0) || isComplex(t1): + switch { + case isInterface: + v0 := genComplex(c0) + v1 := genComplex(c1) + n.exec = func(f *frame) bltn { + s0 := v0(f) + s1 := v1(f) + dest(f).Set(reflect.ValueOf(s0 != s1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + s0 := vComplex(c0.rval) + v1 := genComplex(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + case c1.rval.IsValid(): + s1 := vComplex(c1.rval) + v0 := genComplex(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + s0 := v0(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + s0 := v0(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + default: + v0 := genComplex(c0) + v1 := genComplex(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + s0 := v0(f) + s1 := v1(f) + if s0 != s1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + s0 := v0(f) + s1 := v1(f) + dest(f).SetBool(s0 != s1) + return tnext + } + } + } + default: + switch { + case isInterface: + v0 := genValue(c0) + v1 := genValue(c1) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).Set(reflect.ValueOf(i0 != i1).Convert(typ)) + return tnext + } + case c0.rval.IsValid(): + i0 := c0.rval.Interface() + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i1 := v1(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + case c1.rval.IsValid(): + i1 := c1.rval.Interface() + v0 := genValue(c0) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + default: + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + } + } +} diff --git a/internal/yaegi/interp/program.go b/internal/yaegi/interp/program.go new file mode 100644 index 00000000..90bbbe44 --- /dev/null +++ b/internal/yaegi/interp/program.go @@ -0,0 +1,207 @@ +package interp + +import ( + "context" + "go/ast" + "go/token" + "os" + "reflect" + "runtime" + "runtime/debug" +) + +// A Program is Go code that has been parsed and compiled. +type Program struct { + pkgName string + root *node + init []*node +} + +// PackageName returns name used in a package clause. +func (p *Program) PackageName() string { + return p.pkgName +} + +// FileSet is the fileset that must be used for parsing Go that will be passed +// to interp.CompileAST(). +func (interp *Interpreter) FileSet() *token.FileSet { + return interp.fset +} + +// Compile parses and compiles a Go code represented as a string. +func (interp *Interpreter) Compile(src string) (*Program, error) { + return interp.compileSrc(src, "", true) +} + +// CompilePath parses and compiles a Go code located at the given path. +func (interp *Interpreter) CompilePath(path string) (*Program, error) { + if !isFile(interp.filesystem, path) { + _, err := interp.importSrc(mainID, path, NoTest) + return nil, err + } + + b, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return interp.compileSrc(string(b), path, false) +} + +func (interp *Interpreter) compileSrc(src, name string, inc bool) (*Program, error) { + if name != "" { + interp.name = name + } + if interp.name == "" { + interp.name = DefaultSourceName + } + + // Parse source to AST. + n, err := interp.parse(src, interp.name, inc) + if err != nil { + return nil, err + } + + return interp.CompileAST(n) +} + +// CompileAST builds a Program for the given Go code AST. Files and block +// statements can be compiled, as can most expressions. Var declaration nodes +// cannot be compiled. +// +// WARNING: The node must have been parsed using interp.FileSet(). Results are +// unpredictable otherwise. +func (interp *Interpreter) CompileAST(n ast.Node) (*Program, error) { + // Convert AST. + pkgName, root, err := interp.ast(n) + if err != nil || root == nil { + return nil, err + } + + if interp.astDot { + dotCmd := interp.dotCmd + if dotCmd == "" { + dotCmd = defaultDotCmd(interp.name, "yaegi-ast-") + } + root.astDot(dotWriter(dotCmd), interp.name) + if interp.noRun { + return nil, err + } + } + + // Perform global types analysis. + if err = interp.gtaRetry([]*node{root}, pkgName, pkgName); err != nil { + return nil, err + } + + // Annotate AST with CFG informations. + initNodes, err := interp.cfg(root, nil, pkgName, pkgName) + if err != nil { + if interp.cfgDot { + dotCmd := interp.dotCmd + if dotCmd == "" { + dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-") + } + root.cfgDot(dotWriter(dotCmd)) + } + return nil, err + } + + if root.kind != fileStmt { + // REPL may skip package statement. + setExec(root.start) + } + interp.mutex.Lock() + gs := interp.scopes[pkgName] + if interp.universe.sym[pkgName] == nil { + // Make the package visible under a path identical to its name. + interp.srcPkg[pkgName] = gs.sym + interp.universe.sym[pkgName] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: pkgName}} + interp.pkgNames[pkgName] = pkgName + } + interp.mutex.Unlock() + + // Add main to list of functions to run, after all inits. + if m := gs.sym[mainID]; pkgName == mainID && m != nil { + initNodes = append(initNodes, m.node) + } + + if interp.cfgDot { + dotCmd := interp.dotCmd + if dotCmd == "" { + dotCmd = defaultDotCmd(interp.name, "yaegi-cfg-") + } + root.cfgDot(dotWriter(dotCmd)) + } + + return &Program{pkgName, root, initNodes}, nil +} + +// Execute executes compiled Go code. +func (interp *Interpreter) Execute(p *Program) (res reflect.Value, err error) { + defer func() { + r := recover() + if r != nil { + var pc [64]uintptr // 64 frames should be enough. + n := runtime.Callers(1, pc[:]) + err = Panic{Value: r, Callers: pc[:n], Stack: debug.Stack()} + } + }() + + // Generate node exec closures. + if err = genRun(p.root); err != nil { + return res, err + } + + // Init interpreter execution memory frame. + interp.frame.setrunid(interp.runid()) + interp.frame.mutex.Lock() + interp.resizeFrame() + interp.frame.mutex.Unlock() + + // Execute node closures. + interp.run(p.root, nil) + + // Wire and execute global vars. + n, err := genGlobalVars([]*node{p.root}, interp.scopes[p.pkgName]) + if err != nil { + return res, err + } + interp.run(n, nil) + + for _, n := range p.init { + interp.run(n, interp.frame) + } + v := genValue(p.root) + res = v(interp.frame) + + // If result is an interpreter node, wrap it in a runtime callable function. + if res.IsValid() { + if n, ok := res.Interface().(*node); ok { + res = genFunctionWrapper(n)(interp.frame) + } + } + + return res, err +} + +// ExecuteWithContext executes compiled Go code. +func (interp *Interpreter) ExecuteWithContext(ctx context.Context, p *Program) (res reflect.Value, err error) { + interp.mutex.Lock() + interp.done = make(chan struct{}) + interp.cancelChan = !interp.opt.fastChan + interp.mutex.Unlock() + + done := make(chan struct{}) + go func() { + defer close(done) + res, err = interp.Execute(p) + }() + + select { + case <-ctx.Done(): + interp.stop() + return reflect.Value{}, ctx.Err() + case <-done: + } + return res, err +} diff --git a/internal/yaegi/interp/realfs.go b/internal/yaegi/interp/realfs.go new file mode 100644 index 00000000..9f680abc --- /dev/null +++ b/internal/yaegi/interp/realfs.go @@ -0,0 +1,21 @@ +package interp + +import ( + "io/fs" + "os" +) + +// realFS complies with the fs.FS interface (go 1.16 onwards) +// We use this rather than os.DirFS as DirFS has no concept of +// what the current working directory is, whereas this simple +// passthru to os.Open knows about working dir automagically. +type realFS struct{} + +// Open complies with the fs.FS interface. +func (dir realFS) Open(name string) (fs.File, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/internal/yaegi/interp/run.go b/internal/yaegi/interp/run.go new file mode 100644 index 00000000..cebcfd71 --- /dev/null +++ b/internal/yaegi/interp/run.go @@ -0,0 +1,4090 @@ +package interp + +//go:generate go run ../internal/cmd/genop/genop.go + +import ( + "errors" + "fmt" + "go/constant" + "reflect" + "regexp" + "strings" +) + +// bltn type defines functions which run at CFG execution. +type bltn func(f *frame) bltn + +// bltnGenerator type defines a builtin generator function. +type bltnGenerator func(n *node) + +var builtin = [...]bltnGenerator{ + aNop: nop, + aAddr: addr, + aAssign: assign, + aAdd: add, + aAddAssign: addAssign, + aAnd: and, + aAndAssign: andAssign, + aAndNot: andNot, + aAndNotAssign: andNotAssign, + aBitNot: bitNot, + aCall: call, + aCallSlice: call, + aCase: _case, + aCompositeLit: arrayLit, + aDec: dec, + aEqual: equal, + aGetFunc: getFunc, + aGreater: greater, + aGreaterEqual: greaterEqual, + aInc: inc, + aLand: land, + aLor: lor, + aLower: lower, + aLowerEqual: lowerEqual, + aMul: mul, + aMulAssign: mulAssign, + aNeg: neg, + aNot: not, + aNotEqual: notEqual, + aOr: or, + aOrAssign: orAssign, + aPos: pos, + aQuo: quo, + aQuoAssign: quoAssign, + aRange: _range, + aRecv: recv, + aRem: rem, + aRemAssign: remAssign, + aReturn: _return, + aSend: send, + aShl: shl, + aShlAssign: shlAssign, + aShr: shr, + aShrAssign: shrAssign, + aSlice: slice, + aSlice0: slice0, + aStar: deref, + aSub: sub, + aSubAssign: subAssign, + aTypeAssert: typeAssertShort, + aXor: xor, + aXorAssign: xorAssign, +} + +var receiverStripperRxp *regexp.Regexp + +func init() { + re := `func\(((.*?(, |\)))(.*))` + var err error + receiverStripperRxp, err = regexp.Compile(re) + if err != nil { + panic(err) + } +} + +type valueInterface struct { + node *node + value reflect.Value +} + +var floatType, complexType reflect.Type + +func init() { + floatType = reflect.ValueOf(0.0).Type() + complexType = reflect.ValueOf(complex(0, 0)).Type() +} + +func (interp *Interpreter) run(n *node, cf *frame) { + if n == nil { + return + } + var f *frame + if cf == nil { + f = interp.frame + } else { + f = newFrame(cf, len(n.types), interp.runid()) + } + interp.mutex.RLock() + c := reflect.ValueOf(interp.done) + interp.mutex.RUnlock() + + f.mutex.Lock() + f.done = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: c} + f.mutex.Unlock() + + for i, t := range n.types { + f.data[i] = reflect.New(t).Elem() + } + runCfg(n.start, f, n, nil) +} + +func isExecNode(n *node, exec bltn) bool { + if n == nil || n.exec == nil || exec == nil { + return false + } + + a1 := reflect.ValueOf(n.exec).Pointer() + a2 := reflect.ValueOf(exec).Pointer() + return a1 == a2 +} + +// originalExecNode looks in the tree of nodes for the node which has exec, +// aside from n, in order to know where n "inherited" that exec from. +func originalExecNode(n *node, exec bltn) *node { + execAddr := reflect.ValueOf(exec).Pointer() + var originalNode *node + seen := make(map[int64]struct{}) + root := n + for { + root = root.anc + if root == nil { + break + } + if _, ok := seen[root.index]; ok { + continue + } + + root.Walk(func(wn *node) bool { + if _, ok := seen[wn.index]; ok { + return true + } + seen[wn.index] = struct{}{} + if wn.index == n.index { + return true + } + if wn.exec == nil { + return true + } + if reflect.ValueOf(wn.exec).Pointer() == execAddr { + originalNode = wn + return false + } + return true + }, nil) + + if originalNode != nil { + break + } + } + + return originalNode +} + +// cloned from net/http/server.go , so we can enforce a similar behavior: +// in the stdlib, this error is used as sentinel in panic triggered e.g. on +// request cancellation, in order to catch it and suppress it in a following defer. +// in yaegi, we use it to suppress a "panic" log message that happens in the +// same circumstances. +var errAbortHandler = errors.New("net/http: abort Handler") + +// Functions set to run during execution of CFG. + +func panicFunc(s *scope) string { + if s == nil { + return "" + } + def := s.def + if def == nil { + return s.pkgID + } + switch def.kind { + case funcDecl: + if c := def.child[1]; c.kind == identExpr { + return s.pkgID + "." + c.ident + } + case funcLit: + if def.anc != nil { + return panicFunc(def.anc.scope) + ".func" + } + } + return s.pkgID +} + +// runCfg executes a node AST by walking its CFG and running node builtin at each step. +func runCfg(n *node, f *frame, funcNode, callNode *node) { + var exec bltn + defer func() { + f.mutex.Lock() + f.recovered = recover() + for _, val := range f.deferred { + val[0].Call(val[1:]) + } + if f.recovered != nil { + oNode := originalExecNode(n, exec) + if oNode == nil { + oNode = n + } + errorer, ok := f.recovered.(error) + // in this specific case, the stdlib would/will suppress the panic, so we + // suppress the logging here accordingly, to get a similar and consistent + // behavior. + if !ok || errorer.Error() != errAbortHandler.Error() { + fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic: %s(...)", panicFunc(oNode.scope))) + } + f.mutex.Unlock() + panic(f.recovered) + } + f.mutex.Unlock() + }() + + dbg := n.interp.debugger + if dbg == nil { + for exec := n.exec; exec != nil && f.runid() == n.interp.runid(); { + exec = exec(f) + } + return + } + + if n.exec == nil { + return + } + + dbg.enterCall(funcNode, callNode, f) + defer dbg.exitCall(funcNode, callNode, f) + + for m, exec := n, n.exec; f.runid() == n.interp.runid(); { + if dbg.exec(m, f) { + break + } + + exec = exec(f) + if exec == nil { + break + } + + if m == nil { + m = originalExecNode(n, exec) + continue + } + + switch { + case isExecNode(m.tnext, exec): + m = m.tnext + case isExecNode(m.fnext, exec): + m = m.fnext + default: + m = originalExecNode(m, exec) + } + } +} + +func stripReceiverFromArgs(signature string) (string, error) { + fields := receiverStripperRxp.FindStringSubmatch(signature) + if len(fields) < 5 { + return "", errors.New("error while matching method signature") + } + if fields[3] == ")" { + return fmt.Sprintf("func()%s", fields[4]), nil + } + return fmt.Sprintf("func(%s", fields[4]), nil +} + +func typeAssertShort(n *node) { + typeAssert(n, true, false) +} + +func typeAssertLong(n *node) { + typeAssert(n, true, true) +} + +func typeAssertStatus(n *node) { + typeAssert(n, false, true) +} + +func typeAssert(n *node, withResult, withOk bool) { + c0, c1 := n.child[0], n.child[1] + value := genValue(c0) // input value + var value0, value1 func(*frame) reflect.Value + setStatus := false + switch { + case withResult && withOk: + value0 = genValue(n.anc.child[0]) // returned result + value1 = genValue(n.anc.child[1]) // returned status + setStatus = n.anc.child[1].ident != "_" // do not assign status to "_" + case withResult && !withOk: + value0 = genValue(n) // returned result + case !withResult && withOk: + value1 = genValue(n.anc.child[1]) // returned status + setStatus = n.anc.child[1].ident != "_" // do not assign status to "_" + } + + typ := c1.typ // type to assert or convert to + typID := typ.id() + rtype := typ.refType(nil) // type to assert + next := getExec(n.tnext) + + switch { + case isInterfaceSrc(typ): + n.exec = func(f *frame) bltn { + valf := value(f) + v, ok := valf.Interface().(valueInterface) + if setStatus { + defer func() { + value1(f).SetBool(ok) + }() + } + if !ok { + if !withOk { + panic(n.cfgErrorf("interface conversion: nil is not %v", typID)) + } + return next + } + if c0.typ.cat == valueT { + valf = reflect.ValueOf(v) + } + if v.node.typ.id() == typID { + if withResult { + value0(f).Set(valf) + } + return next + } + m0 := v.node.typ.methods() + m1 := typ.methods() + if len(m0) < len(m1) { + ok = false + if !withOk { + panic(n.cfgErrorf("interface conversion: %v is not %v", v.node.typ.id(), typID)) + } + return next + } + + for k, meth1 := range m1 { + var meth0 string + meth0, ok = m0[k] + if !ok { + return next + } + // As far as we know this equality check can fail because they are two ways to + // represent the signature of a method: one where the receiver appears before the + // func keyword, and one where it is just a func signature, and the receiver is + // seen as the first argument. That's why if that equality fails, we try harder to + // compare them afterwards. Hopefully that is the only reason this equality can fail. + if meth0 == meth1 { + continue + } + tm := lookupFieldOrMethod(v.node.typ, k) + if tm == nil { + ok = false + return next + } + + var err error + meth0, err = stripReceiverFromArgs(meth0) + if err != nil { + ok = false + return next + } + + if meth0 != meth1 { + ok = false + return next + } + } + + if withResult { + value0(f).Set(valf) + } + return next + } + case isInterface(typ): + n.exec = func(f *frame) bltn { + var leftType reflect.Type + v := value(f) + val, ok := v.Interface().(valueInterface) + if setStatus { + defer func() { + value1(f).SetBool(ok) + }() + } + if ok && val.node.typ.cat != valueT { + m0 := val.node.typ.methods() + m1 := typ.methods() + if len(m0) < len(m1) { + ok = false + return next + } + + for k, meth1 := range m1 { + var meth0 string + meth0, ok = m0[k] + if !ok { + return next + } + if meth0 != meth1 { + ok = false + return next + } + } + + if withResult { + value0(f).Set(genInterfaceWrapper(val.node, rtype)(f)) + } + ok = true + return next + } + + if ok { + v = val.value + leftType = val.node.typ.rtype + } else { + v = v.Elem() + leftType = v.Type() + ok = true + } + ok = v.IsValid() + if !ok { + if !withOk { + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) + } + return next + } + ok = canAssertTypes(leftType, rtype) + if !ok { + if !withOk { + method := firstMissingMethod(leftType, rtype) + panic(n.cfgErrorf("interface conversion: %s is not %s: missing method %s", leftType.String(), rtype.String(), method)) + } + return next + } + if withResult { + value0(f).Set(v) + } + return next + } + case isEmptyInterface(n.child[0].typ): + n.exec = func(f *frame) bltn { + var ok bool + if setStatus { + defer func() { + value1(f).SetBool(ok) + }() + } + val := value(f) + concrete := val.Interface() + ctyp := reflect.TypeOf(concrete) + + if vv, ok := concrete.(valueInterface); ok { + ctyp = vv.value.Type() + concrete = vv.value.Interface() + } + ok = canAssertTypes(ctyp, rtype) + if !ok { + if !withOk { + // TODO(mpl): think about whether this should ever happen. + if ctyp == nil { + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) + } + panic(n.cfgErrorf("interface conversion: interface {} is %s, not %s", ctyp.String(), rtype.String())) + } + return next + } + if withResult { + if isInterfaceSrc(typ) { + // TODO(mpl): this requires more work. the wrapped node is not complete enough. + value0(f).Set(reflect.ValueOf(valueInterface{n.child[0], reflect.ValueOf(concrete)})) + } else { + value0(f).Set(reflect.ValueOf(concrete)) + } + } + return next + } + case n.child[0].typ.cat == valueT || n.child[0].typ.cat == errorT: + n.exec = func(f *frame) bltn { + v := value(f).Elem() + ok := v.IsValid() + if setStatus { + defer func() { + value1(f).SetBool(ok) + }() + } + if !ok { + if !withOk { + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) + } + return next + } + v = valueInterfaceValue(v) + if vt := v.Type(); vt.Kind() == reflect.Struct && vt.Field(0).Name == "IValue" { + // Value is retrieved from an interface wrapper. + v = v.Field(0).Elem() + } + ok = canAssertTypes(v.Type(), rtype) + if !ok { + if !withOk { + method := firstMissingMethod(v.Type(), rtype) + panic(n.cfgErrorf("interface conversion: %s is not %s: missing method %s", v.Type().String(), rtype.String(), method)) + } + return next + } + if withResult { + value0(f).Set(v) + } + return next + } + default: + n.exec = func(f *frame) bltn { + v, ok := value(f).Interface().(valueInterface) + if setStatus { + defer func() { + value1(f).SetBool(ok) + }() + } + if !ok || !v.value.IsValid() { + ok = false + if !withOk { + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) + } + return next + } + + ok = canAssertTypes(v.value.Type(), rtype) + if !ok { + if !withOk { + panic(n.cfgErrorf("interface conversion: interface {} is %s, not %s", v.value.Type().String(), rtype.String())) + } + return next + } + if withResult { + value0(f).Set(v.value) + } + return next + } + } +} + +func canAssertTypes(src, dest reflect.Type) bool { + if dest == nil { + return false + } + if src == dest { + return true + } + if dest.Kind() == reflect.Interface && src.Implements(dest) { + return true + } + if src == nil { + return false + } + if src.AssignableTo(dest) { + return true + } + return false +} + +func firstMissingMethod(src, dest reflect.Type) string { + for i := 0; i < dest.NumMethod(); i++ { + m := dest.Method(i).Name + if _, ok := src.MethodByName(m); !ok { + return m + } + } + return "" +} + +func convert(n *node) { + dest := genValue(n) + c := n.child[1] + typ := n.child[0].typ.frameType() + next := getExec(n.tnext) + + if c.isNil() { // convert nil to type + // TODO(mpl): Try to completely remove, as maybe frameType already does the job for interfaces. + if isInterfaceSrc(n.child[0].typ) && !isEmptyInterface(n.child[0].typ) { + typ = valueInterfaceType + } + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.New(typ).Elem()) + return next + } + return + } + + doConvert := true + var value func(*frame) reflect.Value + switch { + case isFuncSrc(c.typ): + value = genFunctionWrapper(c) + default: + value = genValue(c) + } + + for _, con := range n.interp.hooks.convert { + if c.typ.rtype == nil { + continue + } + + fn := con(c.typ.rtype, typ) + if fn == nil { + continue + } + n.exec = func(f *frame) bltn { + fn(value(f), dest(f)) + return next + } + return + } + + n.exec = func(f *frame) bltn { + if doConvert { + dest(f).Set(value(f).Convert(typ)) + } else { + dest(f).Set(value(f)) + } + return next + } +} + +// assignFromCall assigns values from a function call. +func assignFromCall(n *node) { + ncall := n.lastChild() + l := len(n.child) - 1 + if n.anc.kind == varDecl && n.child[l-1].isType(n.scope) { + // Ignore the type in the assignment if it is part of a variable declaration. + l-- + } + dvalue := make([]func(*frame) reflect.Value, l) + for i := range dvalue { + if n.child[i].ident == "_" { + continue + } + dvalue[i] = genValue(n.child[i]) + } + next := getExec(n.tnext) + n.exec = func(f *frame) bltn { + for i, v := range dvalue { + if v == nil { + continue + } + s := f.data[ncall.findex+i] + c := n.child[i] + if n.kind == defineXStmt && !c.redeclared { + // Recreate destination value in case of define statement, + // to preserve previous value possibly in use by a closure. + data := getFrame(f, c.level).data + data[c.findex] = reflect.New(data[c.findex].Type()).Elem() + data[c.findex].Set(s) + continue + } + v(f).Set(s) + } + return next + } +} + +func assign(n *node) { + next := getExec(n.tnext) + dvalue := make([]func(*frame) reflect.Value, n.nleft) + ivalue := make([]func(*frame) reflect.Value, n.nleft) + svalue := make([]func(*frame) reflect.Value, n.nleft) + var sbase int + if n.nright > 0 { + sbase = len(n.child) - n.nright + } + + for i := 0; i < n.nleft; i++ { + dest, src := n.child[i], n.child[sbase+i] + if isNamedFuncSrc(src.typ) { + svalue[i] = genFuncValue(src) + } else { + svalue[i] = genDestValue(dest.typ, src) + } + if isMapEntry(dest) { + if isInterfaceSrc(dest.child[1].typ) { // key + ivalue[i] = genValueInterface(dest.child[1]) + } else { + ivalue[i] = genValue(dest.child[1]) + } + dvalue[i] = genValue(dest.child[0]) + } else { + dvalue[i] = genValue(dest) + } + } + + if n.nleft == 1 { + // Single assign operation. + switch s, d, i := svalue[0], dvalue[0], ivalue[0]; { + case n.child[0].ident == "_": + n.exec = func(f *frame) bltn { + return next + } + case i != nil: + n.exec = func(f *frame) bltn { + d(f).SetMapIndex(i(f), s(f)) + return next + } + case n.kind == defineStmt: + l := n.level + ind := n.findex + n.exec = func(f *frame) bltn { + data := getFrame(f, l).data + data[ind] = reflect.New(data[ind].Type()).Elem() + data[ind].Set(s(f)) + return next + } + default: + n.exec = func(f *frame) bltn { + d(f).Set(s(f)) + return next + } + } + return + } + + // Multi assign operation. + types := make([]reflect.Type, n.nright) + index := make([]int, n.nright) + level := make([]int, n.nright) + + for i := range types { + var t reflect.Type + switch typ := n.child[sbase+i].typ; { + case isInterfaceSrc(typ): + t = valueInterfaceType + default: + t = typ.TypeOf() + } + types[i] = t + index[i] = n.child[i].findex + level[i] = n.child[i].level + } + + if n.kind == defineStmt { + // Handle a multiple var declararation / assign. It cannot be a swap. + n.exec = func(f *frame) bltn { + for i, s := range svalue { + if n.child[i].ident == "_" { + continue + } + data := getFrame(f, level[i]).data + j := index[i] + data[j] = reflect.New(data[j].Type()).Elem() + data[j].Set(s(f)) + } + return next + } + return + } + + // To handle possible swap in multi-assign: + // evaluate and copy all values in assign right hand side into temporary + // then evaluate assign left hand side and copy temporary into it + n.exec = func(f *frame) bltn { + t := make([]reflect.Value, len(svalue)) + for i, s := range svalue { + if n.child[i].ident == "_" { + continue + } + t[i] = reflect.New(types[i]).Elem() + t[i].Set(s(f)) + } + for i, d := range dvalue { + if n.child[i].ident == "_" { + continue + } + if j := ivalue[i]; j != nil { + d(f).SetMapIndex(j(f), t[i]) // Assign a map entry + } else { + d(f).Set(t[i]) // Assign a var or array/slice entry + } + } + return next + } +} + +func not(n *node) { + dest := genValue(n) + value := genValue(n.child[0]) + tnext := getExec(n.tnext) + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if !value(f).Bool() { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + n.exec = func(f *frame) bltn { + dest(f).SetBool(!value(f).Bool()) + return tnext + } + } +} + +func addr(n *node) { + dest := genValue(n) + next := getExec(n.tnext) + c0 := n.child[0] + value := genValue(c0) + + if isInterfaceSrc(c0.typ) || isPtrSrc(c0.typ) { + i := n.findex + l := n.level + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Addr() + return next + } + return + } + + n.exec = func(f *frame) bltn { + dest(f).Set(value(f).Addr()) + return next + } +} + +func deref(n *node) { + value := genValue(n.child[0]) + tnext := getExec(n.tnext) + i := n.findex + l := n.level + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + r := value(f).Elem() + if r.Bool() { + getFrame(f, l).data[i] = r + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Elem() + return tnext + } + } +} + +func _print(n *node) { + child := n.child[1:] + values := make([]func(*frame) reflect.Value, len(child)) + for i, c := range child { + values[i] = genValue(c) + } + out := n.interp.stdout + + genBuiltinDeferWrapper(n, values, nil, func(args []reflect.Value) []reflect.Value { + for i, value := range args { + if i > 0 { + fmt.Fprintf(out, " ") + } + fmt.Fprintf(out, "%v", value) + } + return nil + }) +} + +func _println(n *node) { + child := n.child[1:] + values := make([]func(*frame) reflect.Value, len(child)) + for i, c := range child { + values[i] = genValue(c) + } + out := n.interp.stdout + + genBuiltinDeferWrapper(n, values, nil, func(args []reflect.Value) []reflect.Value { + for i, value := range args { + if i > 0 { + fmt.Fprintf(out, " ") + } + fmt.Fprintf(out, "%v", value) + } + fmt.Fprintln(out, "") + return nil + }) +} + +func _recover(n *node) { + tnext := getExec(n.tnext) + dest := genValue(n) + + n.exec = func(f *frame) bltn { + if f.anc.recovered == nil { + // TODO(mpl): maybe we don't need that special case, and we're just forgetting to unwrap the valueInterface somewhere else. + if isEmptyInterface(n.typ) { + return tnext + } + dest(f).Set(reflect.ValueOf(valueInterface{})) + return tnext + } + + if isEmptyInterface(n.typ) { + dest(f).Set(reflect.ValueOf(f.anc.recovered)) + } else { + dest(f).Set(reflect.ValueOf(valueInterface{n, reflect.ValueOf(f.anc.recovered)})) + } + f.anc.recovered = nil + return tnext + } +} + +func _panic(n *node) { + value := genValue(n.child[1]) + + n.exec = func(f *frame) bltn { + panic(value(f)) + } +} + +func genBuiltinDeferWrapper(n *node, in, out []func(*frame) reflect.Value, fn func([]reflect.Value) []reflect.Value) { + next := getExec(n.tnext) + + if n.anc.kind == deferStmt { + n.exec = func(f *frame) bltn { + val := make([]reflect.Value, len(in)+1) + inTypes := make([]reflect.Type, len(in)) + for i, v := range in { + val[i+1] = v(f) + inTypes[i] = val[i+1].Type() + } + outTypes := make([]reflect.Type, len(out)) + for i, v := range out { + outTypes[i] = v(f).Type() + } + + funcType := reflect.FuncOf(inTypes, outTypes, false) + val[0] = reflect.MakeFunc(funcType, fn) + f.deferred = append([][]reflect.Value{val}, f.deferred...) + return next + } + return + } + + n.exec = func(f *frame) bltn { + val := make([]reflect.Value, len(in)) + for i, v := range in { + val[i] = v(f) + } + + dests := fn(val) + + for i, dest := range dests { + out[i](f).Set(dest) + } + return next + } +} + +func genFunctionWrapper(n *node) func(*frame) reflect.Value { + var def *node + var ok bool + + if def, ok = n.val.(*node); !ok { + return genValueAsFunctionWrapper(n) + } + start := def.child[3].start + numRet := len(def.typ.ret) + var rcvr func(*frame) reflect.Value + + if n.recv != nil { + rcvr = genValueRecv(n) + } + funcType := n.typ.TypeOf() + + return func(f *frame) reflect.Value { + return reflect.MakeFunc(funcType, func(in []reflect.Value) []reflect.Value { + // Allocate and init local frame. All values to be settable and addressable. + fr := newFrame(f, len(def.types), f.runid()) + d := fr.data + for i, t := range def.types { + d[i] = reflect.New(t).Elem() + } + + if rcvr == nil { + d = d[numRet:] + } else { + // Copy method receiver as first argument. + src, dest := rcvr(f), d[numRet] + sk, dk := src.Kind(), dest.Kind() + for { + vs, ok := src.Interface().(valueInterface) + if !ok { + break + } + src = vs.value + sk = src.Kind() + } + switch { + case sk == reflect.Ptr && dk != reflect.Ptr: + dest.Set(src.Elem()) + case sk != reflect.Ptr && dk == reflect.Ptr: + dest.Set(src.Addr()) + default: + dest.Set(src) + } + d = d[numRet+1:] + } + + // Copy function input arguments in local frame. + for i, arg := range in { + if i >= len(d) { + // In case of unused arg, there may be not even a frame entry allocated, just skip. + break + } + typ := def.typ.arg[i] + switch { + case isEmptyInterface(typ) || typ.TypeOf() == valueInterfaceType: + d[i].Set(arg) + case isInterfaceSrc(typ): + d[i].Set(reflect.ValueOf(valueInterface{value: arg.Elem()})) + default: + d[i].Set(arg) + } + } + + // Interpreter code execution. + runCfg(start, fr, def, n) + + return fr.data[:numRet] + }) + } +} + +func genInterfaceWrapper(n *node, typ reflect.Type) func(*frame) reflect.Value { + value := genValue(n) + if typ == nil || typ.Kind() != reflect.Interface || typ.NumMethod() == 0 || n.typ.cat == valueT { + return value + } + tc := n.typ.cat + if tc != structT { + // Always force wrapper generation for struct types, as they may contain + // embedded interface fields which require wrapping, even if reported as + // implementing typ by reflect. + if nt := n.typ.frameType(); nt != nil && nt.Implements(typ) { + return value + } + } + + // Retrieve methods from the interface wrapper, which is a struct where all fields + // except the first define the methods to implement. + // As the field name was generated with a prefixed first character (in order to avoid + // collisions with method names), this first character is ignored in comparisons. + wrap := getWrapper(n, typ) + mn := wrap.NumField() - 1 + names := make([]string, mn) + methods := make([]*node, mn) + indexes := make([][]int, mn) + for i := 0; i < mn; i++ { + names[i] = wrap.Field(i + 1).Name[1:] + methods[i], indexes[i] = n.typ.lookupMethod(names[i]) + if methods[i] == nil && n.typ.cat != nilT { + // interpreted method not found, look for binary method, possibly embedded + _, indexes[i], _, _ = n.typ.lookupBinMethod(names[i]) + } + } + + return func(f *frame) reflect.Value { + v := value(f) + if tc != structT && v.Type().Implements(typ) { + return v + } + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + if v.IsNil() { + return reflect.New(typ).Elem() + } + } + var n2 *node + if vi, ok := v.Interface().(valueInterface); ok { + n2 = vi.node + } + v = getConcreteValue(v) + w := reflect.New(wrap).Elem() + w.Field(0).Set(v) + for i, m := range methods { + if m == nil { + // First direct method lookup on field. + if r := methodByName(v, names[i], indexes[i]); r.IsValid() { + w.Field(i + 1).Set(r) + continue + } + if n2 == nil { + panic(n.cfgErrorf("method not found: %s", names[i])) + } + // Method lookup in embedded valueInterface. + m2, i2 := n2.typ.lookupMethod(names[i]) + if m2 != nil { + nod := *m2 + nod.recv = &receiver{n, v, i2} + w.Field(i + 1).Set(genFunctionWrapper(&nod)(f)) + continue + } + panic(n.cfgErrorf("method not found: %s", names[i])) + } + nod := *m + nod.recv = &receiver{n, v, indexes[i]} + w.Field(i + 1).Set(genFunctionWrapper(&nod)(f)) + } + return w + } +} + +// methodByName returns the method corresponding to name on value, or nil if not found. +// The search is extended on valueInterface wrapper if present. +// If valid, the returned value is a method function with the receiver already set +// (no need to pass it at call). +func methodByName(value reflect.Value, name string, index []int) (v reflect.Value) { + if vi, ok := value.Interface().(valueInterface); ok { + if v = getConcreteValue(vi.value).MethodByName(name); v.IsValid() { + return + } + } + if v = value.MethodByName(name); v.IsValid() { + return + } + for value.Kind() == reflect.Ptr { + value = value.Elem() + if checkFieldIndex(value.Type(), index) { + value = value.FieldByIndex(index) + } + if v = value.MethodByName(name); v.IsValid() { + return + } + } + return +} + +func checkFieldIndex(typ reflect.Type, index []int) bool { + if len(index) == 0 { + return false + } + t := typ + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Struct { + return false + } + i := index[0] + if i >= t.NumField() { + return false + } + if len(index) > 1 { + return checkFieldIndex(t.Field(i).Type, index[1:]) + } + return true +} + +func call(n *node) { + goroutine := n.anc.kind == goStmt + c0 := n.child[0] + value := genValue(c0) + var values []func(*frame) reflect.Value + + numRet := len(c0.typ.ret) + variadic := variadicPos(n) + child := n.child[1:] + tnext := getExec(n.tnext) + fnext := getExec(n.fnext) + hasVariadicArgs := n.action == aCallSlice // callSlice implies variadic call with ellipsis. + + // Compute input argument value functions. + for i, c := range child { + var arg *itype + if variadic >= 0 && i >= variadic { + arg = c0.typ.arg[variadic].val + } else { + arg = c0.typ.arg[i] + } + switch { + case isBinCall(c, c.scope): + // Handle nested function calls: pass returned values as arguments. + numOut := c.child[0].typ.rtype.NumOut() + for j := 0; j < numOut; j++ { + ind := c.findex + j + if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) { + values = append(values, func(f *frame) reflect.Value { return f.data[ind] }) + continue + } + values = append(values, func(f *frame) reflect.Value { + return reflect.ValueOf(valueInterface{value: f.data[ind]}) + }) + } + case isRegularCall(c): + // Arguments are return values of a nested function call. + cc0 := c.child[0] + for j := range cc0.typ.ret { + ind := c.findex + j + if hasVariadicArgs || !isInterfaceSrc(arg) || isEmptyInterface(arg) { + values = append(values, func(f *frame) reflect.Value { return f.data[ind] }) + continue + } + values = append(values, func(f *frame) reflect.Value { + return reflect.ValueOf(valueInterface{node: cc0.typ.ret[j].node, value: f.data[ind]}) + }) + } + default: + if c.kind == basicLit || c.rval.IsValid() { + argType := arg.TypeOf() + convertLiteralValue(c, argType) + } + switch { + case hasVariadicArgs: + values = append(values, genValue(c)) + case isInterfaceSrc(arg) && (!isEmptyInterface(arg) || len(c.typ.method) > 0): + values = append(values, genValueInterface(c)) + case isInterfaceBin(arg): + values = append(values, genInterfaceWrapper(c, arg.rtype)) + case isFuncSrc(arg): + values = append(values, genFuncValue(c)) + default: + values = append(values, genValue(c)) + } + } + } + + // Compute output argument value functions. + rtypes := c0.typ.ret + rvalues := make([]func(*frame) reflect.Value, len(rtypes)) + switch n.anc.kind { + case defineXStmt, assignXStmt: + l := n.level + for i := range rvalues { + c := n.anc.child[i] + switch { + case c.ident == "_": + // Skip assigning return value to blank var. + case isInterfaceSrc(c.typ) && !isEmptyInterface(c.typ) && !isInterfaceSrc(rtypes[i]): + rvalues[i] = genValueInterfaceValue(c) + default: + j := n.findex + i + rvalues[i] = func(f *frame) reflect.Value { return getFrame(f, l).data[j] } + } + } + case returnStmt: + // Function call from a return statement: forward return values (always at frame start). + for i := range rtypes { + j := n.findex + i + // Set the return value location in return value of caller frame. + rvalues[i] = func(f *frame) reflect.Value { return f.data[j] } + } + default: + // Multiple return values frame index are indexed from the node frame index. + l := n.level + for i := range rtypes { + j := n.findex + i + rvalues[i] = func(f *frame) reflect.Value { return getFrame(f, l).data[j] } + } + } + + if n.anc.kind == deferStmt { + // Store function call in frame for deferred execution. + value = genFunctionWrapper(c0) + n.exec = func(f *frame) bltn { + val := make([]reflect.Value, len(values)+1) + val[0] = value(f) + for i, v := range values { + val[i+1] = v(f) + } + f.deferred = append([][]reflect.Value{val}, f.deferred...) + return tnext + } + return + } + + n.exec = func(f *frame) bltn { + f.mutex.Lock() + bf := value(f) + def, ok := bf.Interface().(*node) + if ok { + bf = def.rval + } + f.mutex.Unlock() + + // Call bin func if defined + if bf.IsValid() { + var callf func([]reflect.Value) []reflect.Value + + // Lambda definitions are necessary here. Due to reflect internals, + // having `callf = bf.Call` or `callf = bf.CallSlice` does not work. + //nolint:gocritic + if hasVariadicArgs { + callf = func(in []reflect.Value) []reflect.Value { return bf.CallSlice(in) } + } else { + callf = func(in []reflect.Value) []reflect.Value { return bf.Call(in) } + } + + if goroutine { + // Goroutine's arguments should be copied. + in := make([]reflect.Value, len(values)) + for i, v := range values { + value := v(f) + in[i] = reflect.New(value.Type()).Elem() + in[i].Set(value) + } + + go callf(in) + return tnext + } + + in := make([]reflect.Value, len(values)) + for i, v := range values { + in[i] = v(f) + } + out := callf(in) + for i, v := range rvalues { + if v != nil { + v(f).Set(out[i]) + } + } + if fnext != nil && !out[0].Bool() { + return fnext + } + return tnext + } + + nf := newFrame(f, len(def.types), f.runid()) + var vararg reflect.Value + + // Init return values + for i, v := range rvalues { + if v != nil { + nf.data[i] = v(f) + } else { + nf.data[i] = reflect.New(def.types[i]).Elem() + } + } + + // Init local frame values + for i, t := range def.types[numRet:] { + nf.data[numRet+i] = reflect.New(t).Elem() + } + + // Init variadic argument vector + if variadic >= 0 { + vararg = nf.data[numRet+variadic] + } + + // Copy input parameters from caller + if dest := nf.data[numRet:]; len(dest) > 0 { + for i, v := range values { + switch { + case variadic >= 0 && i >= variadic: + if v(f).Type() == vararg.Type() { + vararg.Set(v(f)) + } else { + vararg.Set(reflect.Append(vararg, v(f))) + } + default: + val := v(f) + if val.IsZero() && dest[i].Kind() != reflect.Interface { + // Work around a recursive struct zero interface issue. + // Once there is a better way to handle this case, the dest can just be set. + continue + } + if nod, ok := val.Interface().(*node); ok && nod.recv != nil { + // An interpreted method is passed as value in a function call. + // It must be wrapped now, otherwise the receiver will be missing + // at the method call (#1332). + // TODO (marc): wrapping interpreted functions should be always done + // everywhere at runtime to simplify the whole code, + // but it requires deeper refactoring. + dest[i] = genFunctionWrapper(nod)(f) + continue + } + dest[i].Set(val) + } + } + } + + // Execute function body + if goroutine { + go runCfg(def.child[3].start, nf, def, n) + return tnext + } + runCfg(def.child[3].start, nf, def, n) + + // Handle branching according to boolean result + if fnext != nil && !nf.data[0].Bool() { + return fnext + } + return tnext + } +} + +func getFrame(f *frame, l int) *frame { + switch l { + case globalFrame: + return f.root + case 0: + return f + case 1: + return f.anc + case 2: + return f.anc.anc + } + for ; l > 0; l-- { + f = f.anc + } + return f +} + +// Callbin calls a function from a bin import, accessible through reflect. +func callBin(n *node) { + tnext := getExec(n.tnext) + fnext := getExec(n.fnext) + child := n.child[1:] + c0 := n.child[0] + value := genValue(c0) + var values []func(*frame) reflect.Value + funcType := c0.typ.rtype + wt := wrappedType(c0) + variadic := -1 + if funcType.IsVariadic() { + variadic = funcType.NumIn() - 1 + } + // A method signature obtained from reflect.Type includes receiver as 1st arg, except for interface types. + rcvrOffset := 0 + if recv := c0.recv; recv != nil && !isInterface(recv.node.typ) { + if variadic > 0 || funcType.NumIn() > len(child) { + rcvrOffset = 1 + } + } + + // getMapType returns a reflect type suitable for interface wrapper for functions + // with some special processing in case of interface{} argument, i.e. fmt.Printf. + var getMapType func(*itype) reflect.Type + if lr, ok := n.interp.mapTypes[c0.rval]; ok { + getMapType = func(typ *itype) reflect.Type { + for _, rt := range lr { + if typ.implements(&itype{cat: valueT, rtype: rt}) { + return rt + } + } + return nil + } + } + + // Determine if we should use `Call` or `CallSlice` on the function Value. + callFn := func(v reflect.Value, in []reflect.Value) []reflect.Value { return v.Call(in) } + if n.action == aCallSlice { + callFn = func(v reflect.Value, in []reflect.Value) []reflect.Value { return v.CallSlice(in) } + } + + for i, c := range child { + switch { + case isBinCall(c, c.scope): + // Handle nested function calls: pass returned values as arguments + numOut := c.child[0].typ.rtype.NumOut() + for j := 0; j < numOut; j++ { + ind := c.findex + j + values = append(values, func(f *frame) reflect.Value { return valueInterfaceValue(f.data[ind]) }) + } + case isRegularCall(c): + // Handle nested function calls: pass returned values as arguments + for j := range c.child[0].typ.ret { + ind := c.findex + j + values = append(values, func(f *frame) reflect.Value { return valueInterfaceValue(f.data[ind]) }) + } + default: + if c.kind == basicLit || c.rval.IsValid() { + // Convert literal value (untyped) to function argument type (if not an interface{}) + var argType reflect.Type + if variadic >= 0 && i+rcvrOffset >= variadic { + argType = funcType.In(variadic).Elem() + } else { + argType = funcType.In(i + rcvrOffset) + } + convertLiteralValue(c, argType) + if !reflect.ValueOf(c.val).IsValid() { // Handle "nil" + c.val = reflect.Zero(argType) + } + } + + if wt != nil && isInterfaceSrc(wt.arg[i]) { + values = append(values, genValueInterface(c)) + break + } + + // defType is the target type for a potential interface wrapper. + var defType reflect.Type + if variadic >= 0 && i+rcvrOffset >= variadic { + defType = funcType.In(variadic) + } else { + defType = funcType.In(rcvrOffset + i) + } + if getMapType != nil { + if rt := getMapType(c.typ); rt != nil { + defType = rt + } + } + + switch { + case isEmptyInterface(c.typ): + values = append(values, genValue(c)) + case isInterfaceSrc(c.typ): + values = append(values, genValueInterfaceValue(c)) + case isFuncSrc(c.typ): + values = append(values, genFunctionWrapper(c)) + case c.typ.cat == arrayT || c.typ.cat == variadicT: + if isEmptyInterface(c.typ.val) { + values = append(values, genValueArray(c)) + } else { + values = append(values, genInterfaceWrapper(c, defType)) + } + case isPtrSrc(c.typ): + if c.typ.val.cat == valueT { + values = append(values, genValue(c)) + } else { + values = append(values, genInterfaceWrapper(c, defType)) + } + case c.typ.cat == valueT: + values = append(values, genValue(c)) + default: + values = append(values, genInterfaceWrapper(c, defType)) + } + } + } + l := len(values) + + switch { + case n.anc.kind == deferStmt: + // Store function call in frame for deferred execution. + n.exec = func(f *frame) bltn { + val := make([]reflect.Value, l+1) + val[0] = value(f) + for i, v := range values { + val[i+1] = getBinValue(getMapType, v, f) + } + f.deferred = append([][]reflect.Value{val}, f.deferred...) + return tnext + } + case n.anc.kind == goStmt: + // Execute function in a goroutine, discard results. + n.exec = func(f *frame) bltn { + in := make([]reflect.Value, l) + for i, v := range values { + in[i] = getBinValue(getMapType, v, f) + } + go callFn(value(f), in) + return tnext + } + case fnext != nil: + // Handle branching according to boolean result. + index := n.findex + level := n.level + n.exec = func(f *frame) bltn { + in := make([]reflect.Value, l) + for i, v := range values { + in[i] = getBinValue(getMapType, v, f) + } + res := callFn(value(f), in) + b := res[0].Bool() + getFrame(f, level).data[index].SetBool(b) + if b { + return tnext + } + return fnext + } + default: + switch n.anc.action { + case aAssignX: + // The function call is part of an assign expression, store results direcly + // to assigned location, to avoid an additional frame copy. + // The optimization of aAssign is handled in assign(), and should not + // be handled here. + rvalues := make([]func(*frame) reflect.Value, funcType.NumOut()) + for i := range rvalues { + c := n.anc.child[i] + if c.ident == "_" { + continue + } + if isInterfaceSrc(c.typ) { + rvalues[i] = genValueInterfaceValue(c) + } else { + rvalues[i] = genValue(c) + } + } + n.exec = func(f *frame) bltn { + in := make([]reflect.Value, l) + for i, v := range values { + in[i] = getBinValue(getMapType, v, f) + } + out := callFn(value(f), in) + for i, v := range rvalues { + if v == nil { + continue // Skip assign "_". + } + c := n.anc.child[i] + if n.anc.kind == defineXStmt && !c.redeclared { + // In case of a define statement, the destination value in the frame + // must be recreated. This is necessary to preserve the previous value + // which may be still used in a separate closure. + data := getFrame(f, c.level).data + data[c.findex] = reflect.New(data[c.findex].Type()).Elem() + data[c.findex].Set(out[i]) + continue + } + v(f).Set(out[i]) + } + return tnext + } + case aReturn: + // The function call is part of a return statement, store output results + // directly in the frame location of outputs of the current function. + b := childPos(n) + n.exec = func(f *frame) bltn { + in := make([]reflect.Value, l) + for i, v := range values { + in[i] = getBinValue(getMapType, v, f) + } + out := callFn(value(f), in) + for i, v := range out { + dest := f.data[b+i] + if _, ok := dest.Interface().(valueInterface); ok { + v = reflect.ValueOf(valueInterface{value: v}) + } + dest.Set(v) + } + return tnext + } + default: + n.exec = func(f *frame) bltn { + in := make([]reflect.Value, l) + for i, v := range values { + in[i] = getBinValue(getMapType, v, f) + } + out := callFn(value(f), in) + for i := 0; i < len(out); i++ { + r := out[i] + if r.Kind() == reflect.Func { + getFrame(f, n.level).data[n.findex+i] = r + continue + } + dest := getFrame(f, n.level).data[n.findex+i] + if _, ok := dest.Interface().(valueInterface); ok { + r = reflect.ValueOf(valueInterface{value: r}) + } + dest.Set(r) + } + return tnext + } + } + } +} + +func getIndexBinMethod(n *node) { + // dest := genValue(n) + i := n.findex + l := n.level + m := n.val.(int) + value := genValue(n.child[0]) + next := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + // Can not use .Set() because dest type contains the receiver and source not + // dest(f).Set(value(f).Method(m)) + getFrame(f, l).data[i] = value(f).Method(m) + return next + } +} + +func getIndexBinElemMethod(n *node) { + i := n.findex + l := n.level + m := n.val.(int) + value := genValue(n.child[0]) + next := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + // Can not use .Set() because dest type contains the receiver and source not + getFrame(f, l).data[i] = value(f).Elem().Method(m) + return next + } +} + +func getIndexBinPtrMethod(n *node) { + i := n.findex + l := n.level + m := n.val.(int) + value := genValue(n.child[0]) + next := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + // Can not use .Set() because dest type contains the receiver and source not + getFrame(f, l).data[i] = value(f).Addr().Method(m) + return next + } +} + +// getIndexArray returns array value from index. +func getIndexArray(n *node) { + tnext := getExec(n.tnext) + value0 := genValueArray(n.child[0]) // array + i := n.findex + l := n.level + + if n.child[1].rval.IsValid() { // constant array index + ai := int(vInt(n.child[1].rval)) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + r := value0(f).Index(ai) + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value0(f).Index(ai) + return tnext + } + } + } else { + value1 := genValueInt(n.child[1]) // array index + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + _, vi := value1(f) + r := value0(f).Index(int(vi)) + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + _, vi := value1(f) + getFrame(f, l).data[i] = value0(f).Index(int(vi)) + return tnext + } + } + } +} + +// getIndexMap retrieves map value from index. +func getIndexMap(n *node) { + dest := genValue(n) + value0 := genValue(n.child[0]) // map + tnext := getExec(n.tnext) + z := reflect.New(n.child[0].typ.frameType().Elem()).Elem() + + if n.child[1].rval.IsValid() { // constant map index + mi := n.child[1].rval + + switch { + case n.fnext != nil: + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if v := value0(f).MapIndex(mi); v.IsValid() && v.Bool() { + dest(f).SetBool(true) + return tnext + } + dest(f).Set(z) + return fnext + } + default: + n.exec = func(f *frame) bltn { + if v := value0(f).MapIndex(mi); v.IsValid() { + dest(f).Set(v) + } else { + dest(f).Set(z) + } + return tnext + } + } + } else { + value1 := genValue(n.child[1]) // map index + + switch { + case n.fnext != nil: + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if v := value0(f).MapIndex(value1(f)); v.IsValid() && v.Bool() { + dest(f).SetBool(true) + return tnext + } + dest(f).Set(z) + return fnext + } + default: + n.exec = func(f *frame) bltn { + if v := value0(f).MapIndex(value1(f)); v.IsValid() { + dest(f).Set(v) + } else { + dest(f).Set(z) + } + return tnext + } + } + } +} + +// getIndexMap2 retrieves map value from index and set status. +func getIndexMap2(n *node) { + dest := genValue(n.anc.child[0]) // result + value0 := genValue(n.child[0]) // map + value2 := genValue(n.anc.child[1]) // status + next := getExec(n.tnext) + doValue := n.anc.child[0].ident != "_" + doStatus := n.anc.child[1].ident != "_" + + if !doValue && !doStatus { + nop(n) + return + } + if n.child[1].rval.IsValid() { // constant map index + mi := n.child[1].rval + switch { + case !doValue: + n.exec = func(f *frame) bltn { + v := value0(f).MapIndex(mi) + value2(f).SetBool(v.IsValid()) + return next + } + default: + n.exec = func(f *frame) bltn { + v := value0(f).MapIndex(mi) + if v.IsValid() { + dest(f).Set(v) + } + if doStatus { + value2(f).SetBool(v.IsValid()) + } + return next + } + } + } else { + value1 := genValue(n.child[1]) // map index + switch { + case !doValue: + n.exec = func(f *frame) bltn { + v := value0(f).MapIndex(value1(f)) + value2(f).SetBool(v.IsValid()) + return next + } + default: + n.exec = func(f *frame) bltn { + v := value0(f).MapIndex(value1(f)) + if v.IsValid() { + dest(f).Set(v) + } + if doStatus { + value2(f).SetBool(v.IsValid()) + } + return next + } + } + } +} + +// getFunc compiles a closure function generator for anonymous functions. +func getFunc(n *node) { + i := n.findex + l := n.level + next := getExec(n.tnext) + numRet := len(n.typ.ret) + + n.exec = func(f *frame) bltn { + fr := f.clone() + o := getFrame(f, l).data[i] + + fct := reflect.MakeFunc(n.typ.TypeOf(), func(in []reflect.Value) []reflect.Value { + // Allocate and init local frame. All values to be settable and addressable. + fr2 := newFrame(fr, len(n.types), fr.runid()) + d := fr2.data + for i, t := range n.types { + d[i] = reflect.New(t).Elem() + } + d = d[numRet:] + + // Copy function input arguments in local frame. + for i, arg := range in { + if i >= len(d) { + // In case of unused arg, there may be not even a frame entry allocated, just skip. + break + } + typ := n.typ.arg[i] + switch { + case isEmptyInterface(typ) || typ.TypeOf() == valueInterfaceType: + d[i].Set(arg) + case isInterfaceSrc(typ): + d[i].Set(reflect.ValueOf(valueInterface{value: arg.Elem()})) + default: + d[i].Set(arg) + } + } + + // Interpreter code execution. + runCfg(n.child[3].start, fr2, n, n) + + f.mutex.Lock() + getFrame(f, l).data[i] = o + f.mutex.Unlock() + + return fr2.data[:numRet] + }) + + f.mutex.Lock() + getFrame(f, l).data[i] = fct + f.mutex.Unlock() + + return next + } +} + +func getMethod(n *node) { + i := n.findex + l := n.level + next := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + nod := *(n.val.(*node)) + nod.val = &nod + nod.recv = n.recv + getFrame(f, l).data[i] = genFuncValue(&nod)(f) + return next + } +} + +func getMethodByName(n *node) { + next := getExec(n.tnext) + value0 := genValue(n.child[0]) + name := n.child[1].ident + i := n.findex + l := n.level + + n.exec = func(f *frame) bltn { + // The interface object must be directly accessible, or embedded in a struct (exported anonymous field). + val0 := value0(f) + val, ok := value0(f).Interface().(valueInterface) + if !ok { + // Search the first embedded valueInterface. + for val0.Kind() == reflect.Ptr { + val0 = val0.Elem() + } + for i := 0; i < val0.NumField(); i++ { + fld := val0.Type().Field(i) + if !fld.Anonymous || !fld.IsExported() { + continue + } + if val, ok = val0.Field(i).Interface().(valueInterface); ok { + break + // TODO: should we keep track of all the vals that are indeed valueInterface, + // so that later on we can call MethodByName on all of them until one matches? + } + } + if !ok { + panic(n.cfgErrorf("invalid interface value %v", val0)) + } + } + // Traverse nested interface values to get the concrete value. + for { + v, ok := val.value.Interface().(valueInterface) + if !ok { + break + } + val = v + } + + if met := val.value.MethodByName(name); met.IsValid() { + getFrame(f, l).data[i] = met + return next + } + + typ := val.node.typ + if typ.node == nil && typ.cat == valueT { + // It happens with a var of empty interface type, that has value of concrete type + // from runtime, being asserted to "user-defined" interface. + if _, ok := typ.rtype.MethodByName(name); !ok { + panic(n.cfgErrorf("method not found: %s", name)) + } + return next + } + + // Finally search method recursively in embedded valueInterfaces. + r, m, li := lookupMethodValue(val, name) + if r.IsValid() { + getFrame(f, l).data[i] = r + return next + } + if m == nil { + panic(n.cfgErrorf("method not found: %s", name)) + } + + nod := *m + nod.val = &nod + nod.recv = &receiver{nil, val.value, li} + getFrame(f, l).data[i] = genFuncValue(&nod)(f) + return next + } +} + +// lookupMethodValue recursively looks within val for the method with the given +// name. If a runtime value is found, it is returned in r, otherwise it is returned +// in m, with li as the list of recursive field indexes. +func lookupMethodValue(val valueInterface, name string) (r reflect.Value, m *node, li []int) { + if r = val.value.MethodByName(name); r.IsValid() { + return + } + if m, li = val.node.typ.lookupMethod(name); m != nil { + return + } + if !isStruct(val.node.typ) { + return + } + v := val.value + for v.Type().Kind() == reflect.Ptr { + v = v.Elem() + } + nf := v.NumField() + for i := 0; i < nf; i++ { + vi, ok := v.Field(i).Interface().(valueInterface) + if !ok { + continue + } + if r, m, li = lookupMethodValue(vi, name); m != nil { + li = append([]int{i}, li...) + return + } + } + return +} + +func getIndexSeq(n *node) { + value := genValue(n.child[0]) + index := n.val.([]int) + tnext := getExec(n.tnext) + i := n.findex + l := n.level + + // Note: + // Here we have to store the result using + // f.data[i] = value(...) + // instead of normal + // dest(f).Set(value(...) + // because the value returned by FieldByIndex() must be preserved + // for possible future Set operations on the struct field (avoid a + // dereference from Set, resulting in setting a copy of the + // original field). + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + v := value(f) + r := v.FieldByIndex(index) + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + v := value(f) + getFrame(f, l).data[i] = v.FieldByIndex(index) + return tnext + } + } +} + +func getPtrIndexSeq(n *node) { + index := n.val.([]int) + tnext := getExec(n.tnext) + value := genValue(n.child[0]) + i := n.findex + l := n.level + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + r := value(f).Elem().FieldByIndex(index) + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(index) + return tnext + } + } +} + +func getIndexSeqField(n *node) { + value := genValue(n.child[0]) + index := n.val.([]int) + i := n.findex + l := n.level + tnext := getExec(n.tnext) + + if n.fnext != nil { + fnext := getExec(n.fnext) + if n.child[0].typ.TypeOf().Kind() == reflect.Ptr { + n.exec = func(f *frame) bltn { + r := value(f).Elem().FieldByIndex(index) + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + r := value(f).FieldByIndex(index) + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + } + } else { + if n.child[0].typ.TypeOf().Kind() == reflect.Ptr { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(index) + return tnext + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).FieldByIndex(index) + return tnext + } + } + } +} + +func getIndexSeqPtrMethod(n *node) { + value := genValue(n.child[0]) + index := n.val.([]int) + fi := index[1:] + mi := index[0] + i := n.findex + l := n.level + next := getExec(n.tnext) + + if n.child[0].typ.TypeOf().Kind() == reflect.Ptr { + if len(fi) == 0 { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Method(mi) + return next + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(fi).Addr().Method(mi) + return next + } + } + } else { + if len(fi) == 0 { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Addr().Method(mi) + return next + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).FieldByIndex(fi).Addr().Method(mi) + return next + } + } + } +} + +func getIndexSeqMethod(n *node) { + value := genValue(n.child[0]) + index := n.val.([]int) + fi := index[1:] + mi := index[0] + i := n.findex + l := n.level + next := getExec(n.tnext) + + if n.child[0].typ.TypeOf().Kind() == reflect.Ptr { + if len(fi) == 0 { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Elem().Method(mi) + return next + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Elem().FieldByIndex(fi).Method(mi) + return next + } + } + } else { + if len(fi) == 0 { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).Method(mi) + return next + } + } else { + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i] = value(f).FieldByIndex(fi).Method(mi) + return next + } + } + } +} + +func neg(n *node) { + dest := genValue(n) + value := genValue(n.child[0]) + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + + switch n.typ.TypeOf().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(-value(f).Int()).Convert(typ)) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetInt(-value(f).Int()) + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(-value(f).Uint()).Convert(typ)) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetUint(-value(f).Uint()) + return next + } + case reflect.Float32, reflect.Float64: + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(-value(f).Float()).Convert(typ)) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetFloat(-value(f).Float()) + return next + } + case reflect.Complex64, reflect.Complex128: + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(-value(f).Complex()).Convert(typ)) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetComplex(-value(f).Complex()) + return next + } + } +} + +func pos(n *node) { + dest := genValue(n) + value := genValue(n.child[0]) + next := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + dest(f).Set(value(f)) + return next + } +} + +func bitNot(n *node) { + dest := genValue(n) + value := genValue(n.child[0]) + next := getExec(n.tnext) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + + switch typ.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(^value(f).Int()).Convert(typ)) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetInt(^value(f).Int()) + return next + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(^value(f).Uint()).Convert(typ)) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetUint(^value(f).Uint()) + return next + } + } +} + +func land(n *node) { + value0 := genValue(n.child[0]) + value1 := genValue(n.child[1]) + tnext := getExec(n.tnext) + dest := genValue(n) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if value0(f).Bool() && value1(f).Bool() { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + return + } + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(value0(f).Bool() && value1(f).Bool()).Convert(typ)) + return tnext + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetBool(value0(f).Bool() && value1(f).Bool()) + return tnext + } +} + +func lor(n *node) { + value0 := genValue(n.child[0]) + value1 := genValue(n.child[1]) + tnext := getExec(n.tnext) + dest := genValue(n) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if value0(f).Bool() || value1(f).Bool() { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + return + } + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(value0(f).Bool() || value1(f).Bool()).Convert(typ)) + return tnext + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetBool(value0(f).Bool() || value1(f).Bool()) + return tnext + } +} + +func nop(n *node) { + next := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + return next + } +} + +func branch(n *node) { + tnext := getExec(n.tnext) + fnext := getExec(n.fnext) + value := genValue(n) + + n.exec = func(f *frame) bltn { + if value(f).Bool() { + return tnext + } + return fnext + } +} + +func _return(n *node) { + child := n.child + def := n.val.(*node) + values := make([]func(*frame) reflect.Value, len(child)) + for i, c := range child { + switch t := def.typ.ret[i]; t.cat { + case errorT: + values[i] = genInterfaceWrapper(c, t.TypeOf()) + case funcT: + values[i] = genValue(c) + case valueT: + switch t.rtype.Kind() { + case reflect.Interface: + values[i] = genInterfaceWrapper(c, t.TypeOf()) + continue + case reflect.Func: + values[i] = genFunctionWrapper(c) + continue + } + fallthrough + default: + switch { + case isInterfaceSrc(t): + if len(t.field) == 0 { + // empty interface case. + // we can't let genValueInterface deal with it, because we call on c, + // not on n, which means that the interfaceT knowledge is lost. + values[i] = genValue(c) + break + } + values[i] = genValueInterface(c) + case c.typ.untyped: + values[i] = genValueAs(c, t.TypeOf()) + default: + values[i] = genValue(c) + } + } + } + + switch len(child) { + case 0: + n.exec = nil + case 1: + switch { + case !child[0].rval.IsValid() && child[0].kind == binaryExpr: + // No additional runtime operation is necessary for constants (not in frame) or + // binary expressions (stored directly at the right location in frame). + n.exec = nil + case isCall(child[0]) && n.child[0].typ.id() == def.typ.ret[0].id(): + // Calls are optmized as long as no type conversion is involved. + n.exec = nil + default: + // Regular return: store the value to return at to start of the frame. + v := values[0] + n.exec = func(f *frame) bltn { + f.data[0].Set(v(f)) + return nil + } + } + case 2: + v0, v1 := values[0], values[1] + n.exec = func(f *frame) bltn { + f.data[0].Set(v0(f)) + f.data[1].Set(v1(f)) + return nil + } + default: + n.exec = func(f *frame) bltn { + for i, value := range values { + f.data[i].Set(value(f)) + } + return nil + } + } +} + +func arrayLit(n *node) { + value := valueGenerator(n, n.findex) + next := getExec(n.tnext) + child := n.child + if n.nleft == 1 { + child = n.child[1:] + } + + values := make([]func(*frame) reflect.Value, len(child)) + index := make([]int, len(child)) + var max, prev int + + ntyp := n.typ.resolveAlias() + for i, c := range child { + if c.kind == keyValueExpr { + values[i] = genDestValue(ntyp.val, c.child[1]) + index[i] = int(vInt(c.child[0].rval)) + } else { + values[i] = genDestValue(ntyp.val, c) + index[i] = prev + } + prev = index[i] + 1 + if prev > max { + max = prev + } + } + + typ := n.typ.frameType() + kind := typ.Kind() + n.exec = func(f *frame) bltn { + var a reflect.Value + if kind == reflect.Slice { + a = reflect.MakeSlice(typ, max, max) + } else { + a, _ = n.typ.zero() + } + for i, v := range values { + a.Index(index[i]).Set(v(f)) + } + value(f).Set(a) + return next + } +} + +func mapLit(n *node) { + value := valueGenerator(n, n.findex) + next := getExec(n.tnext) + child := n.child + if n.nleft == 1 { + child = n.child[1:] + } + typ := n.typ.frameType() + keys := make([]func(*frame) reflect.Value, len(child)) + values := make([]func(*frame) reflect.Value, len(child)) + for i, c := range child { + keys[i] = genDestValue(n.typ.key, c.child[0]) + values[i] = genDestValue(n.typ.val, c.child[1]) + } + + n.exec = func(f *frame) bltn { + m := reflect.MakeMap(typ) + for i, k := range keys { + m.SetMapIndex(k(f), values[i](f)) + } + value(f).Set(m) + return next + } +} + +func compositeBinMap(n *node) { + value := valueGenerator(n, n.findex) + next := getExec(n.tnext) + child := n.child + if n.nleft == 1 { + child = n.child[1:] + } + typ := n.typ.frameType() + keys := make([]func(*frame) reflect.Value, len(child)) + values := make([]func(*frame) reflect.Value, len(child)) + for i, c := range child { + convertLiteralValue(c.child[0], typ.Key()) + convertLiteralValue(c.child[1], typ.Elem()) + keys[i] = genValue(c.child[0]) + + if isFuncSrc(c.child[1].typ) { + values[i] = genFunctionWrapper(c.child[1]) + } else { + values[i] = genValue(c.child[1]) + } + } + + n.exec = func(f *frame) bltn { + m := reflect.MakeMap(typ) + for i, k := range keys { + m.SetMapIndex(k(f), values[i](f)) + } + value(f).Set(m) + return next + } +} + +func compositeBinSlice(n *node) { + value := valueGenerator(n, n.findex) + next := getExec(n.tnext) + child := n.child + if n.nleft == 1 { + child = n.child[1:] + } + + values := make([]func(*frame) reflect.Value, len(child)) + index := make([]int, len(child)) + rtype := n.typ.rtype.Elem() + var max, prev int + + for i, c := range child { + if c.kind == keyValueExpr { + convertLiteralValue(c.child[1], rtype) + values[i] = genValue(c.child[1]) + index[i] = int(vInt(c.child[0].rval)) + } else { + convertLiteralValue(c, rtype) + values[i] = genValue(c) + index[i] = prev + } + prev = index[i] + 1 + if prev > max { + max = prev + } + } + + typ := n.typ.frameType() + kind := typ.Kind() + n.exec = func(f *frame) bltn { + var a reflect.Value + if kind == reflect.Slice { + a = reflect.MakeSlice(typ, max, max) + } else { + a, _ = n.typ.zero() + } + for i, v := range values { + a.Index(index[i]).Set(v(f)) + } + value(f).Set(a) + return next + } +} + +// doCompositeBinStruct creates and populates a struct object from a binary type. +func doCompositeBinStruct(n *node, hasType bool) { + next := getExec(n.tnext) + value := valueGenerator(n, n.findex) + typ := n.typ.rtype + if n.typ.cat == ptrT || n.typ.cat == linkedT { + typ = n.typ.val.rtype + } + child := n.child + if hasType { + child = n.child[1:] + } + values := make([]func(*frame) reflect.Value, len(child)) + fieldIndex := make([][]int, len(child)) + for i, c := range child { + if c.kind == keyValueExpr { + if sf, ok := typ.FieldByName(c.child[0].ident); ok { + fieldIndex[i] = sf.Index + convertLiteralValue(c.child[1], sf.Type) + if isFuncSrc(c.child[1].typ) { + values[i] = genFunctionWrapper(c.child[1]) + } else { + values[i] = genValue(c.child[1]) + } + } + } else { + fieldIndex[i] = []int{i} + if isFuncSrc(c.typ) && len(c.child) > 1 { + convertLiteralValue(c.child[1], typ.Field(i).Type) + values[i] = genFunctionWrapper(c.child[1]) + } else { + convertLiteralValue(c, typ.Field(i).Type) + values[i] = genValue(c) + } + } + } + + frameIndex := n.findex + l := n.level + + n.exec = func(f *frame) bltn { + s := reflect.New(typ).Elem() + for i, v := range values { + s.FieldByIndex(fieldIndex[i]).Set(v(f)) + } + d := value(f) + switch { + case d.Kind() == reflect.Ptr: + d.Set(s.Addr()) + default: + getFrame(f, l).data[frameIndex] = s + } + return next + } +} + +func compositeBinStruct(n *node) { doCompositeBinStruct(n, true) } +func compositeBinStructNotype(n *node) { doCompositeBinStruct(n, false) } + +func destType(n *node) *itype { + switch n.anc.kind { + case assignStmt, defineStmt: + return n.anc.child[0].typ + default: + return n.typ + } +} + +func doComposite(n *node, hasType bool, keyed bool) { + value := valueGenerator(n, n.findex) + next := getExec(n.tnext) + typ := n.typ + if typ.cat == ptrT || typ.cat == linkedT { + typ = typ.val + } + child := n.child + if hasType { + child = n.child[1:] + } + destInterface := isInterfaceSrc(destType(n)) + + values := make(map[int]func(*frame) reflect.Value) + for i, c := range child { + var val *node + var fieldIndex int + if keyed { + val = c.child[1] + fieldIndex = typ.fieldIndex(c.child[0].ident) + } else { + val = c + fieldIndex = i + } + ft := typ.field[fieldIndex].typ + rft := ft.TypeOf() + convertLiteralValue(val, rft) + switch { + case val.typ.cat == nilT: + values[fieldIndex] = func(*frame) reflect.Value { return reflect.New(rft).Elem() } + case isNamedFuncSrc(val.typ): + values[fieldIndex] = genValueAsFunctionWrapper(val) + case isInterfaceSrc(ft) && (!isEmptyInterface(ft) || len(val.typ.method) > 0): + values[fieldIndex] = genValueInterface(val) + case isInterface(ft): + values[fieldIndex] = genInterfaceWrapper(val, rft) + default: + values[fieldIndex] = genValue(val) + } + } + + frameIndex := n.findex + l := n.level + rt := typ.TypeOf() + + n.exec = func(f *frame) bltn { + a := reflect.New(rt).Elem() + for i, v := range values { + a.Field(i).Set(v(f)) + } + d := value(f) + switch { + case d.Kind() == reflect.Ptr: + d.Set(a.Addr()) + case destInterface: + if len(destType(n).field) > 0 { + d.Set(reflect.ValueOf(valueInterface{n, a})) + break + } + d.Set(a) + default: + getFrame(f, l).data[frameIndex] = a + } + return next + } +} + +// doCompositeLit creates and populates a struct object. +func doCompositeLit(n *node, hasType bool) { + doComposite(n, hasType, false) +} + +func compositeLit(n *node) { doCompositeLit(n, true) } +func compositeLitNotype(n *node) { doCompositeLit(n, false) } + +// doCompositeLitKeyed creates a struct Object, filling fields from sparse key-values. +func doCompositeLitKeyed(n *node, hasType bool) { + doComposite(n, hasType, true) +} + +func compositeLitKeyed(n *node) { doCompositeLitKeyed(n, true) } +func compositeLitKeyedNotype(n *node) { doCompositeLitKeyed(n, false) } + +func empty(n *node) {} + +var rat = reflect.ValueOf((*[]rune)(nil)).Type().Elem() // runes array type + +func _range(n *node) { + index0 := n.child[0].findex // array index location in frame + index2 := index0 - 1 // shallow array for range, always just behind index0 + index3 := index2 - 1 // additional location to store string char position + fnext := getExec(n.fnext) + tnext := getExec(n.tnext) + + var value func(*frame) reflect.Value + var an *node + if len(n.child) == 4 { + an = n.child[2] + index1 := n.child[1].findex // array value location in frame + if isString(an.typ.TypeOf()) { + // Special variant of "range" for string, where the index indicates the byte position + // of the rune in the string, rather than the index of the rune in array. + stringType := reflect.TypeOf("") + value = genValueAs(an, rat) // range on string iterates over runes + n.exec = func(f *frame) bltn { + a := f.data[index2] + v0 := f.data[index3] + v0.SetInt(v0.Int() + 1) + i := int(v0.Int()) + if i >= a.Len() { + return fnext + } + // Compute byte position of the rune in string + pos := a.Slice(0, i).Convert(stringType).Len() + f.data[index0].SetInt(int64(pos)) + f.data[index1].Set(a.Index(i)) + return tnext + } + } else { + value = genValueRangeArray(an) + n.exec = func(f *frame) bltn { + a := f.data[index2] + v0 := f.data[index0] + v0.SetInt(v0.Int() + 1) + i := int(v0.Int()) + if i >= a.Len() { + return fnext + } + f.data[index1].Set(a.Index(i)) + return tnext + } + } + } else { + an = n.child[1] + if isString(an.typ.TypeOf()) { + value = genValueAs(an, rat) // range on string iterates over runes + } else { + value = genValueRangeArray(an) + } + n.exec = func(f *frame) bltn { + v0 := f.data[index0] + v0.SetInt(v0.Int() + 1) + if int(v0.Int()) >= f.data[index2].Len() { + return fnext + } + return tnext + } + } + + // Init sequence + next := n.exec + index := index0 + if isString(an.typ.TypeOf()) && len(n.child) == 4 { + index = index3 + } + n.child[0].exec = func(f *frame) bltn { + f.data[index2] = value(f) // set array shallow copy for range + f.data[index].SetInt(-1) // assing index value + return next + } +} + +func rangeChan(n *node) { + i := n.child[0].findex // element index location in frame + value := genValue(n.child[1]) // chan + fnext := getExec(n.fnext) + tnext := getExec(n.tnext) + + n.exec = func(f *frame) bltn { + f.mutex.RLock() + done := f.done + f.mutex.RUnlock() + + chosen, v, ok := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectRecv, Chan: value(f)}}) + if chosen == 0 { + return nil + } + if !ok { + return fnext + } + f.data[i].Set(v) + return tnext + } +} + +func rangeMap(n *node) { + index0 := n.child[0].findex // map index location in frame + index2 := index0 - 1 // iterator for range, always just behind index0 + fnext := getExec(n.fnext) + tnext := getExec(n.tnext) + + var value func(*frame) reflect.Value + if len(n.child) == 4 { + index1 := n.child[1].findex // map value location in frame + value = genValue(n.child[2]) // map + n.exec = func(f *frame) bltn { + iter := f.data[index2].Interface().(*reflect.MapIter) + if !iter.Next() { + return fnext + } + f.data[index0].Set(iter.Key()) + f.data[index1].Set(iter.Value()) + return tnext + } + } else { + value = genValue(n.child[1]) // map + n.exec = func(f *frame) bltn { + iter := f.data[index2].Interface().(*reflect.MapIter) + if !iter.Next() { + return fnext + } + f.data[index0].Set(iter.Key()) + return tnext + } + } + + // Init sequence + next := n.exec + n.child[0].exec = func(f *frame) bltn { + f.data[index2].Set(reflect.ValueOf(value(f).MapRange())) + return next + } +} + +func _case(n *node) { + tnext := getExec(n.tnext) + + // TODO(mpl): a lot of what is done in typeAssert should probably be redone/reused here. + switch { + case n.anc.anc.kind == typeSwitch: + fnext := getExec(n.fnext) + sn := n.anc.anc // switch node + types := make([]*itype, len(n.child)-1) + for i := range types { + types[i] = n.child[i].typ + } + srcValue := genValue(sn.child[1].lastChild().child[0]) + + if len(sn.child[1].child) != 2 { + // no assign in switch guard + if len(n.child) <= 1 { + n.exec = func(f *frame) bltn { return tnext } + } else { + n.exec = func(f *frame) bltn { + ival := srcValue(f).Interface() + val, ok := ival.(valueInterface) + // TODO(mpl): I'm assuming here that !ok means that we're dealing with the empty + // interface case. But maybe we should make sure by checking the relevant cat + // instead? later. Use t := v.Type(); t.Kind() == reflect.Interface , like above. + if !ok { + var stype string + if ival != nil { + stype = strings.ReplaceAll(reflect.TypeOf(ival).String(), " {}", "{}") + } + for _, typ := range types { + // TODO(mpl): we should actually use canAssertTypes, but need to find a valid + // rtype for typ. Plus we need to refactor with typeAssert(). + // weak check instead for now. + if ival == nil { + if typ.cat == nilT { + return tnext + } + continue + } + if stype == typ.id() { + return tnext + } + } + return fnext + } + if v := val.node; v != nil { + for _, typ := range types { + if v.typ.id() == typ.id() { + return tnext + } + } + } + return fnext + } + } + break + } + + // assign in switch guard + destValue := genValue(n.lastChild().child[0]) + switch len(types) { + case 0: + // default clause: assign var to interface value + n.exec = func(f *frame) bltn { + destValue(f).Set(srcValue(f)) + return tnext + } + case 1: + // match against 1 type: assign var to concrete value + typ := types[0] + n.exec = func(f *frame) bltn { + v := srcValue(f) + if !v.IsValid() { + // match zero value against nil + if typ.cat == nilT { + return tnext + } + return fnext + } + if t := v.Type(); t.Kind() == reflect.Interface { + if typ.cat == nilT && v.IsNil() { + return tnext + } + rtyp := typ.TypeOf() + if rtyp == nil { + return fnext + } + elem := v.Elem() + if rtyp.String() == t.String() && implementsInterface(v, typ) { + destValue(f).Set(elem) + return tnext + } + ival := v.Interface() + if ival != nil && rtyp.String() == reflect.TypeOf(ival).String() { + destValue(f).Set(elem) + return tnext + } + if typ.cat == valueT && rtyp.Kind() == reflect.Interface && elem.IsValid() && elem.Type().Implements(rtyp) { + destValue(f).Set(elem) + return tnext + } + return fnext + } + if vi, ok := v.Interface().(valueInterface); ok { + if vi.node != nil { + if vi.node.typ.id() == typ.id() { + destValue(f).Set(vi.value) + return tnext + } + } + return fnext + } + if v.Type() == typ.TypeOf() { + destValue(f).Set(v) + return tnext + } + return fnext + } + + default: + n.exec = func(f *frame) bltn { + val := srcValue(f) + if t := val.Type(); t.Kind() == reflect.Interface { + for _, typ := range types { + if typ.cat == nilT && val.IsNil() { + return tnext + } + rtyp := typ.TypeOf() + if rtyp == nil { + continue + } + elem := val.Elem() + if rtyp.String() == t.String() && implementsInterface(val, typ) { + destValue(f).Set(elem) + return tnext + } + ival := val.Interface() + if ival != nil && rtyp.String() == reflect.TypeOf(ival).String() { + destValue(f).Set(elem) + return tnext + } + if typ.cat == valueT && rtyp.Kind() == reflect.Interface && elem.IsValid() && elem.Type().Implements(rtyp) { + destValue(f).Set(elem) + return tnext + } + } + return fnext + } + if vi, ok := val.Interface().(valueInterface); ok { + if v := vi.node; v != nil { + for _, typ := range types { + if v.typ.id() == typ.id() { + destValue(f).Set(val) + return tnext + } + } + } + return fnext + } + vt := val.Type() + for _, typ := range types { + if vt == typ.TypeOf() { + destValue(f).Set(val) + return tnext + } + } + return fnext + } + } + + case len(n.child) <= 1: // default clause + n.exec = func(f *frame) bltn { return tnext } + + default: + fnext := getExec(n.fnext) + l := len(n.anc.anc.child) + value := genValue(n.anc.anc.child[l-2]) + values := make([]func(*frame) reflect.Value, len(n.child)-1) + for i := range values { + values[i] = genValue(n.child[i]) + } + n.exec = func(f *frame) bltn { + v0 := value(f) + for _, v := range values { + v1 := v(f) + if !v0.Type().AssignableTo(v1.Type()) { + v0 = v0.Convert(v1.Type()) + } + if v0.Interface() == v1.Interface() { + return tnext + } + } + return fnext + } + } +} + +func implementsInterface(v reflect.Value, t *itype) bool { + rt := v.Type() + if t.cat == valueT { + return rt.Implements(t.rtype) + } + vt := &itype{cat: valueT, rtype: rt} + if vt.methods().contains(t.methods()) { + return true + } + vi, ok := v.Interface().(valueInterface) + if !ok { + return false + } + return vi.node != nil && vi.node.typ.methods().contains(t.methods()) +} + +func appendSlice(n *node) { + dest := genValueOutput(n, n.typ.rtype) + next := getExec(n.tnext) + value := genValue(n.child[1]) + value0 := genValue(n.child[2]) + + if isString(n.child[2].typ.TypeOf()) { + typ := reflect.TypeOf([]byte{}) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.AppendSlice(value(f), value0(f).Convert(typ))) + return next + } + } else { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.AppendSlice(value(f), value0(f))) + return next + } + } +} + +func _append(n *node) { + if len(n.child) == 3 { + c1, c2 := n.child[1], n.child[2] + if (c1.typ.cat == valueT || c2.typ.cat == valueT) && c1.typ.rtype == c2.typ.rtype || + isArray(c2.typ) && c2.typ.elem().id() == n.typ.elem().id() || + isByteArray(c1.typ.TypeOf()) && isString(c2.typ.TypeOf()) { + appendSlice(n) + return + } + } + + dest := genValueOutput(n, n.typ.rtype) + value := genValue(n.child[1]) + next := getExec(n.tnext) + + switch l := len(n.child); { + case l == 2: + n.exec = func(f *frame) bltn { + dest(f).Set(value(f)) + return next + } + case l > 3: + args := n.child[2:] + l := len(args) + values := make([]func(*frame) reflect.Value, l) + for i, arg := range args { + switch elem := n.typ.elem(); { + case isInterfaceSrc(elem) && (!isEmptyInterface(elem) || len(arg.typ.method) > 0): + values[i] = genValueInterface(arg) + case isInterfaceBin(elem): + values[i] = genInterfaceWrapper(arg, elem.rtype) + case arg.typ.untyped: + values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem()) + default: + values[i] = genValue(arg) + } + } + + n.exec = func(f *frame) bltn { + sl := make([]reflect.Value, l) + for i, v := range values { + sl[i] = v(f) + } + dest(f).Set(reflect.Append(value(f), sl...)) + return next + } + default: + var value0 func(*frame) reflect.Value + switch elem := n.typ.elem(); { + case isInterfaceSrc(elem) && (!isEmptyInterface(elem) || len(n.child[2].typ.method) > 0): + value0 = genValueInterface(n.child[2]) + case isInterfaceBin(elem): + value0 = genInterfaceWrapper(n.child[2], elem.rtype) + case n.child[2].typ.untyped: + value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem()) + default: + value0 = genValue(n.child[2]) + } + + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.Append(value(f), value0(f))) + return next + } + } +} + +func _cap(n *node) { + dest := genValueOutput(n, reflect.TypeOf(int(0))) + value := genValue(n.child[1]) + next := getExec(n.tnext) + + if wantEmptyInterface(n) { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(value(f).Cap())) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetInt(int64(value(f).Cap())) + return next + } +} + +func _copy(n *node) { + in := []func(*frame) reflect.Value{genValueArray(n.child[1]), genValue(n.child[2])} + out := []func(*frame) reflect.Value{genValueOutput(n, reflect.TypeOf(0))} + + genBuiltinDeferWrapper(n, in, out, func(args []reflect.Value) []reflect.Value { + cnt := reflect.Copy(args[0], args[1]) + return []reflect.Value{reflect.ValueOf(cnt)} + }) +} + +func _close(n *node) { + in := []func(*frame) reflect.Value{genValue(n.child[1])} + + genBuiltinDeferWrapper(n, in, nil, func(args []reflect.Value) []reflect.Value { + args[0].Close() + return nil + }) +} + +func _complex(n *node) { + dest := genValueOutput(n, reflect.TypeOf(complex(0, 0))) + c1, c2 := n.child[1], n.child[2] + convertLiteralValue(c1, floatType) + convertLiteralValue(c2, floatType) + value0 := genValue(c1) + value1 := genValue(c2) + next := getExec(n.tnext) + + typ := n.typ.TypeOf() + if isComplex(typ) { + if wantEmptyInterface(n) { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(complex(value0(f).Float(), value1(f).Float()))) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetComplex(complex(value0(f).Float(), value1(f).Float())) + return next + } + return + } + // Not a complex type: ignore imaginary part + n.exec = func(f *frame) bltn { + dest(f).Set(value0(f).Convert(typ)) + return next + } +} + +func _imag(n *node) { + dest := genValueOutput(n, reflect.TypeOf(float64(0))) + convertLiteralValue(n.child[1], complexType) + value := genValue(n.child[1]) + next := getExec(n.tnext) + + if wantEmptyInterface(n) { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(imag(value(f).Complex()))) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetFloat(imag(value(f).Complex())) + return next + } +} + +func _real(n *node) { + dest := genValueOutput(n, reflect.TypeOf(float64(0))) + convertLiteralValue(n.child[1], complexType) + value := genValue(n.child[1]) + next := getExec(n.tnext) + + if wantEmptyInterface(n) { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(real(value(f).Complex()))) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetFloat(real(value(f).Complex())) + return next + } +} + +func _delete(n *node) { + value0 := genValue(n.child[1]) // map + value1 := genValue(n.child[2]) // key + in := []func(*frame) reflect.Value{value0, value1} + var z reflect.Value + + genBuiltinDeferWrapper(n, in, nil, func(args []reflect.Value) []reflect.Value { + args[0].SetMapIndex(args[1], z) + return nil + }) +} + +func capConst(n *node) { + // There is no Cap() method for reflect.Type, just return Len() instead. + lenConst(n) +} + +func lenConst(n *node) { + n.rval = reflect.New(reflect.TypeOf(int(0))).Elem() + c1 := n.child[1] + if c1.rval.IsValid() { + n.rval.SetInt(int64(len(vString(c1.rval)))) + return + } + t := c1.typ.TypeOf() + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + n.rval.SetInt(int64(t.Len())) +} + +func _len(n *node) { + dest := genValueOutput(n, reflect.TypeOf(int(0))) + value := genValue(n.child[1]) + if isPtr(n.child[1].typ) { + val := value + value = func(f *frame) reflect.Value { + v := val(f).Elem() + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + return v + } + } + next := getExec(n.tnext) + + if wantEmptyInterface(n) { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(value(f).Len())) + return next + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetInt(int64(value(f).Len())) + return next + } +} + +func _new(n *node) { + next := getExec(n.tnext) + t1 := n.child[1].typ + typ := t1.TypeOf() + dest := genValueOutput(n, reflect.PtrTo(typ)) + + if isInterfaceSrc(t1) && (!isEmptyInterface(t1) || len(t1.method) > 0) { + typ = zeroInterfaceValue().Type() + } + + n.exec = func(f *frame) bltn { + v := reflect.New(typ) + if vi, ok := v.Interface().(*valueInterface); ok { + vi.node = n + } + dest(f).Set(v) + return next + } +} + +// _make allocates and initializes a slice, a map or a chan. +func _make(n *node) { + next := getExec(n.tnext) + typ := n.child[1].typ.frameType() + dest := genValueOutput(n, typ) + + switch typ.Kind() { + case reflect.Array, reflect.Slice: + value := genValue(n.child[2]) + + switch len(n.child) { + case 3: + n.exec = func(f *frame) bltn { + length := int(vInt(value(f))) + dest(f).Set(reflect.MakeSlice(typ, length, length)) + return next + } + case 4: + value1 := genValue(n.child[3]) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.MakeSlice(typ, int(vInt(value(f))), int(vInt(value1(f))))) + return next + } + } + + case reflect.Chan: + switch len(n.child) { + case 2: + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.MakeChan(typ, 0)) + return next + } + case 3: + value := genValue(n.child[2]) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.MakeChan(typ, int(vInt(value(f))))) + return next + } + } + + case reflect.Map: + switch len(n.child) { + case 2: + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.MakeMap(typ)) + return next + } + case 3: + value := genValue(n.child[2]) + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.MakeMapWithSize(typ, int(vInt(value(f))))) + return next + } + } + } +} + +func reset(n *node) { + next := getExec(n.tnext) + + switch l := len(n.child) - 1; l { + case 1: + typ := n.child[0].typ.frameType() + i := n.child[0].findex + n.exec = func(f *frame) bltn { + f.data[i] = reflect.New(typ).Elem() + return next + } + case 2: + c0, c1 := n.child[0], n.child[1] + i0, i1 := c0.findex, c1.findex + t0, t1 := c0.typ.frameType(), c1.typ.frameType() + n.exec = func(f *frame) bltn { + f.data[i0] = reflect.New(t0).Elem() + f.data[i1] = reflect.New(t1).Elem() + return next + } + default: + types := make([]reflect.Type, l) + index := make([]int, l) + for i, c := range n.child[:l] { + index[i] = c.findex + types[i] = c.typ.frameType() + } + n.exec = func(f *frame) bltn { + for i, ind := range index { + f.data[ind] = reflect.New(types[i]).Elem() + } + return next + } + } +} + +// recv reads from a channel. +func recv(n *node) { + value := genValue(n.child[0]) + tnext := getExec(n.tnext) + i := n.findex + l := n.level + + if n.interp.cancelChan { + // Cancellable channel read + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + // Fast: channel read doesn't block + ch := value(f) + if r, ok := ch.TryRecv(); ok { + getFrame(f, l).data[i] = r + if r.Bool() { + return tnext + } + return fnext + } + // Slow: channel read blocks, allow cancel + f.mutex.RLock() + done := f.done + f.mutex.RUnlock() + + chosen, v, _ := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectRecv, Chan: ch}}) + if chosen == 0 { + return nil + } + if v.Bool() { + return tnext + } + return fnext + } + } else { + n.exec = func(f *frame) bltn { + // Fast: channel read doesn't block + ch := value(f) + if r, ok := ch.TryRecv(); ok { + getFrame(f, l).data[i] = r + return tnext + } + // Slow: channel is blocked, allow cancel + f.mutex.RLock() + done := f.done + f.mutex.RUnlock() + + var chosen int + chosen, getFrame(f, l).data[i], _ = reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectRecv, Chan: ch}}) + if chosen == 0 { + return nil + } + return tnext + } + } + } else { + // Blocking channel read (less overhead) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + if r, _ := value(f).Recv(); r.Bool() { + getFrame(f, l).data[i] = r + return tnext + } + return fnext + } + } else { + i := n.findex + n.exec = func(f *frame) bltn { + getFrame(f, l).data[i], _ = value(f).Recv() + return tnext + } + } + } +} + +func recv2(n *node) { + vchan := genValue(n.child[0]) // chan + vres := genValue(n.anc.child[0]) // result + vok := genValue(n.anc.child[1]) // status + tnext := getExec(n.tnext) + + if n.interp.cancelChan { + // Cancellable channel read + n.exec = func(f *frame) bltn { + ch, result, status := vchan(f), vres(f), vok(f) + // Fast: channel read doesn't block + if v, ok := ch.TryRecv(); ok { + result.Set(v) + status.SetBool(true) + return tnext + } + // Slow: channel is blocked, allow cancel + f.mutex.RLock() + done := f.done + f.mutex.RUnlock() + + chosen, v, ok := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectRecv, Chan: ch}}) + if chosen == 0 { + return nil + } + result.Set(v) + status.SetBool(ok) + return tnext + } + } else { + // Blocking channel read (less overhead) + n.exec = func(f *frame) bltn { + v, ok := vchan(f).Recv() + vres(f).Set(v) + vok(f).SetBool(ok) + return tnext + } + } +} + +func convertLiteralValue(n *node, t reflect.Type) { + switch { + case n.typ.cat == nilT: + // Create a zero value of target type. + n.rval = reflect.New(t).Elem() + case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface || t == valueInterfaceType || t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface: + // Skip non-constant values, undefined target type or interface target type. + case n.rval.IsValid(): + // Convert constant value to target type. + convertConstantValue(n) + n.rval = n.rval.Convert(t) + default: + // Create a zero value of target type. + n.rval = reflect.New(t).Elem() + } +} + +func convertConstantValue(n *node) { + if !n.rval.IsValid() { + return + } + c, ok := n.rval.Interface().(constant.Value) + if !ok { + return + } + + var v reflect.Value + + switch c.Kind() { + case constant.Bool: + v = reflect.ValueOf(constant.BoolVal(c)) + case constant.String: + v = reflect.ValueOf(constant.StringVal(c)) + case constant.Int: + i, x := constant.Int64Val(c) + if !x { + panic(n.cfgErrorf("constant %s overflows int64", c.ExactString())) + } + v = reflect.ValueOf(int(i)) + case constant.Float: + f, _ := constant.Float64Val(c) + v = reflect.ValueOf(f) + case constant.Complex: + r, _ := constant.Float64Val(constant.Real(c)) + i, _ := constant.Float64Val(constant.Imag(c)) + v = reflect.ValueOf(complex(r, i)) + } + + n.rval = v.Convert(n.typ.TypeOf()) +} + +// Write to a channel. +func send(n *node) { + next := getExec(n.tnext) + c0, c1 := n.child[0], n.child[1] + value0 := genValue(c0) // Send channel. + value1 := genDestValue(c0.typ.val, c1) + + if !n.interp.cancelChan { + // Send is non-cancellable, has the least overhead. + n.exec = func(f *frame) bltn { + value0(f).Send(value1(f)) + return next + } + return + } + + // Send is cancellable, may have some overhead. + n.exec = func(f *frame) bltn { + ch, data := value0(f), value1(f) + // Fast: send on channel doesn't block. + if ok := ch.TrySend(data); ok { + return next + } + // Slow: send on channel blocks, allow cancel. + f.mutex.RLock() + done := f.done + f.mutex.RUnlock() + + chosen, _, _ := reflect.Select([]reflect.SelectCase{done, {Dir: reflect.SelectSend, Chan: ch, Send: data}}) + if chosen == 0 { + return nil + } + return next + } +} + +func clauseChanDir(n *node) (*node, *node, *node, reflect.SelectDir) { + dir := reflect.SelectDefault + var nod, assigned, ok *node + var stop bool + + n.Walk(func(m *node) bool { + switch m.action { + case aRecv: + dir = reflect.SelectRecv + nod = m.child[0] + switch m.anc.action { + case aAssign: + assigned = m.anc.child[0] + case aAssignX: + assigned = m.anc.child[0] + ok = m.anc.child[1] + } + stop = true + case aSend: + dir = reflect.SelectSend + nod = m.child[0] + assigned = m.child[1] + stop = true + } + return !stop + }, nil) + return nod, assigned, ok, dir +} + +func _select(n *node) { + nbClause := len(n.child) + chans := make([]*node, nbClause) + assigned := make([]*node, nbClause) + ok := make([]*node, nbClause) + clause := make([]bltn, nbClause) + chanValues := make([]func(*frame) reflect.Value, nbClause) + assignedValues := make([]func(*frame) reflect.Value, nbClause) + okValues := make([]func(*frame) reflect.Value, nbClause) + cases := make([]reflect.SelectCase, nbClause+1) + next := getExec(n.tnext) + + for i := 0; i < nbClause; i++ { + cl := n.child[i] + if cl.kind == commClauseDefault { + cases[i].Dir = reflect.SelectDefault + if len(cl.child) == 0 { + clause[i] = func(*frame) bltn { return next } + } else { + clause[i] = getExec(cl.child[0].start) + } + continue + } + // The comm clause is in send or recv direction. + switch c0 := cl.child[0]; { + case len(cl.child) > 1: + // The comm clause contains a channel operation and a clause body. + clause[i] = getExec(cl.child[1].start) + chans[i], assigned[i], ok[i], cases[i].Dir = clauseChanDir(c0) + chanValues[i] = genValue(chans[i]) + if assigned[i] != nil { + assignedValues[i] = genValue(assigned[i]) + } + if ok[i] != nil { + okValues[i] = genValue(ok[i]) + } + case c0.kind == exprStmt && len(c0.child) == 1 && c0.child[0].action == aRecv: + // The comm clause has an empty body clause after channel receive. + chanValues[i] = genValue(c0.child[0].child[0]) + cases[i].Dir = reflect.SelectRecv + clause[i] = func(*frame) bltn { return next } + case c0.kind == sendStmt: + // The comm clause as an empty body clause after channel send. + chanValues[i] = genValue(c0.child[0]) + cases[i].Dir = reflect.SelectSend + assignedValues[i] = genValue(c0.child[1]) + clause[i] = func(*frame) bltn { return next } + } + } + + n.exec = func(f *frame) bltn { + // Kit patch: keep channel values local to each execution frame. + // Concurrent callbacks share the compiled node, not select state. + cases := append([]reflect.SelectCase(nil), cases...) + f.mutex.RLock() + cases[nbClause] = f.done + f.mutex.RUnlock() + + for i := range cases[:nbClause] { + switch cases[i].Dir { + case reflect.SelectRecv: + cases[i].Chan = chanValues[i](f) + case reflect.SelectSend: + cases[i].Chan = chanValues[i](f) + cases[i].Send = assignedValues[i](f) + case reflect.SelectDefault: + // Keep zero values for comm clause + } + } + j, v, s := reflect.Select(cases) + if j == nbClause { + return nil + } + if cases[j].Dir == reflect.SelectRecv && assignedValues[j] != nil { + assignedValues[j](f).Set(v) + if ok[j] != nil { + okValues[j](f).SetBool(s) + } + } + return clause[j] + } +} + +// slice expression: array[low:high:max]. +func slice(n *node) { + i := n.findex + l := n.level + next := getExec(n.tnext) + value0 := genValueArray(n.child[0]) // array + value1 := genValue(n.child[1]) // low (if 2 or 3 args) or high (if 1 arg) + + switch len(n.child) { + case 2: + n.exec = func(f *frame) bltn { + a := value0(f) + getFrame(f, l).data[i] = a.Slice(int(vInt(value1(f))), a.Len()) + return next + } + case 3: + value2 := genValue(n.child[2]) // max + + n.exec = func(f *frame) bltn { + a := value0(f) + getFrame(f, l).data[i] = a.Slice(int(vInt(value1(f))), int(vInt(value2(f)))) + return next + } + case 4: + value2 := genValue(n.child[2]) + value3 := genValue(n.child[3]) + + n.exec = func(f *frame) bltn { + a := value0(f) + getFrame(f, l).data[i] = a.Slice3(int(vInt(value1(f))), int(vInt(value2(f))), int(vInt(value3(f)))) + return next + } + } +} + +// slice expression, no low value: array[:high:max]. +func slice0(n *node) { + i := n.findex + l := n.level + next := getExec(n.tnext) + value0 := genValueArray(n.child[0]) + + switch len(n.child) { + case 1: + n.exec = func(f *frame) bltn { + a := value0(f) + getFrame(f, l).data[i] = a.Slice(0, a.Len()) + return next + } + case 2: + value1 := genValue(n.child[1]) + n.exec = func(f *frame) bltn { + a := value0(f) + getFrame(f, l).data[i] = a.Slice(0, int(vInt(value1(f)))) + return next + } + case 3: + value1 := genValue(n.child[1]) + value2 := genValue(n.child[2]) + n.exec = func(f *frame) bltn { + a := value0(f) + getFrame(f, l).data[i] = a.Slice3(0, int(vInt(value1(f))), int(vInt(value2(f)))) + return next + } + } +} + +func isNilChild(child int) func(n *node) { + return func(n *node) { + var value func(*frame) reflect.Value + child := n.child[child] + value = genValue(child) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + tnext := getExec(n.tnext) + dest := genValue(n) + if n.fnext == nil { + if !isInterfaceSrc(child.typ) { + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(value(f).IsNil()).Convert(typ)) + return tnext + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetBool(value(f).IsNil()) + return tnext + } + return + } + if isInterface { + n.exec = func(f *frame) bltn { + v := value(f) + var r bool + if vi, ok := v.Interface().(valueInterface); ok { + r = (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) + } else { + r = v.IsNil() + } + dest(f).Set(reflect.ValueOf(r).Convert(typ)) + return tnext + } + return + } + n.exec = func(f *frame) bltn { + v := value(f) + var r bool + if vi, ok := v.Interface().(valueInterface); ok { + r = (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) + } else { + r = v.IsNil() + } + dest(f).SetBool(r) + return tnext + } + return + } + + fnext := getExec(n.fnext) + + if !isInterfaceSrc(child.typ) { + n.exec = func(f *frame) bltn { + if value(f).IsNil() { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + return + } + + n.exec = func(f *frame) bltn { + v := value(f) + if vi, ok := v.Interface().(valueInterface); ok { + if (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + if v.IsNil() { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } +} + +func isNotNil(n *node) { + var value func(*frame) reflect.Value + c0 := n.child[0] + value = genValue(c0) + typ := n.typ.concrete().TypeOf() + isInterface := n.typ.TypeOf().Kind() == reflect.Interface + tnext := getExec(n.tnext) + dest := genValue(n) + + if n.fnext == nil { + if isInterfaceSrc(c0.typ) && c0.typ.TypeOf() != valueInterfaceType { + if isInterface { + n.exec = func(f *frame) bltn { + dest(f).Set(reflect.ValueOf(!value(f).IsNil()).Convert(typ)) + return tnext + } + return + } + n.exec = func(f *frame) bltn { + dest(f).SetBool(!value(f).IsNil()) + return tnext + } + return + } + + if isInterface { + n.exec = func(f *frame) bltn { + v := value(f) + var r bool + if vi, ok := v.Interface().(valueInterface); ok { + r = (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) + } else { + r = v.IsNil() + } + dest(f).Set(reflect.ValueOf(!r).Convert(typ)) + return tnext + } + return + } + n.exec = func(f *frame) bltn { + v := value(f) + var r bool + if vi, ok := v.Interface().(valueInterface); ok { + r = (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) + } else { + r = v.IsNil() + } + dest(f).SetBool(!r) + return tnext + } + return + } + + fnext := getExec(n.fnext) + + if isInterfaceSrc(c0.typ) && c0.typ.TypeOf() != valueInterfaceType { + n.exec = func(f *frame) bltn { + if value(f).IsNil() { + dest(f).SetBool(false) + return fnext + } + dest(f).SetBool(true) + return tnext + } + return + } + + n.exec = func(f *frame) bltn { + v := value(f) + if vi, ok := v.Interface().(valueInterface); ok { + if (vi == valueInterface{} || vi.node.kind == basicLit && vi.node.typ.cat == nilT) { + dest(f).SetBool(false) + return fnext + } + dest(f).SetBool(true) + return tnext + } + if v.IsNil() { + dest(f).SetBool(false) + return fnext + } + dest(f).SetBool(true) + return tnext + } +} + +func complexConst(n *node) { + if v0, v1 := n.child[1].rval, n.child[2].rval; v0.IsValid() && v1.IsValid() { + n.rval = reflect.ValueOf(complex(vFloat(v0), vFloat(v1))) + n.gen = nop + } +} + +func imagConst(n *node) { + if v := n.child[1].rval; v.IsValid() { + n.rval = reflect.ValueOf(imag(v.Complex())) + n.gen = nop + } +} + +func realConst(n *node) { + if v := n.child[1].rval; v.IsValid() { + n.rval = reflect.ValueOf(real(v.Complex())) + n.gen = nop + } +} diff --git a/internal/yaegi/interp/scope.go b/internal/yaegi/interp/scope.go new file mode 100644 index 00000000..52087aee --- /dev/null +++ b/internal/yaegi/interp/scope.go @@ -0,0 +1,253 @@ +package interp + +import ( + "log" + "reflect" + "strconv" +) + +// A sKind represents the kind of symbol. +type sKind uint + +// Symbol kinds for the Go interpreter. +const ( + undefSym sKind = iota + binSym // Binary from runtime + bltnSym // Builtin + constSym // Constant + funcSym // Function + labelSym // Label + pkgSym // Package + typeSym // Type + varTypeSym // Variable type (generic) + varSym // Variable +) + +var symKinds = [...]string{ + undefSym: "undefSym", + binSym: "binSym", + bltnSym: "bltnSym", + constSym: "constSym", + funcSym: "funcSym", + labelSym: "labelSym", + pkgSym: "pkgSym", + typeSym: "typeSym", + varTypeSym: "varTypeSym", + varSym: "varSym", +} + +func (k sKind) String() string { + if k < sKind(len(symKinds)) { + return symKinds[k] + } + return "SymKind(" + strconv.Itoa(int(k)) + ")" +} + +// A symbol represents an interpreter object such as type, constant, var, func, +// label, builtin or binary object. Symbols are defined within a scope. +type symbol struct { + kind sKind + typ *itype // Type of value + node *node // Node value if index is negative + from []*node // list of goto nodes jumping to this label node, or nil + recv *receiver // receiver node value, if sym refers to a method + index int // index of value in frame or -1 + rval reflect.Value // default value (used for constants) + builtin bltnGenerator // Builtin function or nil + global bool // true if symbol is defined in global space +} + +// scope type stores symbols in maps, and frame layout as array of types +// The purposes of scopes are to manage the visibility of each symbol +// and to store the memory frame layout information (type and index in frame) +// at each level (global, package, functions) +// +// scopes are organized in a stack fashion: a first scope (universe) is created +// once at global level, and for each block (package, func, for, etc...), a new +// scope is pushed at entry, and poped at exit. +// +// Nested scopes with the same level value use the same frame: it allows to have +// exactly one frame per function, with a fixed position for each variable (named +// or not), no matter the inner complexity (number of nested blocks in the function) +// +// In symbols, the index value corresponds to the index in scope.types, and at +// execution to the index in frame, created exactly from the types layout. +type scope struct { + anc *scope // ancestor upper scope + child []*scope // included scopes + def *node // function definition node this scope belongs to, or nil + loop *node // loop exit node for break statement + loopRestart *node // loop restart node for continue statement + pkgID string // unique id of package in which scope is defined + pkgName string // package name for the package + types []reflect.Type // frame layout, may be shared by same level scopes + level int // frame level: number of frame indirections to access var during execution + sym map[string]*symbol // map of symbols defined in this current scope + global bool // true if scope refers to global space (single frame for universe and package level scopes) + iota int // iota value in this scope +} + +// push creates a new child scope and chain it to the current one. +func (s *scope) push(indirect bool) *scope { + sc := &scope{anc: s, level: s.level, sym: map[string]*symbol{}} + s.child = append(s.child, sc) + if indirect { + sc.types = []reflect.Type{} + sc.level = s.level + 1 + } else { + // Propagate size, types, def and global as scopes at same level share the same frame. + sc.types = s.types + sc.def = s.def + sc.global = s.global + sc.level = s.level + } + // inherit loop state and pkgID from ancestor + sc.loop, sc.loopRestart, sc.pkgID = s.loop, s.loopRestart, s.pkgID + return sc +} + +func (s *scope) pushBloc() *scope { return s.push(false) } +func (s *scope) pushFunc() *scope { return s.push(true) } + +func (s *scope) pop() *scope { + if s.level == s.anc.level { + // Propagate size and types, as scopes at same level share the same frame. + s.anc.types = s.types + } + return s.anc +} + +func (s *scope) upperLevel() *scope { + level := s.level + for s != nil && s.level == level { + s = s.anc + } + return s +} + +// lookup searches for a symbol in the current scope, and upper ones if not found +// it returns the symbol, the number of indirections level from the current scope +// and status (false if no result). +func (s *scope) lookup(ident string) (*symbol, int, bool) { + level := s.level + for { + if sym, ok := s.sym[ident]; ok { + if sym.global { + return sym, globalFrame, true + } + return sym, level - s.level, true + } + if s.anc == nil { + break + } + s = s.anc + } + return nil, 0, false +} + +func (s *scope) rangeChanType(n *node) *itype { + if sym, _, found := s.lookup(n.child[1].ident); found { + if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) { + return t + } + } + + c := n.child[1] + if c.typ == nil { + return nil + } + switch { + case c.typ.cat == chanT, c.typ.cat == chanRecvT: + return c.typ + case c.typ.cat == valueT && c.typ.rtype.Kind() == reflect.Chan: + dir := chanSendRecv + switch c.typ.rtype.ChanDir() { + case reflect.RecvDir: + dir = chanRecv + case reflect.SendDir: + dir = chanSend + } + return chanOf(valueTOf(c.typ.rtype.Elem()), dir) + } + + return nil +} + +// fixType returns the input type, or a valid default type for untyped constant. +func (s *scope) fixType(t *itype) *itype { + if !t.untyped || t.cat != valueT { + return t + } + switch typ := t.TypeOf(); typ.Kind() { + case reflect.Int64: + return s.getType("int") + case reflect.Uint64: + return s.getType("uint") + case reflect.Float64: + return s.getType("float64") + case reflect.Complex128: + return s.getType("complex128") + } + return t +} + +func (s *scope) getType(ident string) *itype { + var t *itype + if sym, _, found := s.lookup(ident); found { + if sym.kind == typeSym { + t = sym.typ + } + } + return t +} + +// add adds a type to the scope types array, and returns its index. +func (s *scope) add(typ *itype) (index int) { + if typ == nil { + log.Panic("nil type") + } + index = len(s.types) + t := typ.frameType() + if t == nil { + log.Panic("nil reflect type") + } + s.types = append(s.types, t) + return +} + +func (interp *Interpreter) initScopePkg(pkgID, pkgName string) *scope { + sc := interp.universe + + interp.mutex.Lock() + if _, ok := interp.scopes[pkgID]; !ok { + interp.scopes[pkgID] = sc.pushBloc() + } + sc = interp.scopes[pkgID] + sc.pkgID = pkgID + sc.pkgName = pkgName + interp.mutex.Unlock() + return sc +} + +// Globals returns a map of global variables and constants in the main package. +func (interp *Interpreter) Globals() map[string]reflect.Value { + syms := map[string]reflect.Value{} + interp.mutex.RLock() + defer interp.mutex.RUnlock() + + v, ok := interp.srcPkg["main"] + if !ok { + return syms + } + + for n, s := range v { + switch s.kind { + case constSym: + syms[n] = s.rval + case varSym: + syms[n] = interp.frame.data[s.index] + } + } + + return syms +} diff --git a/internal/yaegi/interp/src.go b/internal/yaegi/interp/src.go new file mode 100644 index 00000000..bf167c37 --- /dev/null +++ b/internal/yaegi/interp/src.go @@ -0,0 +1,328 @@ +package interp + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// importSrc calls gta on the source code for the package identified by +// importPath. rPath is the relative path to the directory containing the source +// code for the package. It can also be "main" as a special value. +func (interp *Interpreter) importSrc(rPath, importPath string, skipTest bool) (string, error) { + var dir string + var err error + + if interp.srcPkg[importPath] != nil { + name, ok := interp.pkgNames[importPath] + if !ok { + return "", fmt.Errorf("inconsistent knowledge about %s", importPath) + } + return name, nil + } + + // For relative import paths in the form "./xxx" or "../xxx", the initial + // base path is the directory of the interpreter input file, or "." if no file + // was provided. + // In all other cases, absolute import paths are resolved from the GOPATH + // and the nested "vendor" directories. + if isPathRelative(importPath) { + if rPath == mainID { + rPath = "." + } + dir = filepath.Join(filepath.Dir(interp.name), rPath, importPath) + } else if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil { + // Try again, assuming a root dir at the source location. + if rPath, err = interp.rootFromSourceLocation(); err != nil { + return "", err + } + if dir, rPath, err = interp.pkgDir(interp.context.GOPATH, rPath, importPath); err != nil { + return "", err + } + } + + if interp.rdir[importPath] { + return "", fmt.Errorf("import cycle not allowed\n\timports %s", importPath) + } + interp.rdir[importPath] = true + + files, err := fs.ReadDir(interp.opt.filesystem, dir) + if err != nil { + return "", err + } + + var initNodes []*node + var rootNodes []*node + revisit := make(map[string][]*node) + + var root *node + var pkgName string + + // Parse source files. + for _, file := range files { + name := file.Name() + if skipFile(&interp.context, name, skipTest) { + continue + } + + name = filepath.Join(dir, name) + var buf []byte + if buf, err = fs.ReadFile(interp.opt.filesystem, name); err != nil { + return "", err + } + + n, err := interp.parse(string(buf), name, false) + if err != nil { + return "", err + } + if n == nil { + continue + } + + var pname string + if pname, root, err = interp.ast(n); err != nil { + return "", err + } + if root == nil { + continue + } + + if interp.astDot { + dotCmd := interp.dotCmd + if dotCmd == "" { + dotCmd = defaultDotCmd(name, "yaegi-ast-") + } + root.astDot(dotWriter(dotCmd), name) + } + if pkgName == "" { + pkgName = pname + } else if pkgName != pname && skipTest { + return "", fmt.Errorf("found packages %s and %s in %s", pkgName, pname, dir) + } + rootNodes = append(rootNodes, root) + + subRPath := effectivePkg(rPath, importPath) + var list []*node + list, err = interp.gta(root, subRPath, importPath, pkgName) + if err != nil { + return "", err + } + revisit[subRPath] = append(revisit[subRPath], list...) + } + + // Revisit incomplete nodes where GTA could not complete. + for _, nodes := range revisit { + if err = interp.gtaRetry(nodes, importPath, pkgName); err != nil { + return "", err + } + } + + // Generate control flow graphs. + for _, root := range rootNodes { + var nodes []*node + if nodes, err = interp.cfg(root, nil, importPath, pkgName); err != nil { + return "", err + } + initNodes = append(initNodes, nodes...) + } + + // Register source package in the interpreter. The package contains only + // the global symbols in the package scope. + interp.mutex.Lock() + gs := interp.scopes[importPath] + if gs == nil { + interp.mutex.Unlock() + // A nil scope means that no even an empty package is created from source. + return "", fmt.Errorf("no Go files in %s", dir) + } + interp.srcPkg[importPath] = gs.sym + interp.pkgNames[importPath] = pkgName + + interp.frame.mutex.Lock() + interp.resizeFrame() + interp.frame.mutex.Unlock() + interp.mutex.Unlock() + + // Once all package sources have been parsed, execute entry points then init functions. + for _, n := range rootNodes { + if err = genRun(n); err != nil { + return "", err + } + interp.run(n, nil) + } + + // Wire and execute global vars in global scope gs. + n, err := genGlobalVars(rootNodes, gs) + if err != nil { + return "", err + } + interp.run(n, nil) + + // Add main to list of functions to run, after all inits. + if m := gs.sym[mainID]; pkgName == mainID && m != nil && skipTest { + initNodes = append(initNodes, m.node) + } + + for _, n := range initNodes { + interp.run(n, interp.frame) + } + + return pkgName, nil +} + +// rootFromSourceLocation returns the path to the directory containing the input +// Go file given to the interpreter, relative to $GOPATH/src. +// It is meant to be called in the case when the initial input is a main package. +func (interp *Interpreter) rootFromSourceLocation() (string, error) { + sourceFile := interp.name + if sourceFile == DefaultSourceName { + return "", nil + } + wd, err := os.Getwd() + if err != nil { + return "", err + } + pkgDir := filepath.Join(wd, filepath.Dir(sourceFile)) + root := strings.TrimPrefix(pkgDir, filepath.Join(interp.context.GOPATH, "src")+"/") + if root == wd { + return "", fmt.Errorf("package location %s not in GOPATH", pkgDir) + } + return root, nil +} + +// pkgDir returns the absolute path in filesystem for a package given its import path +// and the root of the subtree dependencies. +func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (string, string, error) { + rPath := filepath.Join(root, "vendor") + dir := filepath.Join(goPath, "src", rPath, importPath) + + if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil { + return dir, rPath, nil // found! + } + + dir = filepath.Join(goPath, "src", effectivePkg(root, importPath)) + + if _, err := fs.Stat(interp.opt.filesystem, dir); err == nil { + return dir, root, nil // found! + } + + if root == "" { + if interp.context.GOPATH == "" { + return "", "", fmt.Errorf("unable to find source related to: %q. Either the GOPATH environment variable, or the Interpreter.Options.GoPath needs to be set", importPath) + } + return "", "", fmt.Errorf("unable to find source related to: %q", importPath) + } + + rootPath := filepath.Join(goPath, "src", root) + prevRoot, err := previousRoot(interp.opt.filesystem, rootPath, root) + if err != nil { + return "", "", err + } + + return interp.pkgDir(goPath, prevRoot, importPath) +} + +const vendor = "vendor" + +// Find the previous source root (vendor > vendor > ... > GOPATH). +func previousRoot(filesystem fs.FS, rootPath, root string) (string, error) { + rootPath = filepath.Clean(rootPath) + parent, final := filepath.Split(rootPath) + parent = filepath.Clean(parent) + + // TODO(mpl): maybe it works for the special case main, but can't be bothered for now. + if root != mainID && final != vendor { + root = strings.TrimSuffix(root, string(filepath.Separator)) + prefix := strings.TrimSuffix(strings.TrimSuffix(rootPath, root), string(filepath.Separator)) + + // look for the closest vendor in one of our direct ancestors, as it takes priority. + var vendored string + for { + fi, err := fs.Stat(filesystem, filepath.Join(parent, vendor)) + if err == nil && fi.IsDir() { + vendored = strings.TrimPrefix(strings.TrimPrefix(parent, prefix), string(filepath.Separator)) + break + } + if !errors.Is(err, fs.ErrNotExist) { + return "", err + } + // stop when we reach GOPATH/src + if parent == prefix { + break + } + + // stop when we reach GOPATH/src/blah + parent = filepath.Dir(parent) + if parent == prefix { + break + } + + // just an additional failsafe, stop if we reach the filesystem root, or dot (if + // we are dealing with relative paths). + // TODO(mpl): It should probably be a critical error actually, + // as we shouldn't have gone that high up in the tree. + // TODO(dennwc): This partially fails on Windows, since it cannot recognize drive letters as "root". + if parent == string(filepath.Separator) || parent == "." || parent == "" { + break + } + } + + if vendored != "" { + return vendored, nil + } + } + + // TODO(mpl): the algorithm below might be redundant with the one above, + // but keeping it for now. Investigate/simplify/remove later. + splitRoot := strings.Split(root, string(filepath.Separator)) + var index int + for i := len(splitRoot) - 1; i >= 0; i-- { + if splitRoot[i] == "vendor" { + index = i + break + } + } + + if index == 0 { + return "", nil + } + + return filepath.Join(splitRoot[:index]...), nil +} + +func effectivePkg(root, path string) string { + splitRoot := strings.Split(root, string(filepath.Separator)) + splitPath := strings.Split(path, string(filepath.Separator)) + + var result []string + + rootIndex := 0 + prevRootIndex := 0 + for i := 0; i < len(splitPath); i++ { + part := splitPath[len(splitPath)-1-i] + + index := len(splitRoot) - 1 - rootIndex + if index > 0 && part == splitRoot[index] && i != 0 { + prevRootIndex = rootIndex + rootIndex++ + } else if prevRootIndex == rootIndex { + result = append(result, part) + } + } + + var frag string + for i := len(result) - 1; i >= 0; i-- { + frag = filepath.Join(frag, result[i]) + } + + return filepath.Join(root, frag) +} + +// isPathRelative returns true if path starts with "./" or "../". +// It is intended for use on import paths, where "/" is always the directory separator. +func isPathRelative(s string) bool { + return strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") +} diff --git a/internal/yaegi/interp/type.go b/internal/yaegi/interp/type.go new file mode 100644 index 00000000..be18a981 --- /dev/null +++ b/internal/yaegi/interp/type.go @@ -0,0 +1,2531 @@ +package interp + +import ( + "fmt" + "go/constant" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/mark3labs/kit/internal/yaegi/unsafe2" +) + +// tcat defines interpreter type categories. +type tcat uint + +// Types for go language. +const ( + nilT tcat = iota + arrayT + binT + binPkgT + boolT + builtinT + chanT + chanSendT + chanRecvT + comparableT + complex64T + complex128T + constraintT + errorT + float32T + float64T + funcT + genericT + interfaceT + intT + int8T + int16T + int32T + int64T + linkedT + mapT + ptrT + sliceT + srcPkgT + stringT + structT + uintT + uint8T + uint16T + uint32T + uint64T + uintptrT + valueT + variadicT + maxT +) + +var cats = [...]string{ + nilT: "nilT", + arrayT: "arrayT", + binT: "binT", + binPkgT: "binPkgT", + boolT: "boolT", + builtinT: "builtinT", + chanT: "chanT", + comparableT: "comparableT", + complex64T: "complex64T", + complex128T: "complex128T", + constraintT: "constraintT", + errorT: "errorT", + float32T: "float32", + float64T: "float64T", + funcT: "funcT", + genericT: "genericT", + interfaceT: "interfaceT", + intT: "intT", + int8T: "int8T", + int16T: "int16T", + int32T: "int32T", + int64T: "int64T", + linkedT: "linkedT", + mapT: "mapT", + ptrT: "ptrT", + sliceT: "sliceT", + srcPkgT: "srcPkgT", + stringT: "stringT", + structT: "structT", + uintT: "uintT", + uint8T: "uint8T", + uint16T: "uint16T", + uint32T: "uint32T", + uint64T: "uint64T", + uintptrT: "uintptrT", + valueT: "valueT", + variadicT: "variadicT", +} + +func (c tcat) String() string { + if c < tcat(len(cats)) { + return cats[c] + } + return "Cat(" + strconv.Itoa(int(c)) + ")" +} + +// structField type defines a field in a struct. +type structField struct { + name string + tag string + embed bool + typ *itype +} + +// itype defines the internal representation of types in the interpreter. +type itype struct { + cat tcat // Type category + field []structField // Array of struct fields if structT or interfaceT + key *itype // Type of key element if MapT or nil + val *itype // Type of value element if chanT, chanSendT, chanRecvT, mapT, ptrT, linkedT, arrayT, sliceT, variadicT or genericT + recv *itype // Receiver type for funcT or nil + arg []*itype // Argument types if funcT or nil + ret []*itype // Return types if funcT or nil + ptr *itype // Pointer to this type. Might be nil + method []*node // Associated methods or nil + constraint []*itype // For interfaceT: list of types part of interface set + ulconstraint []*itype // For interfaceT: list of underlying types part of interface set + instance []*itype // For genericT: list of instantiated types + name string // name of type within its package for a defined type + path string // for a defined type, the package import path + length int // length of array if ArrayT + rtype reflect.Type // Reflection type if ValueT, or nil + node *node // root AST node of type definition + scope *scope // type declaration scope (in case of re-parse incomplete type) + str string // String representation of the type + incomplete bool // true if type must be parsed again (out of order declarations) + untyped bool // true for a literal value (string or number) + isBinMethod bool // true if the type refers to a bin method function +} + +type generic struct{} + +func untypedBool(n *node) *itype { + return &itype{cat: boolT, name: "bool", untyped: true, str: "untyped bool", node: n} +} + +func untypedString(n *node) *itype { + return &itype{cat: stringT, name: "string", untyped: true, str: "untyped string", node: n} +} + +func untypedRune(n *node) *itype { + return &itype{cat: int32T, name: "int32", untyped: true, str: "untyped rune", node: n} +} + +func untypedInt(n *node) *itype { + return &itype{cat: intT, name: "int", untyped: true, str: "untyped int", node: n} +} + +func untypedFloat(n *node) *itype { + return &itype{cat: float64T, name: "float64", untyped: true, str: "untyped float", node: n} +} + +func untypedComplex(n *node) *itype { + return &itype{cat: complex128T, name: "complex128", untyped: true, str: "untyped complex", node: n} +} + +func errorMethodType(sc *scope) *itype { + return &itype{cat: funcT, ret: []*itype{sc.getType("string")}, str: "func() string"} +} + +type itypeOption func(*itype) + +func isBinMethod() itypeOption { + return func(t *itype) { + t.isBinMethod = true + } +} + +func withRecv(typ *itype) itypeOption { + return func(t *itype) { + t.recv = typ + } +} + +func withNode(n *node) itypeOption { + return func(t *itype) { + t.node = n + } +} + +func withScope(sc *scope) itypeOption { + return func(t *itype) { + t.scope = sc + } +} + +func withUntyped(b bool) itypeOption { + return func(t *itype) { + t.untyped = b + } +} + +// valueTOf returns a valueT itype. +func valueTOf(rtype reflect.Type, opts ...itypeOption) *itype { + t := &itype{cat: valueT, rtype: rtype, str: rtype.String()} + for _, opt := range opts { + opt(t) + } + if t.untyped { + t.str = "untyped " + t.str + } + return t +} + +// wrapperValueTOf returns a valueT itype wrapping an itype. +func wrapperValueTOf(rtype reflect.Type, val *itype, opts ...itypeOption) *itype { + t := &itype{cat: valueT, rtype: rtype, val: val, str: rtype.String()} + for _, opt := range opts { + opt(t) + } + return t +} + +func variadicOf(val *itype, opts ...itypeOption) *itype { + t := &itype{cat: variadicT, val: val, str: "..." + val.str} + for _, opt := range opts { + opt(t) + } + return t +} + +// ptrOf returns a pointer to t. +func ptrOf(val *itype, opts ...itypeOption) *itype { + if val.ptr != nil { + return val.ptr + } + t := &itype{cat: ptrT, val: val, str: "*" + val.str} + for _, opt := range opts { + opt(t) + } + val.ptr = t + return t +} + +// namedOf returns a named type of val. +func namedOf(val *itype, path, name string, opts ...itypeOption) *itype { + str := name + if path != "" { + str = path + "." + name + } + t := &itype{cat: linkedT, val: val, path: path, name: name, str: str} + for _, opt := range opts { + opt(t) + } + return t +} + +// funcOf returns a function type with the given args and returns. +func funcOf(args []*itype, ret []*itype, opts ...itypeOption) *itype { + b := []byte{} + b = append(b, "func("...) + b = append(b, paramsTypeString(args)...) + b = append(b, ')') + if len(ret) != 0 { + b = append(b, ' ') + if len(ret) > 1 { + b = append(b, '(') + } + b = append(b, paramsTypeString(ret)...) + if len(ret) > 1 { + b = append(b, ')') + } + } + + t := &itype{cat: funcT, arg: args, ret: ret, str: string(b)} + for _, opt := range opts { + opt(t) + } + return t +} + +type chanDir uint8 + +const ( + chanSendRecv chanDir = iota + chanSend + chanRecv +) + +// chanOf returns a channel of the underlying type val. +func chanOf(val *itype, dir chanDir, opts ...itypeOption) *itype { + cat := chanT + str := "chan " + switch dir { + case chanSend: + cat = chanSendT + str = "chan<- " + case chanRecv: + cat = chanRecvT + str = "<-chan " + } + t := &itype{cat: cat, val: val, str: str + val.str} + for _, opt := range opts { + opt(t) + } + return t +} + +// arrayOf returns am array type of the underlying val with the given length. +func arrayOf(val *itype, l int, opts ...itypeOption) *itype { + lstr := strconv.Itoa(l) + t := &itype{cat: arrayT, val: val, length: l, str: "[" + lstr + "]" + val.str} + for _, opt := range opts { + opt(t) + } + return t +} + +// sliceOf returns a slice type of the underlying val. +func sliceOf(val *itype, opts ...itypeOption) *itype { + t := &itype{cat: sliceT, val: val, str: "[]" + val.str} + for _, opt := range opts { + opt(t) + } + return t +} + +// mapOf returns a map type of the underlying key and val. +func mapOf(key, val *itype, opts ...itypeOption) *itype { + t := &itype{cat: mapT, key: key, val: val, str: "map[" + key.str + "]" + val.str} + for _, opt := range opts { + opt(t) + } + return t +} + +// interfaceOf returns an interface type with the given fields. +func interfaceOf(t *itype, fields []structField, constraint, ulconstraint []*itype, opts ...itypeOption) *itype { + str := "interface{}" + if len(fields) > 0 { + str = "interface { " + methodsTypeString(fields) + "}" + } + if t == nil { + t = &itype{} + } + t.cat = interfaceT + t.field = fields + t.constraint = constraint + t.ulconstraint = ulconstraint + t.str = str + for _, opt := range opts { + opt(t) + } + return t +} + +// structOf returns a struct type with the given fields. +func structOf(t *itype, fields []structField, opts ...itypeOption) *itype { + str := "struct {}" + if len(fields) > 0 { + str = "struct { " + fieldsTypeString(fields) + "}" + } + if t == nil { + t = &itype{} + } + t.cat = structT + t.field = fields + t.str = str + for _, opt := range opts { + opt(t) + } + return t +} + +// genericOf returns a generic type. +func genericOf(val *itype, name, path string, opts ...itypeOption) *itype { + t := &itype{cat: genericT, name: name, path: path, str: name, val: val} + for _, opt := range opts { + opt(t) + } + return t +} + +// seenNode determines if a node has been seen. +// +// seenNode treats the slice of nodes as the path traveled down a node +// tree. +func seenNode(ns []*node, n *node) bool { + for _, nn := range ns { + if nn == n { + return true + } + } + return false +} + +// nodeType returns a type definition for the corresponding AST subtree. +func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) { + return nodeType2(interp, sc, n, nil) +} + +func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, err error) { + if n.typ != nil && !n.typ.incomplete { + return n.typ, nil + } + if sname := typeName(n); sname != "" { + sym, _, found := sc.lookup(sname) + if found && sym.kind == typeSym && sym.typ != nil { + if sym.typ.isComplete() { + return sym.typ, nil + } + if seenNode(seen, n) { + // We have seen this node in our tree, so it must be recursive. + sym.typ.incomplete = false + return sym.typ, nil + } + } + } + seen = append(seen, n) + defer func() { seen = seen[:len(seen)-1] }() + + switch n.kind { + case addressExpr, starExpr: + val, err := nodeType2(interp, sc, n.child[0], seen) + if err != nil { + return nil, err + } + t = ptrOf(val, withNode(n), withScope(sc)) + t.incomplete = val.incomplete + + case arrayType: + c0 := n.child[0] + if len(n.child) == 1 { + val, err := nodeType2(interp, sc, c0, seen) + if err != nil { + return nil, err + } + t = sliceOf(val, withNode(n), withScope(sc)) + t.incomplete = val.incomplete + break + } + // Array size is defined. + var ( + length int + incomplete bool + ) + switch v := c0.rval; { + case v.IsValid(): + // Size if defined by a constant literal value. + if isConstantValue(v.Type()) { + c := v.Interface().(constant.Value) + length = constToInt(c) + } else { + switch v.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + length = int(v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + length = int(v.Uint()) + default: + return nil, c0.cfgErrorf("non integer constant %v", v) + } + } + case c0.kind == ellipsisExpr: + // [...]T expression, get size from the length of composite array. + length, err = arrayTypeLen(n.anc, sc) + if err != nil { + incomplete = true + } + case c0.kind == identExpr: + sym, _, ok := sc.lookup(c0.ident) + if !ok { + incomplete = true + break + } + // Size is defined by a symbol which must be a constant integer. + if sym.kind != constSym { + return nil, c0.cfgErrorf("non-constant array bound %q", c0.ident) + } + if sym.typ == nil || !isInt(sym.typ.TypeOf()) || !sym.rval.IsValid() { + incomplete = true + break + } + length = int(vInt(sym.rval)) + default: + // Size is defined by a numeric constant expression. + var ok bool + if _, err := interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil { + if strings.Contains(err.Error(), " undefined: ") { + incomplete = true + break + } + return nil, err + } + if !c0.rval.IsValid() { + return nil, c0.cfgErrorf("undefined array size") + } + if length, ok = c0.rval.Interface().(int); !ok { + v, ok := c0.rval.Interface().(constant.Value) + if !ok { + incomplete = true + break + } + length = constToInt(v) + } + } + val, err := nodeType2(interp, sc, n.child[1], seen) + if err != nil { + return nil, err + } + t = arrayOf(val, length, withNode(n), withScope(sc)) + t.incomplete = incomplete || val.incomplete + + case basicLit: + switch v := n.rval.Interface().(type) { + case bool: + n.rval = reflect.ValueOf(constant.MakeBool(v)) + t = untypedBool(n) + case rune: + // It is impossible to work out rune const literals in AST + // with the correct type so we must make the const type here. + n.rval = reflect.ValueOf(constant.MakeInt64(int64(v))) + t = untypedRune(n) + case constant.Value: + switch v.Kind() { + case constant.Bool: + t = untypedBool(n) + case constant.String: + t = untypedString(n) + case constant.Int: + t = untypedInt(n) + case constant.Float: + t = untypedFloat(n) + case constant.Complex: + t = untypedComplex(n) + default: + err = n.cfgErrorf("missing support for type %v", n.rval) + } + default: + err = n.cfgErrorf("missing support for type %T: %v", v, n.rval) + } + + case unaryExpr: + // In interfaceType, we process an underlying type constraint definition. + if isInInterfaceType(n) { + t1, err := nodeType2(interp, sc, n.child[0], seen) + if err != nil { + return nil, err + } + t = &itype{cat: constraintT, ulconstraint: []*itype{t1}} + break + } + t, err = nodeType2(interp, sc, n.child[0], seen) + + case binaryExpr: + // In interfaceType, we process a type constraint union definition. + if isInInterfaceType(n) { + t = &itype{cat: constraintT, constraint: []*itype{}, ulconstraint: []*itype{}} + for _, c := range n.child { + t1, err := nodeType2(interp, sc, c, seen) + if err != nil { + return nil, err + } + switch t1.cat { + case constraintT: + t.constraint = append(t.constraint, t1.constraint...) + t.ulconstraint = append(t.ulconstraint, t1.ulconstraint...) + default: + t.constraint = append(t.constraint, t1) + } + } + break + } + // Get type of first operand. + if t, err = nodeType2(interp, sc, n.child[0], seen); err != nil { + return nil, err + } + // For operators other than shift, get the type from the 2nd operand if the first is untyped. + if t.untyped && !isShiftNode(n) { + var t1 *itype + t1, err = nodeType2(interp, sc, n.child[1], seen) + if !(t1.untyped && isInt(t1.TypeOf()) && isFloat(t.TypeOf())) { + t = t1 + } + } + + // If the node is to be assigned or returned, the node type is the destination type. + dt := t + + switch a := n.anc; { + case a.kind == assignStmt && isEmptyInterface(a.child[0].typ): + // Because an empty interface concrete type "mutates" as different values are + // assigned to it, we need to make a new itype from scratch everytime a new + // assignment is made, and not let different nodes (of the same variable) share the + // same itype. Otherwise they would overwrite each other. + a.child[0].typ = &itype{cat: interfaceT, val: dt, str: "interface{}"} + + case a.kind == defineStmt && len(a.child) > a.nleft+a.nright: + if dt, err = nodeType2(interp, sc, a.child[a.nleft], seen); err != nil { + return nil, err + } + + case a.kind == returnStmt: + dt = sc.def.typ.ret[childPos(n)] + } + + if isInterfaceSrc(dt) { + // Set a new interface type preserving the concrete type (.val field). + t2 := *dt + t2.val = t + dt = &t2 + } + t = dt + + case callExpr: + if isBuiltinCall(n, sc) { + // Builtin types are special and may depend from their input arguments. + switch n.child[0].ident { + case bltnComplex: + var nt0, nt1 *itype + if nt0, err = nodeType2(interp, sc, n.child[1], seen); err != nil { + return nil, err + } + if nt1, err = nodeType2(interp, sc, n.child[2], seen); err != nil { + return nil, err + } + if nt0.incomplete || nt1.incomplete { + t.incomplete = true + } else { + switch t0, t1 := nt0.TypeOf(), nt1.TypeOf(); { + case isFloat32(t0) && isFloat32(t1): + t = sc.getType("complex64") + case isFloat64(t0) && isFloat64(t1): + t = sc.getType("complex128") + case nt0.untyped && isNumber(t0) && nt1.untyped && isNumber(t1): + t = untypedComplex(n) + case nt0.untyped && isFloat32(t1) || nt1.untyped && isFloat32(t0): + t = sc.getType("complex64") + case nt0.untyped && isFloat64(t1) || nt1.untyped && isFloat64(t0): + t = sc.getType("complex128") + default: + err = n.cfgErrorf("invalid types %s and %s", t0.Kind(), t1.Kind()) + } + if nt0.untyped && nt1.untyped { + t = untypedComplex(n) + } + } + case bltnReal, bltnImag: + if t, err = nodeType2(interp, sc, n.child[1], seen); err != nil { + return nil, err + } + if !t.incomplete { + switch k := t.TypeOf().Kind(); { + case t.untyped && isNumber(t.TypeOf()): + t = untypedFloat(n) + case k == reflect.Complex64: + t = sc.getType("float32") + case k == reflect.Complex128: + t = sc.getType("float64") + default: + err = n.cfgErrorf("invalid complex type %s", k) + } + } + case bltnCap, bltnCopy, bltnLen: + t = sc.getType("int") + case bltnAppend, bltnMake: + t, err = nodeType2(interp, sc, n.child[1], seen) + case bltnNew: + t, err = nodeType2(interp, sc, n.child[1], seen) + incomplete := t.incomplete + t = ptrOf(t, withScope(sc)) + t.incomplete = incomplete + case bltnRecover: + t = sc.getType("interface{}") + default: + t = &itype{cat: builtinT} + } + if err != nil { + return nil, err + } + } else { + if t, err = nodeType2(interp, sc, n.child[0], seen); err != nil || t == nil { + return nil, err + } + switch t.cat { + case valueT: + if rt := t.rtype; rt.Kind() == reflect.Func && rt.NumOut() == 1 { + t = valueTOf(rt.Out(0), withScope(sc)) + } + default: + if len(t.ret) == 1 { + t = t.ret[0] + } + } + } + + case compositeLitExpr: + t, err = nodeType2(interp, sc, n.child[0], seen) + + case chanType, chanTypeRecv, chanTypeSend: + dir := chanSendRecv + switch n.kind { + case chanTypeRecv: + dir = chanRecv + case chanTypeSend: + dir = chanSend + } + val, err := nodeType2(interp, sc, n.child[0], seen) + if err != nil { + return nil, err + } + t = chanOf(val, dir, withNode(n), withScope(sc)) + t.incomplete = val.incomplete + + case ellipsisExpr: + val, err := nodeType2(interp, sc, n.child[0], seen) + if err != nil { + return nil, err + } + t = variadicOf(val, withNode(n), withScope(sc)) + t.incomplete = t.val.incomplete + + case funcLit: + t, err = nodeType2(interp, sc, n.child[2], seen) + + case funcType: + var incomplete bool + + // Handle type parameters. + for _, arg := range n.child[0].child { + cl := len(arg.child) - 1 + typ, err := nodeType2(interp, sc, arg.child[cl], seen) + if err != nil { + return nil, err + } + for _, c := range arg.child[:cl] { + sc.sym[c.ident] = &symbol{index: -1, kind: varTypeSym, typ: typ} + } + incomplete = incomplete || typ.incomplete + } + + // Handle input parameters. + args := make([]*itype, 0, len(n.child[1].child)) + for _, arg := range n.child[1].child { + cl := len(arg.child) - 1 + typ, err := nodeType2(interp, sc, arg.child[cl], seen) + if err != nil { + return nil, err + } + args = append(args, typ) + // Several arguments may be factorized on the same field type. + for i := 1; i < cl; i++ { + args = append(args, typ) + } + incomplete = incomplete || typ.incomplete + } + + // Handle returned values. + var rets []*itype + if len(n.child) == 3 { + for _, ret := range n.child[2].child { + cl := len(ret.child) - 1 + typ, err := nodeType2(interp, sc, ret.child[cl], seen) + if err != nil { + return nil, err + } + rets = append(rets, typ) + // Several arguments may be factorized on the same field type. + for i := 1; i < cl; i++ { + rets = append(rets, typ) + } + incomplete = incomplete || typ.incomplete + } + } + t = funcOf(args, rets, withNode(n), withScope(sc)) + t.incomplete = incomplete + + case identExpr: + sym, _, found := sc.lookup(n.ident) + if !found { + // retry with the filename, in case ident is a package name. + baseName := filepath.Base(interp.fset.Position(n.pos).Filename) + ident := filepath.Join(n.ident, baseName) + sym, _, found = sc.lookup(ident) + if !found { + t = &itype{name: n.ident, path: sc.pkgName, node: n, incomplete: true, scope: sc} + sc.sym[n.ident] = &symbol{kind: typeSym, typ: t} + break + } + } + if sym.kind == varTypeSym { + t = genericOf(sym.typ, n.ident, sc.pkgName, withNode(n), withScope(sc)) + } else { + t = sym.typ + } + if t == nil { + if t, err = nodeType2(interp, sc, sym.node, seen); err != nil { + return nil, err + } + } + if t.incomplete && t.cat == linkedT && t.val != nil && t.val.cat != nilT { + t.incomplete = false + } + if t.incomplete && t.node != n { + m := t.method + if t, err = nodeType2(interp, sc, t.node, seen); err != nil { + return nil, err + } + t.method = m + sym.typ = t + } + if t.node == nil { + t.node = n + } + + case indexExpr: + var lt *itype + if lt, err = nodeType2(interp, sc, n.child[0], seen); err != nil { + return nil, err + } + if lt.incomplete { + if t == nil { + t = lt + } else { + t.incomplete = true + } + break + } + switch lt.cat { + case arrayT, mapT, sliceT, variadicT: + t = lt.val + case genericT: + t1, err := nodeType2(interp, sc, n.child[1], seen) + if err != nil { + return nil, err + } + if t1.cat == genericT || t1.incomplete { + t = lt + break + } + name := lt.id() + "[" + t1.id() + "]" + if sym, _, found := sc.lookup(name); found { + t = sym.typ + break + } + // A generic type is being instantiated. Generate it. + t, err = genType(interp, sc, name, lt, []*itype{t1}, seen) + if err != nil { + return nil, err + } + } + + case indexListExpr: + // Similar to above indexExpr for generic types, but handle multiple type parameters. + var lt *itype + if lt, err = nodeType2(interp, sc, n.child[0], seen); err != nil { + return nil, err + } + if lt.incomplete { + if t == nil { + t = lt + } else { + t.incomplete = true + } + break + } + + // Index list expressions can be used only in context of generic types. + if lt.cat != genericT { + err = n.cfgErrorf("not a generic type: %s", lt.id()) + return nil, err + } + name := lt.id() + "[" + out := false + types := []*itype{} + for _, c := range n.child[1:] { + t1, err := nodeType2(interp, sc, c, seen) + if err != nil { + return nil, err + } + if t1.cat == genericT || t1.incomplete { + t = lt + out = true + break + } + types = append(types, t1) + name += t1.id() + "," + } + if out { + break + } + name = strings.TrimSuffix(name, ",") + "]" + if sym, _, found := sc.lookup(name); found { + t = sym.typ + break + } + // A generic type is being instantiated. Generate it. + t, err = genType(interp, sc, name, lt, types, seen) + + case interfaceType: + if sname := typeName(n); sname != "" { + if sym, _, found := sc.lookup(sname); found && sym.kind == typeSym { + t = interfaceOf(sym.typ, sym.typ.field, sym.typ.constraint, sym.typ.ulconstraint, withNode(n), withScope(sc)) + } + } + var incomplete bool + fields := []structField{} + constraint := []*itype{} + ulconstraint := []*itype{} + for _, c := range n.child[0].child { + c0 := c.child[0] + if len(c.child) == 1 { + if c0.ident == "error" { + // Unwrap error interface inplace rather than embedding it, because + // "error" is lower case which may cause problems with reflect for method lookup. + typ := errorMethodType(sc) + fields = append(fields, structField{name: "Error", typ: typ}) + continue + } + typ, err := nodeType2(interp, sc, c0, seen) + if err != nil { + return nil, err + } + incomplete = incomplete || typ.incomplete + if typ.cat == constraintT { + constraint = append(constraint, typ.constraint...) + ulconstraint = append(ulconstraint, typ.ulconstraint...) + continue + } + fields = append(fields, structField{name: fieldName(c0), embed: true, typ: typ}) + continue + } + typ, err := nodeType2(interp, sc, c.child[1], seen) + if err != nil { + return nil, err + } + fields = append(fields, structField{name: c0.ident, typ: typ}) + incomplete = incomplete || typ.incomplete + } + t = interfaceOf(t, fields, constraint, ulconstraint, withNode(n), withScope(sc)) + t.incomplete = incomplete + + case landExpr, lorExpr: + t = sc.getType("bool") + + case mapType: + key, err := nodeType2(interp, sc, n.child[0], seen) + if err != nil { + return nil, err + } + val, err := nodeType2(interp, sc, n.child[1], seen) + if err != nil { + return nil, err + } + t = mapOf(key, val, withNode(n), withScope(sc)) + t.incomplete = key.incomplete || val.incomplete + + case parenExpr: + t, err = nodeType2(interp, sc, n.child[0], seen) + + case selectorExpr: + // Resolve the left part of selector, then lookup the right part on it + var lt *itype + + // Lookup the package symbol first if we are in a field expression as + // a previous parameter has the same name as the package, we need to + // prioritize the package type. + if n.anc.kind == fieldExpr { + lt = findPackageType(interp, sc, n.child[0]) + } + if lt == nil { + // No package was found or we are not in a field expression, we are looking for a variable. + if lt, err = nodeType2(interp, sc, n.child[0], seen); err != nil { + return nil, err + } + } + + if lt.incomplete { + break + } + name := n.child[1].ident + switch lt.cat { + case binPkgT: + pkg := interp.binPkg[lt.path] + if v, ok := pkg[name]; ok { + rtype := v.Type() + if isBinType(v) { + // A bin type is encoded as a pointer on a typed nil value. + rtype = rtype.Elem() + } + t = valueTOf(rtype, withNode(n), withScope(sc)) + break + } + // Continue search in source package, as it may exist if package contains generics. + fallthrough + case srcPkgT: + if pkg, ok := interp.srcPkg[lt.path]; ok { + if s, ok := pkg[name]; ok { + t = s.typ + break + } + } + err = n.cfgErrorf("undefined selector %s.%s", lt.path, name) + default: + if m, _ := lt.lookupMethod(name); m != nil { + t, err = nodeType2(interp, sc, m.child[2], seen) + } else if bm, _, _, ok := lt.lookupBinMethod(name); ok { + t = valueTOf(bm.Type, isBinMethod(), withRecv(lt), withScope(sc)) + } else if ti := lt.lookupField(name); len(ti) > 0 { + t = lt.fieldSeq(ti) + } else if bs, _, ok := lt.lookupBinField(name); ok { + t = valueTOf(bs.Type, withScope(sc)) + } else { + err = lt.node.cfgErrorf("undefined selector %s", name) + } + } + + case sliceExpr: + t, err = nodeType2(interp, sc, n.child[0], seen) + if err != nil { + return nil, err + } + + if t.cat == valueT { + switch t.rtype.Kind() { + case reflect.Array, reflect.Ptr: + t = valueTOf(reflect.SliceOf(t.rtype.Elem()), withScope(sc)) + } + break + } + if t.cat == ptrT { + t = t.val + } + if t.cat == arrayT { + incomplete := t.incomplete + t = sliceOf(t.val, withNode(n), withScope(sc)) + t.incomplete = incomplete + } + + case structType: + var sym *symbol + var found bool + sname := structName(n) + if sname != "" { + sym, _, found = sc.lookup(sname) + if found && sym.kind == typeSym && sym.typ != nil { + t = structOf(sym.typ, sym.typ.field, withNode(n), withScope(sc)) + } else { + t = structOf(nil, nil, withNode(n), withScope(sc)) + sc.sym[sname] = &symbol{index: -1, kind: typeSym, typ: t, node: n} + } + } + var incomplete bool + fields := make([]structField, 0, len(n.child[0].child)) + for _, c := range n.child[0].child { + switch { + case len(c.child) == 1: + typ, err := nodeType2(interp, sc, c.child[0], seen) + if err != nil { + return nil, err + } + fields = append(fields, structField{name: fieldName(c.child[0]), embed: true, typ: typ}) + incomplete = incomplete || typ.incomplete + case len(c.child) == 2 && c.child[1].kind == basicLit: + tag := vString(c.child[1].rval) + typ, err := nodeType2(interp, sc, c.child[0], seen) + if err != nil { + return nil, err + } + fields = append(fields, structField{name: fieldName(c.child[0]), embed: true, typ: typ, tag: tag}) + incomplete = incomplete || typ.incomplete + default: + var tag string + l := len(c.child) + if c.lastChild().kind == basicLit { + tag = vString(c.lastChild().rval) + l-- + } + typ, err := nodeType2(interp, sc, c.child[l-1], seen) + if err != nil { + return nil, err + } + incomplete = incomplete || typ.incomplete + for _, d := range c.child[:l-1] { + fields = append(fields, structField{name: d.ident, typ: typ, tag: tag}) + } + } + } + t = structOf(t, fields, withNode(n), withScope(sc)) + t.incomplete = incomplete + if sname != "" { + if sc.sym[sname] == nil { + sc.sym[sname] = &symbol{index: -1, kind: typeSym, node: n} + } + sc.sym[sname].typ = t + } + + case typeAssertExpr: + t, err = nodeType2(interp, sc, n.child[1], seen) + + default: + err = n.cfgErrorf("type definition not implemented: %s", n.kind) + } + + if err == nil && t != nil && t.cat == nilT && !t.incomplete { + err = n.cfgErrorf("use of untyped nil %s", t.name) + } + + // The existing symbol data needs to be recovered, but not in the + // case where we are aliasing another type. + if n.anc.kind == typeSpec && n.kind != selectorExpr && n.kind != identExpr { + name := n.anc.child[0].ident + if sym := sc.sym[name]; sym != nil { + t.path = sc.pkgName + t.name = name + } + } + + switch { + case t == nil: + case t.name != "" && t.path != "": + t.str = t.path + "." + t.name + case t.cat == nilT: + t.str = "nil" + } + + return t, err +} + +func genType(interp *Interpreter, sc *scope, name string, lt *itype, types []*itype, seen []*node) (t *itype, err error) { + // A generic type is being instantiated. Generate it. + g, _, err := genAST(sc, lt.node.anc, types) + if err != nil { + return nil, err + } + t, err = nodeType2(interp, sc, g.lastChild(), seen) + if err != nil { + return nil, err + } + lt.instance = append(lt.instance, t) + // Add generated symbol in the scope of generic source and user. + sc.sym[name] = &symbol{index: -1, kind: typeSym, typ: t, node: g} + if lt.scope.sym[name] == nil { + lt.scope.sym[name] = sc.sym[name] + } + + for _, nod := range lt.method { + if err := genMethod(interp, sc, t, nod, types); err != nil { + return nil, err + } + } + return t, err +} + +func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, types []*itype) error { + gm, _, err := genAST(sc, nod, types) + if err != nil { + return err + } + if gm.typ, err = nodeType(interp, sc, gm.child[2]); err != nil { + return err + } + t.addMethod(gm) + + // If the receiver is a pointer to a generic type, generate also the pointer type. + if rtn := gm.child[0].child[0].lastChild(); rtn != nil && rtn.kind == starExpr { + pt := ptrOf(t, withNode(t.node), withScope(sc)) + pt.addMethod(gm) + rtn.typ = pt + } + + // Compile the method AST in the scope of the generic type. + scop := nod.typ.scope + if _, err = interp.cfg(gm, scop, scop.pkgID, scop.pkgName); err != nil { + return err + } + + // Generate closures for function body. + return genRun(gm) +} + +// findPackageType searches the top level scope for a package type. +func findPackageType(interp *Interpreter, sc *scope, n *node) *itype { + // Find the root scope, the package symbols will exist there. + for { + if sc.level == 0 { + break + } + sc = sc.anc + } + + baseName := filepath.Base(interp.fset.Position(n.pos).Filename) + sym, _, found := sc.lookup(filepath.Join(n.ident, baseName)) + if !found || sym.typ == nil && sym.typ.cat != srcPkgT && sym.typ.cat != binPkgT { + return nil + } + return sym.typ +} + +func isBuiltinCall(n *node, sc *scope) bool { + if n.kind != callExpr { + return false + } + s := n.child[0].sym + if s == nil { + if sym, _, found := sc.lookup(n.child[0].ident); found { + s = sym + } + } + return s != nil && s.kind == bltnSym +} + +// struct name returns the name of a struct type. +func typeName(n *node) string { + if n.anc.kind == typeSpec && len(n.anc.child) == 2 { + return n.anc.child[0].ident + } + return "" +} + +func structName(n *node) string { + if n.anc.kind == typeSpec { + return n.anc.child[0].ident + } + return "" +} + +// fieldName returns an implicit struct field name according to node kind. +func fieldName(n *node) string { + switch n.kind { + case selectorExpr: + return fieldName(n.child[1]) + case starExpr: + return fieldName(n.child[0]) + case indexExpr: + return fieldName(n.child[0]) + case identExpr: + return n.ident + default: + return "" + } +} + +var zeroValues [maxT]reflect.Value + +func init() { + zeroValues[boolT] = reflect.ValueOf(false) + zeroValues[complex64T] = reflect.ValueOf(complex64(0)) + zeroValues[complex128T] = reflect.ValueOf(complex128(0)) + zeroValues[errorT] = reflect.ValueOf(new(error)).Elem() + zeroValues[float32T] = reflect.ValueOf(float32(0)) + zeroValues[float64T] = reflect.ValueOf(float64(0)) + zeroValues[intT] = reflect.ValueOf(int(0)) + zeroValues[int8T] = reflect.ValueOf(int8(0)) + zeroValues[int16T] = reflect.ValueOf(int16(0)) + zeroValues[int32T] = reflect.ValueOf(int32(0)) + zeroValues[int64T] = reflect.ValueOf(int64(0)) + zeroValues[stringT] = reflect.ValueOf("") + zeroValues[uintT] = reflect.ValueOf(uint(0)) + zeroValues[uint8T] = reflect.ValueOf(uint8(0)) + zeroValues[uint16T] = reflect.ValueOf(uint16(0)) + zeroValues[uint32T] = reflect.ValueOf(uint32(0)) + zeroValues[uint64T] = reflect.ValueOf(uint64(0)) + zeroValues[uintptrT] = reflect.ValueOf(uintptr(0)) +} + +// Finalize returns a type pointer and error. It reparses a type from the +// partial AST if necessary (after missing dependecy data is available). +// If error is nil, the type is guarranteed to be completely defined and +// usable for CFG. +func (t *itype) finalize() (*itype, error) { + var err error + if t.incomplete { + sym, _, found := t.scope.lookup(t.name) + if found && !sym.typ.incomplete { + sym.typ.method = append(sym.typ.method, t.method...) + t.method = sym.typ.method + t.incomplete = false + return sym.typ, nil + } + m := t.method + if t, err = nodeType(t.node.interp, t.scope, t.node); err != nil { + return nil, err + } + if t.incomplete { + return nil, t.node.cfgErrorf("incomplete type %s", t.name) + } + t.method = m + t.node.typ = t + if sym != nil { + sym.typ = t + } + } + return t, err +} + +func (t *itype) addMethod(n *node) { + for _, m := range t.method { + if m == n { + return + } + } + t.method = append(t.method, n) +} + +func (t *itype) numIn() int { + switch t.cat { + case funcT: + return len(t.arg) + case valueT: + if t.rtype.Kind() != reflect.Func { + return 0 + } + in := t.rtype.NumIn() + if t.recv != nil { + in-- + } + return in + } + return 0 +} + +func (t *itype) in(i int) *itype { + switch t.cat { + case funcT: + return t.arg[i] + case valueT: + if t.rtype.Kind() == reflect.Func { + if t.recv != nil && !isInterface(t.recv) { + i++ + } + if t.rtype.IsVariadic() && i == t.rtype.NumIn()-1 { + val := valueTOf(t.rtype.In(i).Elem()) + return &itype{cat: variadicT, val: val, str: "..." + val.str} + } + return valueTOf(t.rtype.In(i)) + } + } + return nil +} + +func (t *itype) numOut() int { + switch t.cat { + case funcT: + return len(t.ret) + case valueT: + if t.rtype.Kind() == reflect.Func { + return t.rtype.NumOut() + } + case builtinT: + switch t.name { + case "append", "cap", "complex", "copy", "imag", "len", "make", "new", "real", "recover", "unsafe.Alignof", "unsafe.Offsetof", "unsafe.Sizeof": + return 1 + } + } + return 0 +} + +func (t *itype) out(i int) *itype { + switch t.cat { + case funcT: + return t.ret[i] + case valueT: + if t.rtype.Kind() == reflect.Func { + return valueTOf(t.rtype.Out(i)) + } + } + return nil +} + +func (t *itype) concrete() *itype { + if isInterface(t) && t.val != nil { + return t.val.concrete() + } + return t +} + +func (t *itype) underlying() *itype { + if t.cat == linkedT { + return t.val.underlying() + } + return t +} + +// typeDefined returns true if type t1 is defined from type t2 or t2 from t1. +func typeDefined(t1, t2 *itype) bool { + if t1.cat == linkedT && t1.val == t2 { + return true + } + if t2.cat == linkedT && t2.val == t1 { + return true + } + return false +} + +// isVariadic returns true if the function type is variadic. +// If the type is not a function or is not variadic, it will +// return false. +func (t *itype) isVariadic() bool { + switch t.cat { + case funcT: + return len(t.arg) > 0 && t.arg[len(t.arg)-1].cat == variadicT + case valueT: + if t.rtype.Kind() == reflect.Func { + return t.rtype.IsVariadic() + } + } + return false +} + +// isComplete returns true if type definition is complete. +func (t *itype) isComplete() bool { return isComplete(t, map[string]bool{}) } + +func isComplete(t *itype, visited map[string]bool) bool { + if t.incomplete { + return false + } + name := t.path + "/" + t.name + if visited[name] { + return true + } + if t.name != "" { + visited[name] = true + } + switch t.cat { + case linkedT: + if t.val != nil && t.val.cat != nilT { + // A type aliased to a partially defined type is considered complete, to allow recursivity. + return true + } + fallthrough + case arrayT, chanT, chanRecvT, chanSendT, ptrT, sliceT, variadicT: + return isComplete(t.val, visited) + case funcT: + complete := true + for _, a := range t.arg { + complete = complete && isComplete(a, visited) + } + for _, a := range t.ret { + complete = complete && isComplete(a, visited) + } + return complete + case interfaceT, structT: + complete := true + for _, f := range t.field { + // Field implicit type names must be marked as visited, to break false circles. + visited[f.typ.path+"/"+f.typ.name] = true + complete = complete && isComplete(f.typ, visited) + } + return complete + case mapT: + return isComplete(t.key, visited) && isComplete(t.val, visited) + case nilT: + return false + } + return true +} + +// comparable returns true if the type is comparable. +func (t *itype) comparable() bool { + typ := t.TypeOf() + return t.cat == nilT || typ != nil && typ.Comparable() +} + +func (t *itype) assignableTo(o *itype) bool { + if t.equals(o) { + return true + } + + if t.cat == linkedT && o.cat == linkedT && (t.underlying().id() != o.underlying().id() || !typeDefined(t, o)) { + return false + } + + if t.isNil() && o.hasNil() || o.isNil() && t.hasNil() { + return true + } + + if t.TypeOf().AssignableTo(o.TypeOf()) { + return true + } + + if isInterface(o) && t.implements(o) { + return true + } + + if t.cat == sliceT && o.cat == sliceT { + return t.val.assignableTo(o.val) + } + + if t.isBinMethod && isFunc(o) { + // TODO (marc): check that t without receiver as first parameter is equivalent to o. + return true + } + + if t.untyped && isNumber(t.TypeOf()) && isNumber(o.TypeOf()) { + // Assignability depends on constant numeric value (overflow check), to be tested elsewhere. + return true + } + + n := t.node + if n == nil || !n.rval.IsValid() { + return false + } + con, ok := n.rval.Interface().(constant.Value) + if !ok { + return false + } + if con == nil || !isConstType(o) { + return false + } + return representableConst(con, o.TypeOf()) +} + +// convertibleTo returns true if t is convertible to o. +func (t *itype) convertibleTo(o *itype) bool { + if t.assignableTo(o) { + return true + } + + // unsafe checks + tt, ot := t.TypeOf(), o.TypeOf() + if (tt.Kind() == reflect.Ptr || tt.Kind() == reflect.Uintptr) && ot.Kind() == reflect.UnsafePointer { + return true + } + if tt.Kind() == reflect.UnsafePointer && (ot.Kind() == reflect.Ptr || ot.Kind() == reflect.Uintptr) { + return true + } + + return t.TypeOf().ConvertibleTo(o.TypeOf()) +} + +// ordered returns true if the type is ordered. +func (t *itype) ordered() bool { + typ := t.TypeOf() + return isInt(typ) || isFloat(typ) || isString(typ) +} + +// equals returns true if the given type is identical to the receiver one. +func (t *itype) equals(o *itype) bool { + switch ti, oi := isInterface(t), isInterface(o); { + case ti && oi: + return t.methods().equals(o.methods()) + case ti && !oi: + return o.methods().contains(t.methods()) + case oi && !ti: + return t.methods().contains(o.methods()) + default: + return t.id() == o.id() + } +} + +// matchDefault returns true if the receiver default type is the same as the given one. +func (t *itype) matchDefault(o *itype) bool { + return t.untyped && t.id() == "untyped "+o.id() +} + +// MethodSet defines the set of methods signatures as strings, indexed per method name. +type methodSet map[string]string + +// Contains returns true if the method set m contains the method set n. +func (m methodSet) contains(n methodSet) bool { + for k := range n { + // Only check the presence of method, not its complete signature, + // as the receiver may be part of the arguments, which makes a + // robust check complex. + if _, ok := m[k]; !ok { + return false + } + } + return true +} + +// Equal returns true if the method set m is equal to the method set n. +func (m methodSet) equals(n methodSet) bool { + return m.contains(n) && n.contains(m) +} + +// Methods returns a map of method type strings, indexed by method names. +func (t *itype) methods() methodSet { + seen := map[*itype]bool{} + var getMethods func(typ *itype) methodSet + + getMethods = func(typ *itype) methodSet { + res := make(methodSet) + + if seen[typ] { + // Stop the recursion, we have seen this type. + return res + } + seen[typ] = true + + switch typ.cat { + case linkedT: + for k, v := range getMethods(typ.val) { + res[k] = v + } + case interfaceT: + // Get methods from recursive analysis of interface fields. + for _, f := range typ.field { + if f.typ.cat == funcT { + res[f.name] = f.typ.TypeOf().String() + } else { + for k, v := range getMethods(f.typ) { + res[k] = v + } + } + } + case valueT, errorT: + // Get method from corresponding reflect.Type. + for i := typ.TypeOf().NumMethod() - 1; i >= 0; i-- { + m := typ.rtype.Method(i) + res[m.Name] = m.Type.String() + } + case ptrT: + if typ.val.cat == valueT { + // Ptr receiver methods need to be found with the ptr type. + typ.TypeOf() // Ensure the rtype exists. + for i := typ.rtype.NumMethod() - 1; i >= 0; i-- { + m := typ.rtype.Method(i) + res[m.Name] = m.Type.String() + } + } + for k, v := range getMethods(typ.val) { + res[k] = v + } + case structT: + for _, f := range typ.field { + if !f.embed { + continue + } + for k, v := range getMethods(f.typ) { + res[k] = v + } + } + } + // Get all methods defined on this type. + for _, m := range typ.method { + res[m.ident] = m.typ.TypeOf().String() + } + return res + } + + return getMethods(t) +} + +// id returns a unique type identificator string. +func (t *itype) id() (res string) { + // Prefer the wrapped type string over the rtype string. + if t.cat == valueT && t.val != nil { + return t.val.str + } + return t.str +} + +// fixPossibleConstType returns the input type if it not a constant value, +// otherwise, it returns the default Go type corresponding to the +// constant.Value. +func fixPossibleConstType(t reflect.Type) (r reflect.Type) { + cv, ok := reflect.New(t).Elem().Interface().(constant.Value) + if !ok { + return t + } + switch cv.Kind() { + case constant.Bool: + r = reflect.TypeOf(true) + case constant.Int: + r = reflect.TypeOf(0) + case constant.String: + r = reflect.TypeOf("") + case constant.Float: + r = reflect.TypeOf(float64(0)) + case constant.Complex: + r = reflect.TypeOf(complex128(0)) + } + return r +} + +// zero instantiates and return a zero value object for the given type during execution. +func (t *itype) zero() (v reflect.Value, err error) { + if t, err = t.finalize(); err != nil { + return v, err + } + switch t.cat { + case linkedT: + v, err = t.val.zero() + + case arrayT, ptrT, structT, sliceT: + v = reflect.New(t.frameType()).Elem() + + case valueT: + v = reflect.New(t.rtype).Elem() + + default: + v = zeroValues[t.cat] + } + return v, err +} + +// fieldIndex returns the field index from name in a struct, or -1 if not found. +func (t *itype) fieldIndex(name string) int { + switch t.cat { + case linkedT, ptrT: + return t.val.fieldIndex(name) + } + for i, field := range t.field { + if name == field.name { + return i + } + } + return -1 +} + +// fieldSeq returns the field type from the list of field indexes. +func (t *itype) fieldSeq(seq []int) *itype { + ft := t + for _, i := range seq { + if ft.cat == ptrT { + ft = ft.val + } + ft = ft.field[i].typ + } + return ft +} + +// lookupField returns a list of indices, i.e. a path to access a field in a struct object. +func (t *itype) lookupField(name string) []int { + seen := map[*itype]bool{} + var lookup func(*itype) []int + tias := isStruct(t) + + lookup = func(typ *itype) []int { + if seen[typ] { + return nil + } + seen[typ] = true + + switch typ.cat { + case linkedT, ptrT: + return lookup(typ.val) + } + if fi := typ.fieldIndex(name); fi >= 0 { + return []int{fi} + } + + for i, f := range typ.field { + switch f.typ.cat { + case ptrT, structT, interfaceT, linkedT: + if tias != isStruct(f.typ) { + // Interface fields are not valid embedded struct fields. + // Struct fields are not valid interface fields. + break + } + if index2 := lookup(f.typ); len(index2) > 0 { + return append([]int{i}, index2...) + } + } + } + + return nil + } + + return lookup(t) +} + +// lookupBinField returns a structfield and a path to access an embedded binary field in a struct object. +func (t *itype) lookupBinField(name string) (s reflect.StructField, index []int, ok bool) { + if t.cat == ptrT { + return t.val.lookupBinField(name) + } + if !isStruct(t) { + return + } + rt := t.TypeOf() + for t.cat == valueT && rt.Kind() == reflect.Ptr { + rt = rt.Elem() + } + if rt.Kind() != reflect.Struct { + return + } + s, ok = rt.FieldByName(name) + if !ok { + for i, f := range t.field { + if f.embed { + if s2, index2, ok2 := f.typ.lookupBinField(name); ok2 { + index = append([]int{i}, index2...) + return s2, index, ok2 + } + } + } + } + return s, index, ok +} + +// MethodCallType returns a method function type without the receiver defined. +// The input type must be a method function type with the receiver as the first input argument. +func (t *itype) methodCallType() reflect.Type { + it := []reflect.Type{} + ni := t.rtype.NumIn() + for i := 1; i < ni; i++ { + it = append(it, t.rtype.In(i)) + } + ot := []reflect.Type{} + no := t.rtype.NumOut() + for i := 0; i < no; i++ { + ot = append(ot, t.rtype.Out(i)) + } + return reflect.FuncOf(it, ot, t.rtype.IsVariadic()) +} + +func (t *itype) resolveAlias() *itype { + for t.cat == linkedT { + t = t.val + } + return t +} + +// GetMethod returns a pointer to the method definition. +func (t *itype) getMethod(name string) *node { + for _, m := range t.method { + if name == m.ident { + return m + } + } + return nil +} + +// LookupMethod returns a pointer to method definition associated to type t +// and the list of indices to access the right struct field, in case of an embedded method. +func (t *itype) lookupMethod(name string) (*node, []int) { + return t.lookupMethod2(name, nil) +} + +func (t *itype) lookupMethod2(name string, seen map[*itype]bool) (*node, []int) { + if seen == nil { + seen = map[*itype]bool{} + } + if seen[t] { + return nil, nil + } + seen[t] = true + if t.cat == ptrT { + return t.val.lookupMethod2(name, seen) + } + var index []int + m := t.getMethod(name) + if m == nil { + for i, f := range t.field { + if f.embed { + if n, index2 := f.typ.lookupMethod2(name, seen); n != nil { + index = append([]int{i}, index2...) + return n, index + } + } + } + if t.cat == linkedT || isInterfaceSrc(t) && t.val != nil { + return t.val.lookupMethod2(name, seen) + } + } + return m, index +} + +// interfaceMethod returns type of method matching an interface method name (not as a concrete method). +func (t *itype) interfaceMethod(name string) *itype { + return t.interfaceMethod2(name, nil) +} + +func (t *itype) interfaceMethod2(name string, seen map[*itype]bool) *itype { + if seen == nil { + seen = map[*itype]bool{} + } + if seen[t] { + return nil + } + seen[t] = true + if t.cat == ptrT { + return t.val.interfaceMethod2(name, seen) + } + for _, f := range t.field { + if f.name == name && isInterface(t) { + return f.typ + } + if !f.embed { + continue + } + if typ := f.typ.interfaceMethod2(name, seen); typ != nil { + return typ + } + } + if t.cat == linkedT || isInterfaceSrc(t) && t.val != nil { + return t.val.interfaceMethod2(name, seen) + } + return nil +} + +// methodDepth returns a depth greater or equal to 0, or -1 if no match. +func (t *itype) methodDepth(name string) int { + if m, lint := t.lookupMethod(name); m != nil { + return len(lint) + } + if _, lint, _, ok := t.lookupBinMethod(name); ok { + return len(lint) + } + return -1 +} + +// LookupBinMethod returns a method and a path to access a field in a struct object (the receiver). +func (t *itype) lookupBinMethod(name string) (m reflect.Method, index []int, isPtr, ok bool) { + return t.lookupBinMethod2(name, nil) +} + +func (t *itype) lookupBinMethod2(name string, seen map[*itype]bool) (m reflect.Method, index []int, isPtr, ok bool) { + if seen == nil { + seen = map[*itype]bool{} + } + if seen[t] { + return + } + seen[t] = true + if t.cat == ptrT { + return t.val.lookupBinMethod2(name, seen) + } + for i, f := range t.field { + if f.embed { + if m2, index2, isPtr2, ok2 := f.typ.lookupBinMethod2(name, seen); ok2 { + index = append([]int{i}, index2...) + return m2, index, isPtr2, ok2 + } + } + } + m, ok = t.TypeOf().MethodByName(name) + if !ok { + m, ok = reflect.PtrTo(t.TypeOf()).MethodByName(name) + isPtr = ok + } + return m, index, isPtr, ok +} + +func lookupFieldOrMethod(t *itype, name string) *itype { + switch { + case t.cat == valueT || t.cat == ptrT && t.val.cat == valueT: + m, _, isPtr, ok := t.lookupBinMethod(name) + if !ok { + return nil + } + var recv *itype + if t.rtype.Kind() != reflect.Interface { + recv = t + if isPtr && t.cat != ptrT && t.rtype.Kind() != reflect.Ptr { + recv = ptrOf(t) + } + } + return valueTOf(m.Type, withRecv(recv)) + case t.cat == interfaceT: + seq := t.lookupField(name) + if seq == nil { + return nil + } + return t.fieldSeq(seq) + default: + n, _ := t.lookupMethod(name) + if n == nil { + return nil + } + return n.typ + } +} + +func exportName(s string) string { + if canExport(s) { + return s + } + return "X" + s +} + +var ( + // TODO(mpl): generators. + emptyInterfaceType = reflect.TypeOf((*interface{})(nil)).Elem() + valueInterfaceType = reflect.TypeOf((*valueInterface)(nil)).Elem() + constVal = reflect.TypeOf((*constant.Value)(nil)).Elem() +) + +type refTypeContext struct { + defined map[string]*itype + + // refs keeps track of all the places (in the same type recursion) where the + // type name (as key) is used as a field of another (or possibly the same) struct + // type. Each of these fields will then live as an unsafe2.dummy type until the + // whole recursion is fully resolved, and the type is fixed. + refs map[string][]*itype + + // When we detect for the first time that we are in a recursive type (thanks to + // defined), we keep track of the first occurrence of the type where the recursion + // started, so we can restart the last step that fixes all the types from the same + // "top-level" point. + rect *itype + rebuilding bool + slevel int +} + +// Clone creates a copy of the ref type context. +func (c *refTypeContext) Clone() *refTypeContext { + return &refTypeContext{defined: c.defined, refs: c.refs, rebuilding: c.rebuilding} +} + +func (c *refTypeContext) isComplete() bool { + for _, t := range c.defined { + if t.rtype == nil { + return false + } + } + return true +} + +func (t *itype) fixDummy(typ reflect.Type) reflect.Type { + if typ == unsafe2.DummyType { + return t.rtype + } + switch typ.Kind() { + case reflect.Array: + return reflect.ArrayOf(typ.Len(), t.fixDummy(typ.Elem())) + case reflect.Chan: + return reflect.ChanOf(typ.ChanDir(), t.fixDummy(typ.Elem())) + case reflect.Func: + in := make([]reflect.Type, typ.NumIn()) + for i := range in { + in[i] = t.fixDummy(typ.In(i)) + } + out := make([]reflect.Type, typ.NumOut()) + for i := range out { + out[i] = t.fixDummy(typ.Out(i)) + } + return reflect.FuncOf(in, out, typ.IsVariadic()) + case reflect.Map: + return reflect.MapOf(t.fixDummy(typ.Key()), t.fixDummy(typ.Elem())) + case reflect.Ptr: + return reflect.PtrTo(t.fixDummy(typ.Elem())) + case reflect.Slice: + return reflect.SliceOf(t.fixDummy(typ.Elem())) + case reflect.Struct: + fields := make([]reflect.StructField, typ.NumField()) + for i := range fields { + fields[i] = typ.Field(i) + fields[i].Type = t.fixDummy(fields[i].Type) + } + return reflect.StructOf(fields) + } + return typ +} + +// RefType returns a reflect.Type representation from an interpreter type. +// In simple cases, reflect types are directly mapped from the interpreter +// counterpart. +// For recursive named struct or interfaces, as reflect does not permit to +// create a recursive named struct, a dummy type is set temporarily for each recursive +// field. When done, the dummy type fields are updated with the original reflect type +// pointer using unsafe. We thus obtain a usable recursive type definition, except +// for string representation, as created reflect types are still unnamed. +func (t *itype) refType(ctx *refTypeContext) reflect.Type { + if ctx == nil { + ctx = &refTypeContext{ + defined: map[string]*itype{}, + refs: map[string][]*itype{}, + } + } + if t.incomplete || t.cat == nilT { + var err error + if t, err = t.finalize(); err != nil { + panic(err) + } + } + name := t.path + "/" + t.name + + if t.rtype != nil && !ctx.rebuilding { + return t.rtype + } + if dt := ctx.defined[name]; dt != nil { + // We get here when we are a struct field, and our type name has already been + // seen at least once in one of our englobing structs. i.e. there's at least one + // level of type recursion. + if dt.rtype != nil { + t.rtype = dt.rtype + return dt.rtype + } + + // The recursion has not been fully resolved yet. + // To indicate that a rebuild is needed on the englobing struct, + // return a dummy field type and create an empty entry. + flds := ctx.refs[name] + ctx.rect = dt + + // We know we are used as a field by someone, but we don't know by who + // at this point in the code, so we just mark it as an empty *itype for now. + // We'll complete the *itype in the caller. + ctx.refs[name] = append(flds, (*itype)(nil)) + return unsafe2.DummyType + } + if isGeneric(t) { + return reflect.TypeOf((*generic)(nil)).Elem() + } + switch t.cat { + case linkedT: + t.rtype = t.val.refType(ctx) + case arrayT: + t.rtype = reflect.ArrayOf(t.length, t.val.refType(ctx)) + case sliceT, variadicT: + t.rtype = reflect.SliceOf(t.val.refType(ctx)) + case chanT: + t.rtype = reflect.ChanOf(reflect.BothDir, t.val.refType(ctx)) + case chanRecvT: + t.rtype = reflect.ChanOf(reflect.RecvDir, t.val.refType(ctx)) + case chanSendT: + t.rtype = reflect.ChanOf(reflect.SendDir, t.val.refType(ctx)) + case errorT: + t.rtype = reflect.TypeOf(new(error)).Elem() + case funcT: + variadic := false + in := make([]reflect.Type, len(t.arg)) + out := make([]reflect.Type, len(t.ret)) + for i, v := range t.arg { + in[i] = v.refType(ctx) + variadic = v.cat == variadicT + } + for i, v := range t.ret { + out[i] = v.refType(ctx) + } + t.rtype = reflect.FuncOf(in, out, variadic) + case interfaceT: + if len(t.field) == 0 { + // empty interface, do not wrap it + t.rtype = emptyInterfaceType + break + } + t.rtype = valueInterfaceType + case mapT: + t.rtype = reflect.MapOf(t.key.refType(ctx), t.val.refType(ctx)) + case ptrT: + rt := t.val.refType(ctx) + if rt == unsafe2.DummyType && ctx.slevel > 1 { + // We have a pointer to a recursive struct which is not yet fully computed. + // Return it but do not yet store it in rtype, so the complete version can + // be stored in future. + return reflect.PtrTo(rt) + } + t.rtype = reflect.PtrTo(rt) + case structT: + if t.name != "" { + ctx.defined[name] = t + } + ctx.slevel++ + var fields []reflect.StructField + for _, f := range t.field { + field := reflect.StructField{ + Name: exportName(f.name), + Type: f.typ.refType(ctx), + Tag: reflect.StructTag(f.tag), + } + if len(t.field) == 1 && f.embed { + // Mark the field as embedded (anonymous) only if it is the + // only one, to avoid a panic due to golang/go#15924 issue. + field.Anonymous = true + } + fields = append(fields, field) + // Find any nil type refs that indicates a rebuild is needed on this field. + for _, flds := range ctx.refs { + for j, fld := range flds { + if fld == nil { + flds[j] = t + } + } + } + } + ctx.slevel-- + type fixStructField struct { + name string + index int + } + fieldFix := []fixStructField{} // Slice of field indices to fix for recursivity. + t.rtype = reflect.StructOf(fields) + if ctx.isComplete() { + for _, s := range ctx.defined { + for i := 0; i < s.rtype.NumField(); i++ { + f := s.rtype.Field(i) + if strings.HasSuffix(f.Type.String(), "unsafe2.dummy") { + unsafe2.SetFieldType(s.rtype, i, ctx.rect.fixDummy(s.rtype.Field(i).Type)) + if name == s.path+"/"+s.name { + fieldFix = append(fieldFix, fixStructField{s.name, i}) + } + continue + } + if f.Type.Kind() == reflect.Func && strings.Contains(f.Type.String(), "unsafe2.dummy") { + fieldFix = append(fieldFix, fixStructField{s.name, i}) + } + } + } + } + + // The rtype has now been built, we can go back and rebuild + // all the recursive types that relied on this type. + // However, as we are keyed by type name, if two or more (recursive) fields at + // the same depth level are of the same type, or a "variation" of the same type + // (slice of, map of, etc), they "mask" each other, and only one + // of them is in ctx.refs. That is why the code around here is a bit convoluted, + // and we need both the loop above, around all the struct fields, and the loop + // below, around the ctx.refs. + for _, f := range ctx.refs[name] { + for _, ff := range fieldFix { + if ff.name == f.name { + ftyp := f.field[ff.index].typ.refType(&refTypeContext{defined: ctx.defined, rebuilding: true}) + unsafe2.SetFieldType(f.rtype, ff.index, ftyp) + } + } + } + default: + if z, _ := t.zero(); z.IsValid() { + t.rtype = z.Type() + } + } + return t.rtype +} + +// TypeOf returns the reflection type of dynamic interpreter type t. +func (t *itype) TypeOf() reflect.Type { + return t.refType(nil) +} + +func (t *itype) frameType() (r reflect.Type) { + var err error + if t, err = t.finalize(); err != nil { + panic(err) + } + switch t.cat { + case linkedT: + r = t.val.frameType() + case arrayT: + r = reflect.ArrayOf(t.length, t.val.frameType()) + case sliceT, variadicT: + r = reflect.SliceOf(t.val.frameType()) + case interfaceT: + if len(t.field) == 0 { + // empty interface, do not wrap it + r = emptyInterfaceType + break + } + r = valueInterfaceType + case mapT: + r = reflect.MapOf(t.key.frameType(), t.val.frameType()) + case ptrT: + r = reflect.PtrTo(t.val.frameType()) + default: + r = t.TypeOf() + } + return r +} + +func (t *itype) implements(it *itype) bool { + if isBin(t) { + // Note: in case of a valueInterfaceType, we + // miss required data which will be available + // later, so we optimistically return true to progress, + // and additional checks will be hopefully performed at + // runtime. + if rt := it.TypeOf(); rt == valueInterfaceType { + return true + } + return t.TypeOf().Implements(it.TypeOf()) + } + return t.methods().contains(it.methods()) +} + +// defaultType returns the default type of an untyped type. +func (t *itype) defaultType(v reflect.Value, sc *scope) *itype { + if !t.untyped { + return t + } + + typ := t + // The default type can also be derived from a constant value. + if v.IsValid() && v.Type().Implements(constVal) { + switch v.Interface().(constant.Value).Kind() { + case constant.String: + typ = sc.getType("string") + case constant.Bool: + typ = sc.getType("bool") + case constant.Int: + switch t.cat { + case int32T: + typ = sc.getType("int32") + default: + typ = sc.getType("int") + } + case constant.Float: + typ = sc.getType("float64") + case constant.Complex: + typ = sc.getType("complex128") + } + } + if typ.untyped { + switch t.cat { + case stringT: + typ = sc.getType("string") + case boolT: + typ = sc.getType("bool") + case intT: + typ = sc.getType("int") + case float64T: + typ = sc.getType("float64") + case complex128T: + typ = sc.getType("complex128") + default: + *typ = *t + typ.untyped = false + } + } + return typ +} + +func (t *itype) isNil() bool { return t.cat == nilT } + +func (t *itype) hasNil() bool { + switch rt := t.TypeOf(); rt.Kind() { + case reflect.UnsafePointer: + return true + case reflect.Slice, reflect.Ptr, reflect.Func, reflect.Interface, reflect.Map, reflect.Chan: + return true + case reflect.Struct: + if rt == valueInterfaceType { + return true + } + } + return false +} + +func (t *itype) elem() *itype { + if t.cat == valueT { + return valueTOf(t.rtype.Elem()) + } + return t.val +} + +func hasElem(t reflect.Type) bool { + switch t.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice: + return true + } + return false +} + +func constToInt(c constant.Value) int { + if constant.BitLen(c) > 64 { + panic(fmt.Sprintf("constant %s overflows int64", c.ExactString())) + } + i, _ := constant.Int64Val(c) + return int(i) +} + +func constToString(v reflect.Value) string { + c := v.Interface().(constant.Value) + return constant.StringVal(c) +} + +func wrappedType(n *node) *itype { + if n.typ.cat != valueT { + return nil + } + return n.typ.val +} + +func isShiftNode(n *node) bool { + switch n.action { + case aShl, aShr, aShlAssign, aShrAssign: + return true + } + return false +} + +// chanElement returns the channel element type. +func chanElement(t *itype) *itype { + switch t.cat { + case linkedT: + return chanElement(t.val) + case chanT, chanSendT, chanRecvT: + return t.val + case valueT: + return valueTOf(t.rtype.Elem(), withNode(t.node), withScope(t.scope)) + } + return nil +} + +func isBool(t *itype) bool { return t.TypeOf().Kind() == reflect.Bool } +func isChan(t *itype) bool { return t.TypeOf().Kind() == reflect.Chan } +func isFunc(t *itype) bool { return t.TypeOf().Kind() == reflect.Func } +func isMap(t *itype) bool { return t.TypeOf().Kind() == reflect.Map } +func isPtr(t *itype) bool { return t.TypeOf().Kind() == reflect.Ptr } + +func isEmptyInterface(t *itype) bool { + return t != nil && t.cat == interfaceT && len(t.field) == 0 +} + +func isGeneric(t *itype) bool { + return t.cat == funcT && t.node != nil && len(t.node.child) > 0 && len(t.node.child[0].child) > 0 +} + +func isNamedFuncSrc(t *itype) bool { + return isFuncSrc(t) && t.node.anc.kind == funcDecl +} + +func isFuncSrc(t *itype) bool { + return t.cat == funcT || (t.cat == linkedT && isFuncSrc(t.val)) +} + +func isPtrSrc(t *itype) bool { + return t.cat == ptrT || (t.cat == linkedT && isPtrSrc(t.val)) +} + +func isSendChan(t *itype) bool { + rt := t.TypeOf() + return rt.Kind() == reflect.Chan && rt.ChanDir() == reflect.SendDir +} + +func isArray(t *itype) bool { + if t.cat == nilT { + return false + } + k := t.TypeOf().Kind() + return k == reflect.Array || k == reflect.Slice +} + +func isInterfaceSrc(t *itype) bool { + return t.cat == interfaceT || (t.cat == linkedT && isInterfaceSrc(t.val)) +} + +func isInterfaceBin(t *itype) bool { + return t.cat == valueT && t.rtype.Kind() == reflect.Interface || t.cat == errorT +} + +func isInterface(t *itype) bool { + return isInterfaceSrc(t) || t.TypeOf() == valueInterfaceType || t.TypeOf() != nil && t.TypeOf().Kind() == reflect.Interface +} + +func isBin(t *itype) bool { + switch t.cat { + case valueT: + return true + case linkedT, ptrT: + return isBin(t.val) + default: + return false + } +} + +func isStruct(t *itype) bool { + // Test first for a struct category, because a recursive interpreter struct may be + // represented by an interface{} at reflect level. + switch t.cat { + case structT: + return true + case linkedT, ptrT: + return isStruct(t.val) + case valueT: + k := t.rtype.Kind() + return k == reflect.Struct || (k == reflect.Ptr && t.rtype.Elem().Kind() == reflect.Struct) + default: + return false + } +} + +func isConstType(t *itype) bool { + rt := t.TypeOf() + return isBoolean(rt) || isString(rt) || isNumber(rt) +} + +func isInt(t reflect.Type) bool { + if t == nil { + return false + } + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return true + } + return false +} + +func isUint(t reflect.Type) bool { + if t == nil { + return false + } + switch t.Kind() { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return true + } + return false +} + +func isComplex(t reflect.Type) bool { + if t == nil { + return false + } + switch t.Kind() { + case reflect.Complex64, reflect.Complex128: + return true + } + return false +} + +func isFloat(t reflect.Type) bool { + if t == nil { + return false + } + switch t.Kind() { + case reflect.Float32, reflect.Float64: + return true + } + return false +} + +func isByteArray(t reflect.Type) bool { + if t == nil { + return false + } + k := t.Kind() + return (k == reflect.Array || k == reflect.Slice) && t.Elem().Kind() == reflect.Uint8 +} + +func isFloat32(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Float32 } +func isFloat64(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Float64 } +func isNumber(t reflect.Type) bool { + return isInt(t) || isFloat(t) || isComplex(t) || isConstantValue(t) +} +func isBoolean(t reflect.Type) bool { return t != nil && t.Kind() == reflect.Bool } +func isString(t reflect.Type) bool { return t != nil && t.Kind() == reflect.String } +func isConstantValue(t reflect.Type) bool { return t != nil && t.Implements(constVal) } diff --git a/internal/yaegi/interp/typecheck.go b/internal/yaegi/interp/typecheck.go new file mode 100644 index 00000000..3ebcd2a3 --- /dev/null +++ b/internal/yaegi/interp/typecheck.go @@ -0,0 +1,1283 @@ +package interp + +import ( + "errors" + "go/constant" + "go/token" + "math" + "reflect" +) + +type opPredicates map[action]func(reflect.Type) bool + +// typecheck handles all type checking following "go/types" logic. +// +// Due to variant type systems (itype vs reflect.Type) a single +// type system should used, namely reflect.Type with exception +// of the untyped flag on itype. +type typecheck struct { + scope *scope +} + +// op type checks an expression against a set of expression predicates. +func (check typecheck) op(p opPredicates, a action, n, c *node, t reflect.Type) error { + if pred := p[a]; pred != nil { + if !pred(t) { + return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, c.typ.id()) + } + } else { + return n.cfgErrorf("invalid operation: unknown operator %v", n.action) + } + return nil +} + +// assignment checks if n can be assigned to typ. +// +// Use typ == nil to indicate assignment to an untyped blank identifier. +func (check typecheck) assignment(n *node, typ *itype, context string) error { + if n.typ == nil { + return n.cfgErrorf("invalid type in %s", context) + } + if n.typ.untyped { + if typ == nil || isInterface(typ) { + if typ == nil && n.typ.cat == nilT { + return n.cfgErrorf("use of untyped nil in %s", context) + } + typ = n.typ.defaultType(n.rval, check.scope) + } + if err := check.convertUntyped(n, typ); err != nil { + return err + } + } + + if typ == nil { + return nil + } + + if !n.typ.assignableTo(typ) && typ.str != "*unsafe2.dummy" { + if context == "" { + return n.cfgErrorf("cannot use type %s as type %s", n.typ.id(), typ.id()) + } + return n.cfgErrorf("cannot use type %s as type %s in %s", n.typ.id(), typ.id(), context) + } + return nil +} + +// assignExpr type checks an assign expression. +// +// This is done per pair of assignments. +func (check typecheck) assignExpr(n, dest, src *node) error { + if n.action == aAssign { + isConst := n.anc.kind == constDecl + if !isConst { + // var operations must be typed + dest.typ = dest.typ.defaultType(src.rval, check.scope) + } + + return check.assignment(src, dest.typ, "assignment") + } + + // assignment operations. + if n.nleft > 1 || n.nright > 1 { + return n.cfgErrorf("assignment operation %s requires single-valued expressions", n.action) + } + + return check.binaryExpr(n) +} + +// addressExpr type checks a unary address expression. +func (check typecheck) addressExpr(n *node) error { + c0 := n.child[0] + found := false + for !found { + switch c0.kind { + case parenExpr: + c0 = c0.child[0] + continue + case selectorExpr: + c0 = c0.child[1] + continue + case starExpr: + c0 = c0.child[0] + continue + case indexExpr, sliceExpr: + c := c0.child[0] + if isArray(c.typ) || isMap(c.typ) { + c0 = c + found = true + continue + } + case compositeLitExpr, identExpr: + found = true + continue + } + return n.cfgErrorf("invalid operation: cannot take address of %s [kind: %s]", c0.typ.id(), kinds[c0.kind]) + } + return nil +} + +// starExpr type checks a star expression on a variable. +func (check typecheck) starExpr(n *node) error { + if n.typ.TypeOf().Kind() != reflect.Ptr { + return n.cfgErrorf("invalid operation: cannot indirect %q", n.name()) + } + return nil +} + +var unaryOpPredicates = opPredicates{ + aInc: isNumber, + aDec: isNumber, + aPos: isNumber, + aNeg: isNumber, + aBitNot: isInt, + aNot: isBoolean, +} + +// unaryExpr type checks a unary expression. +func (check typecheck) unaryExpr(n *node) error { + c0 := n.child[0] + if isBlank(c0) { + return n.cfgErrorf("cannot use _ as value") + } + t0 := c0.typ.TypeOf() + + if n.action == aRecv { + if !isChan(c0.typ) { + return n.cfgErrorf("invalid operation: cannot receive from non-channel %s", c0.typ.id()) + } + if isSendChan(c0.typ) { + return n.cfgErrorf("invalid operation: cannot receive from send-only channel %s", c0.typ.id()) + } + return nil + } + + return check.op(unaryOpPredicates, n.action, n, c0, t0) +} + +// shift type checks a shift binary expression. +func (check typecheck) shift(n *node) error { + c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() + + var v0 constant.Value + if c0.typ.untyped && c0.rval.IsValid() { + v0 = constant.ToInt(c0.rval.Interface().(constant.Value)) + c0.rval = reflect.ValueOf(v0) + } + + if !(c0.typ.untyped && v0 != nil && v0.Kind() == constant.Int || isInt(t0)) { + return n.cfgErrorf("invalid operation: shift of type %v", c0.typ.id()) + } + + switch { + case c1.typ.untyped: + if err := check.convertUntyped(c1, check.scope.getType("uint")); err != nil { + return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id()) + } + case isInt(t1): + // nothing to do + default: + return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id()) + } + return nil +} + +// comparison type checks a comparison binary expression. +func (check typecheck) comparison(n *node) error { + t0, t1 := n.child[0].typ, n.child[1].typ + + if !t0.assignableTo(t1) && !t1.assignableTo(t0) { + return n.cfgErrorf("invalid operation: mismatched types %s and %s", t0.id(), t1.id()) + } + + ok := false + + if !isInterface(t0) && !isInterface(t1) && !t0.isNil() && !t1.isNil() && t0.untyped == t1.untyped && t0.id() != t1.id() && !typeDefined(t0, t1) { + // Non interface types must be really equals. + return n.cfgErrorf("invalid operation: mismatched types %s and %s", t0.id(), t1.id()) + } + + switch n.action { + case aEqual, aNotEqual: + ok = t0.comparable() && t1.comparable() || t0.isNil() && t1.hasNil() || t1.isNil() && t0.hasNil() + case aLower, aLowerEqual, aGreater, aGreaterEqual: + ok = t0.ordered() && t1.ordered() + } + if !ok { + typ := t0 + if typ.isNil() { + typ = t1 + } + return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id()) + } + return nil +} + +var binaryOpPredicates = opPredicates{ + aAdd: func(typ reflect.Type) bool { return isNumber(typ) || isString(typ) }, + aSub: isNumber, + aMul: isNumber, + aQuo: isNumber, + aRem: isInt, + + aAnd: isInt, + aOr: isInt, + aXor: isInt, + aAndNot: isInt, + + aLand: isBoolean, + aLor: isBoolean, +} + +// binaryExpr type checks a binary expression. +func (check typecheck) binaryExpr(n *node) error { + c0, c1 := n.child[0], n.child[1] + + if isBlank(c0) || isBlank(c1) { + return n.cfgErrorf("cannot use _ as value") + } + + a := n.action + if isAssignAction(a) { + a-- + } + + if isShiftAction(a) { + return check.shift(n) + } + + switch n.action { + case aAdd: + if n.typ == nil { + break + } + // Catch mixing string and number for "+" operator use. + k, k0, k1 := isNumber(n.typ.TypeOf()), isNumber(c0.typ.TypeOf()), isNumber(c1.typ.TypeOf()) + if k != k0 || k != k1 { + return n.cfgErrorf("cannot use type %s as type %s in assignment", c0.typ.id(), n.typ.id()) + } + case aRem: + if zeroConst(c1) { + return n.cfgErrorf("invalid operation: division by zero") + } + case aQuo: + if zeroConst(c1) { + return n.cfgErrorf("invalid operation: division by zero") + } + if c0.rval.IsValid() && c1.rval.IsValid() { + // Avoid constant conversions below to ensure correct constant integer quotient. + return nil + } + } + + _ = check.convertUntyped(c0, c1.typ) + _ = check.convertUntyped(c1, c0.typ) + + if isComparisonAction(a) { + return check.comparison(n) + } + + if !c0.typ.equals(c1.typ) { + return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id()) + } + + t0 := c0.typ.TypeOf() + + return check.op(binaryOpPredicates, a, n, c0, t0) +} + +func zeroConst(n *node) bool { + return n.typ.untyped && constant.Sign(n.rval.Interface().(constant.Value)) == 0 +} + +func (check typecheck) index(n *node, max int) error { + if err := check.convertUntyped(n, check.scope.getType("int")); err != nil { + return err + } + + if !isInt(n.typ.TypeOf()) { + return n.cfgErrorf("index %s must be integer", n.typ.id()) + } + + if !n.rval.IsValid() || max < 1 { + return nil + } + + if int(vInt(n.rval)) >= max { + return n.cfgErrorf("index %s is out of bounds", n.typ.id()) + } + + return nil +} + +// arrayLitExpr type checks an array composite literal expression. +func (check typecheck) arrayLitExpr(child []*node, typ *itype) error { + cat := typ.cat + length := typ.length + typ = typ.val + visited := make(map[int]bool, len(child)) + index := 0 + for _, c := range child { + n := c + switch { + case c.kind == keyValueExpr: + if err := check.index(c.child[0], length); err != nil { + return c.cfgErrorf("index %s must be integer constant", c.child[0].typ.id()) + } + n = c.child[1] + index = int(vInt(c.child[0].rval)) + case cat == arrayT && index >= length: + return c.cfgErrorf("index %d is out of bounds (>= %d)", index, length) + } + + if visited[index] { + return n.cfgErrorf("duplicate index %d in array or slice literal", index) + } + visited[index] = true + index++ + + if err := check.assignment(n, typ, "array or slice literal"); err != nil { + return err + } + } + return nil +} + +// mapLitExpr type checks an map composite literal expression. +func (check typecheck) mapLitExpr(child []*node, ktyp, vtyp *itype) error { + visited := make(map[interface{}]bool, len(child)) + for _, c := range child { + if c.kind != keyValueExpr { + return c.cfgErrorf("missing key in map literal") + } + + key, val := c.child[0], c.child[1] + if err := check.assignment(key, ktyp, "map literal"); err != nil { + return err + } + + if key.rval.IsValid() { + kval := key.rval.Interface() + if visited[kval] { + return c.cfgErrorf("duplicate key %s in map literal", kval) + } + visited[kval] = true + } + + if err := check.assignment(val, vtyp, "map literal"); err != nil { + return err + } + } + return nil +} + +// structLitExpr type checks a struct composite literal expression. +func (check typecheck) structLitExpr(child []*node, typ *itype) error { + if len(child) == 0 { + return nil + } + + if child[0].kind == keyValueExpr { + // All children must be keyValueExpr + visited := make([]bool, len(typ.field)) + for _, c := range child { + if c.kind != keyValueExpr { + return c.cfgErrorf("mixture of field:value and value elements in struct literal") + } + + key, val := c.child[0], c.child[1] + name := key.ident + if name == "" { + return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id()) + } + i := typ.fieldIndex(name) + if i < 0 { + return c.cfgErrorf("unknown field %s in struct literal", name) + } + field := typ.field[i] + + if err := check.assignment(val, field.typ, "struct literal"); err != nil { + return err + } + + if visited[i] { + return c.cfgErrorf("duplicate field name %s in struct literal", name) + } + visited[i] = true + } + return nil + } + + // No children can be keyValueExpr + for i, c := range child { + if c.kind == keyValueExpr { + return c.cfgErrorf("mixture of field:value and value elements in struct literal") + } + + if i >= len(typ.field) { + return c.cfgErrorf("too many values in struct literal") + } + field := typ.field[i] + // TODO(nick): check if this field is not exported and in a different package. + + if err := check.assignment(c, field.typ, "struct literal"); err != nil { + return err + } + } + if len(child) < len(typ.field) { + return child[len(child)-1].cfgErrorf("too few values in struct literal") + } + return nil +} + +// structBinLitExpr type checks a struct composite literal expression on a binary type. +func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error { + if len(child) == 0 { + return nil + } + + if child[0].kind == keyValueExpr { + // All children must be keyValueExpr + visited := make(map[string]bool, typ.NumField()) + for _, c := range child { + if c.kind != keyValueExpr { + return c.cfgErrorf("mixture of field:value and value elements in struct literal") + } + + key, val := c.child[0], c.child[1] + name := key.ident + if name == "" { + return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id()) + } + field, ok := typ.FieldByName(name) + if !ok { + return c.cfgErrorf("unknown field %s in struct literal", name) + } + + if err := check.assignment(val, valueTOf(field.Type), "struct literal"); err != nil { + return err + } + + if visited[field.Name] { + return c.cfgErrorf("duplicate field name %s in struct literal", name) + } + visited[field.Name] = true + } + return nil + } + + // No children can be keyValueExpr + for i, c := range child { + if c.kind == keyValueExpr { + return c.cfgErrorf("mixture of field:value and value elements in struct literal") + } + + if i >= typ.NumField() { + return c.cfgErrorf("too many values in struct literal") + } + field := typ.Field(i) + if !canExport(field.Name) { + return c.cfgErrorf("implicit assignment to unexported field %s in %s literal", field.Name, typ) + } + + if err := check.assignment(c, valueTOf(field.Type), "struct literal"); err != nil { + return err + } + } + if len(child) < typ.NumField() { + return child[len(child)-1].cfgErrorf("too few values in struct literal") + } + return nil +} + +// sliceExpr type checks a slice expression. +func (check typecheck) sliceExpr(n *node) error { + for _, c := range n.child { + if isBlank(c) { + return n.cfgErrorf("cannot use _ as value") + } + } + + c, child := n.child[0], n.child[1:] + + t := c.typ.TypeOf() + var low, high, max *node + if len(child) >= 1 { + if n.action == aSlice { + low = child[0] + } else { + high = child[0] + } + } + if len(child) >= 2 { + if n.action == aSlice { + high = child[1] + } else { + max = child[1] + } + } + if len(child) == 3 && n.action == aSlice { + max = child[2] + } + + l := -1 + valid := false + switch t.Kind() { + case reflect.String: + valid = true + if c.rval.IsValid() { + l = len(vString(c.rval)) + } + if max != nil { + return max.cfgErrorf("invalid operation: 3-index slice of string") + } + case reflect.Array: + valid = true + l = t.Len() + // TODO(marc): check addressable status of array object (i.e. composite arrays are not). + case reflect.Slice: + valid = true + case reflect.Ptr: + if t.Elem().Kind() == reflect.Array { + valid = true + l = t.Elem().Len() + } + } + if !valid { + return c.cfgErrorf("cannot slice type %s", c.typ.id()) + } + + var ind [3]int64 + for i, nod := range []*node{low, high, max} { + x := int64(-1) + switch { + case nod != nil: + max := -1 + if l >= 0 { + max = l + 1 + } + if err := check.index(nod, max); err != nil { + return err + } + if nod.rval.IsValid() { + x = vInt(nod.rval) + } + case i == 0: + x = 0 + case l >= 0: + x = int64(l) + } + ind[i] = x + } + + for i, x := range ind[:len(ind)-1] { + if x <= 0 { + continue + } + for _, y := range ind[i+1:] { + if y < 0 || x <= y { + continue + } + return n.cfgErrorf("invalid index values, must be low <= high <= max") + } + } + return nil +} + +// typeAssertionExpr type checks a type assert expression. +func (check typecheck) typeAssertionExpr(n *node, typ *itype) error { + // TODO(nick): This type check is not complete and should be revisited once + // https://github.com/golang/go/issues/39717 lands. It is currently impractical to + // type check Named types as they cannot be asserted. + + if rt := n.typ.TypeOf(); rt.Kind() != reflect.Interface && rt != valueInterfaceType { + return n.cfgErrorf("invalid type assertion: non-interface type %s on left", n.typ.id()) + } + ims := n.typ.methods() + if len(ims) == 0 { + // Empty interface must be a dynamic check. + return nil + } + + if isInterface(typ) { + // Asserting to an interface is a dynamic check as we must look to the + // underlying struct. + return nil + } + + for name := range ims { + im := lookupFieldOrMethod(n.typ, name) + tm := lookupFieldOrMethod(typ, name) + if im == nil { + // This should not be possible. + continue + } + if tm == nil { + // Lookup for non-exported methods is impossible + // for bin types, ignore them as they can't be used + // directly by the interpreted programs. + if !token.IsExported(name) && isBin(typ) { + continue + } + return n.cfgErrorf("impossible type assertion: %s does not implement %s (missing %v method)", typ.id(), n.typ.id(), name) + } + if tm.recv != nil && tm.recv.TypeOf().Kind() == reflect.Ptr && typ.TypeOf().Kind() != reflect.Ptr { + return n.cfgErrorf("impossible type assertion: %s does not implement %s as %q method has a pointer receiver", typ.id(), n.typ.id(), name) + } + + if im.cat != funcT || tm.cat != funcT { + // It only makes sense to compare in/out parameter types if both types are functions. + continue + } + + err := n.cfgErrorf("impossible type assertion: %s does not implement %s", typ.id(), n.typ.id()) + if im.numIn() != tm.numIn() || im.numOut() != tm.numOut() { + return err + } + for i := 0; i < im.numIn(); i++ { + if !im.in(i).equals(tm.in(i)) { + return err + } + } + for i := 0; i < im.numOut(); i++ { + if !im.out(i).equals(tm.out(i)) { + return err + } + } + } + return nil +} + +// conversion type checks the conversion of n to typ. +func (check typecheck) conversion(n *node, typ *itype) error { + var c constant.Value + if n.rval.IsValid() { + if con, ok := n.rval.Interface().(constant.Value); ok { + c = con + } + } + + var ok bool + switch { + case c != nil && isConstType(typ): + switch t := typ.TypeOf(); { + case representableConst(c, t): + ok = true + case isInt(n.typ.TypeOf()) && isString(t): + codepoint := int64(-1) + if i, ok := constant.Int64Val(c); ok { + codepoint = i + } + n.rval = reflect.ValueOf(constant.MakeString(string(rune(codepoint)))) + ok = true + } + + case n.typ.convertibleTo(typ): + ok = true + } + if !ok { + return n.cfgErrorf("cannot convert expression of type %s to type %s", n.typ.id(), typ.id()) + } + if !n.typ.untyped || c == nil { + return nil + } + if isInterface(typ) || !isConstType(typ) { + typ = n.typ.defaultType(n.rval, check.scope) + } + return check.convertUntyped(n, typ) +} + +type param struct { + nod *node + typ *itype +} + +func (p param) Type() *itype { + if p.typ != nil { + return p.typ + } + return p.nod.typ +} + +// unpackParams unpacks child parameters into a slice of param. +// If there is only 1 child and it is a callExpr with an n-value return, +// the return types are returned, otherwise the original child nodes are +// returned with nil typ. +func (check typecheck) unpackParams(child []*node) (params []param) { + if len(child) == 1 && isCall(child[0]) && child[0].child[0].typ.numOut() > 1 { + c0 := child[0] + ftyp := child[0].child[0].typ + for i := 0; i < ftyp.numOut(); i++ { + params = append(params, param{nod: c0, typ: ftyp.out(i)}) + } + return params + } + + for _, c := range child { + params = append(params, param{nod: c}) + } + return params +} + +var builtinFuncs = map[string]struct { + args int + variadic bool +}{ + bltnAlignof: {args: 1, variadic: false}, + bltnAppend: {args: 1, variadic: true}, + bltnCap: {args: 1, variadic: false}, + bltnClose: {args: 1, variadic: false}, + bltnComplex: {args: 2, variadic: false}, + bltnImag: {args: 1, variadic: false}, + bltnCopy: {args: 2, variadic: false}, + bltnDelete: {args: 2, variadic: false}, + bltnLen: {args: 1, variadic: false}, + bltnMake: {args: 1, variadic: true}, + bltnNew: {args: 1, variadic: false}, + bltnOffsetof: {args: 1, variadic: false}, + bltnPanic: {args: 1, variadic: false}, + bltnPrint: {args: 0, variadic: true}, + bltnPrintln: {args: 0, variadic: true}, + bltnReal: {args: 1, variadic: false}, + bltnRecover: {args: 0, variadic: false}, + bltnSizeof: {args: 1, variadic: false}, +} + +func (check typecheck) builtin(name string, n *node, child []*node, ellipsis bool) error { + fun := builtinFuncs[name] + if ellipsis && name != bltnAppend { + return n.cfgErrorf("invalid use of ... with builtin %s", name) + } + + var params []param + nparams := len(child) + switch name { + case bltnMake, bltnNew: + // Special param handling + default: + params = check.unpackParams(child) + nparams = len(params) + } + + if nparams < fun.args { + return n.cfgErrorf("not enough arguments in call to %s", name) + } else if !fun.variadic && nparams > fun.args { + return n.cfgErrorf("too many arguments for %s", name) + } + + switch name { + case bltnAppend: + typ := params[0].Type() + t := typ.TypeOf() + if t == nil || t.Kind() != reflect.Slice { + return params[0].nod.cfgErrorf("first argument to append must be slice; have %s", typ.id()) + } + + if nparams == 1 { + return nil + } + // Special case append([]byte, "test"...) is allowed. + t1 := params[1].Type() + if nparams == 2 && ellipsis && t.Elem().Kind() == reflect.Uint8 && t1.TypeOf().Kind() == reflect.String { + if t1.untyped { + return check.convertUntyped(params[1].nod, check.scope.getType("string")) + } + return nil + } + + fun := &node{ + typ: &itype{ + cat: funcT, + arg: []*itype{ + typ, + {cat: variadicT, val: valueTOf(t.Elem())}, + }, + ret: []*itype{typ}, + }, + ident: "append", + } + return check.arguments(n, child, fun, ellipsis) + case bltnCap, bltnLen: + typ := arrayDeref(params[0].Type()) + ok := false + switch typ.TypeOf().Kind() { + case reflect.Array, reflect.Slice, reflect.Chan: + ok = true + case reflect.String, reflect.Map: + ok = name == bltnLen + } + if !ok { + return params[0].nod.cfgErrorf("invalid argument for %s", name) + } + case bltnClose: + p := params[0] + typ := p.Type() + t := typ.TypeOf() + if t.Kind() != reflect.Chan { + return p.nod.cfgErrorf("invalid operation: non-chan type %s", p.nod.typ.id()) + } + if t.ChanDir() == reflect.RecvDir { + return p.nod.cfgErrorf("invalid operation: cannot close receive-only channel") + } + case bltnComplex: + var err error + p0, p1 := params[0], params[1] + typ0, typ1 := p0.Type(), p1.Type() + switch { + case typ0.untyped && !typ1.untyped: + err = check.convertUntyped(p0.nod, typ1) + case !typ0.untyped && typ1.untyped: + err = check.convertUntyped(p1.nod, typ0) + case typ0.untyped && typ1.untyped: + fltType := untypedFloat(nil) + err = check.convertUntyped(p0.nod, fltType) + if err != nil { + break + } + err = check.convertUntyped(p1.nod, fltType) + } + if err != nil { + return err + } + + // check we have the correct types after conversion. + typ0, typ1 = p0.Type(), p1.Type() + if !typ0.equals(typ1) { + return n.cfgErrorf("invalid operation: mismatched types %s and %s", typ0.id(), typ1.id()) + } + if !isFloat(typ0.TypeOf()) { + return n.cfgErrorf("invalid operation: arguments have type %s, expected floating-point", typ0.id()) + } + case bltnImag, bltnReal: + p := params[0] + typ := p.Type() + if typ.untyped { + if err := check.convertUntyped(p.nod, untypedComplex(nil)); err != nil { + return err + } + } + typ = p.Type() + if !isComplex(typ.TypeOf()) { + return p.nod.cfgErrorf("invalid argument type %s for %s", typ.id(), name) + } + case bltnCopy: + typ0, typ1 := params[0].Type(), params[1].Type() + var t0, t1 reflect.Type + if t := typ0.TypeOf(); t.Kind() == reflect.Slice { + t0 = t.Elem() + } + + switch t := typ1.TypeOf(); t.Kind() { + case reflect.String: + t1 = reflect.TypeOf(byte(1)) + case reflect.Slice: + t1 = t.Elem() + } + + if t0 == nil || t1 == nil { + return n.cfgErrorf("copy expects slice arguments") + } + if !reflect.DeepEqual(t0, t1) { + return n.cfgErrorf("arguments to copy have different element types %s and %s", typ0.id(), typ1.id()) + } + case bltnDelete: + typ := params[0].Type() + if typ.TypeOf().Kind() != reflect.Map { + return params[0].nod.cfgErrorf("first argument to delete must be map; have %s", typ.id()) + } + ktyp := params[1].Type() + if typ.key != nil && !ktyp.assignableTo(typ.key) { + return params[1].nod.cfgErrorf("cannot use %s as type %s in delete", ktyp.id(), typ.key.id()) + } + case bltnMake: + var min int + switch child[0].typ.TypeOf().Kind() { + case reflect.Slice: + min = 2 + case reflect.Map, reflect.Chan: + min = 1 + default: + return child[0].cfgErrorf("cannot make %s; type must be slice, map, or channel", child[0].typ.id()) + } + if nparams < min { + return n.cfgErrorf("not enough arguments in call to make") + } else if nparams > min+1 { + return n.cfgErrorf("too many arguments for make") + } + + var sizes []int + for _, c := range child[1:] { + if err := check.index(c, -1); err != nil { + return err + } + if c.rval.IsValid() { + sizes = append(sizes, int(vInt(c.rval))) + } + } + for len(sizes) == 2 && sizes[0] > sizes[1] { + return n.cfgErrorf("len larger than cap in make") + } + + case bltnPanic: + return check.assignment(params[0].nod, check.scope.getType("interface{}"), "argument to panic") + case bltnPrint, bltnPrintln: + for _, param := range params { + if param.typ != nil { + continue + } + + if err := check.assignment(param.nod, nil, "argument to "+name); err != nil { + return err + } + } + case bltnRecover, bltnNew, bltnAlignof, bltnOffsetof, bltnSizeof: + // Nothing to do. + default: + return n.cfgErrorf("unsupported builtin %s", name) + } + return nil +} + +// arrayDeref returns A if typ is *A, otherwise typ. +func arrayDeref(typ *itype) *itype { + if typ.cat == valueT && typ.TypeOf().Kind() == reflect.Ptr { + t := typ.TypeOf() + if t.Elem().Kind() == reflect.Array { + return valueTOf(t.Elem()) + } + return typ + } + + if typ.cat == ptrT && typ.val.cat == arrayT { + return typ.val + } + return typ +} + +// arguments type checks the call expression arguments. +func (check typecheck) arguments(n *node, child []*node, fun *node, ellipsis bool) error { + params := check.unpackParams(child) + l := len(child) + if ellipsis { + if !fun.typ.isVariadic() { + return n.cfgErrorf("invalid use of ..., corresponding parameter is non-variadic") + } + if len(params) > l { + return child[0].cfgErrorf("cannot use ... with %d-valued %s", child[0].child[0].typ.numOut(), child[0].child[0].typ.id()) + } + } + + var cnt int + for i, param := range params { + ellip := i == l-1 && ellipsis + if err := check.argument(param, fun.typ, cnt, l, ellip); err != nil { + return err + } + cnt++ + } + + if fun.typ.isVariadic() { + cnt++ + } + if cnt < fun.typ.numIn() { + return n.cfgErrorf("not enough arguments in call to %s", fun.name()) + } + return nil +} + +func (check typecheck) argument(p param, ftyp *itype, i, l int, ellipsis bool) error { + atyp := getArg(ftyp, i) + if atyp == nil { + return p.nod.cfgErrorf("too many arguments") + } + + if p.typ == nil && isCall(p.nod) && p.nod.child[0].typ.numOut() != 1 { + if l == 1 { + return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.child[0].typ.id(), getArgsID(ftyp)) + } + return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.child[0].typ.id(), atyp.id()) + } + + if ellipsis { + if i != ftyp.numIn()-1 { + return p.nod.cfgErrorf("can only use ... with matching parameter") + } + t := p.Type().TypeOf() + if t.Kind() != reflect.Slice || !(valueTOf(t.Elem())).assignableTo(atyp) { + return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.typ.id(), (sliceOf(atyp)).id()) + } + return nil + } + + if p.typ != nil { + if !p.typ.assignableTo(atyp) { + return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.child[0].typ.id(), getArgsID(ftyp)) + } + return nil + } + return check.assignment(p.nod, atyp, "") +} + +func getArg(ftyp *itype, i int) *itype { + l := ftyp.numIn() + switch { + case ftyp.isVariadic() && i >= l-1: + arg := ftyp.in(l - 1).val + return arg + case i < l: + return ftyp.in(i) + case ftyp.cat == valueT && i < ftyp.rtype.NumIn(): + return valueTOf(ftyp.rtype.In(i)) + default: + return nil + } +} + +func getArgsID(ftyp *itype) string { + res := "(" + for i, arg := range ftyp.arg { + if i > 0 { + res += "," + } + res += arg.id() + } + res += ")" + return res +} + +var errCantConvert = errors.New("cannot convert") + +func (check typecheck) convertUntyped(n *node, typ *itype) error { + if n.typ == nil || !n.typ.untyped || typ == nil { + return nil + } + + convErr := n.cfgErrorf("cannot convert %s to %s", n.typ.id(), typ.id()) + + ntyp, ttyp := n.typ.TypeOf(), typ.TypeOf() + if typ.untyped { + // Both n and target are untyped. + nkind, tkind := ntyp.Kind(), ttyp.Kind() + if isNumber(ntyp) && isNumber(ttyp) { + if nkind < tkind { + n.typ = typ + } + } else if nkind != tkind { + return convErr + } + return nil + } + + var ( + ityp *itype + rtyp reflect.Type + err error + ) + switch { + case typ.isNil() && n.typ.isNil(): + n.typ = typ + return nil + case isNumber(ttyp) || isString(ttyp) || isBoolean(ttyp): + ityp = typ + rtyp = ttyp + case isInterface(typ): + if n.typ.isNil() { + return nil + } + if len(n.typ.methods()) > 0 { // untyped cannot be set to iface + return convErr + } + ityp = n.typ.defaultType(n.rval, check.scope) + rtyp = ntyp + case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ): + // TODO(nick): above we are acting on itype, but really it is an rtype check. This is not clear which type + // plain we are in. Fix this later. + if !n.typ.isNil() { + return convErr + } + return nil + case n.typ.isNil() && typ.id() == "unsafe.Pointer": + n.typ = typ + return nil + default: + return convErr + } + + if err := check.representable(n, rtyp); err != nil { + return err + } + n.rval, err = check.convertConst(n.rval, rtyp) + if err != nil { + if errors.Is(err, errCantConvert) { + return convErr + } + return n.cfgErrorf(err.Error()) + } + n.typ = ityp + return nil +} + +func (check typecheck) representable(n *node, t reflect.Type) error { + if !n.rval.IsValid() { + // TODO(nick): This should be an error as the const is in the frame which is undesirable. + return nil + } + c, ok := n.rval.Interface().(constant.Value) + if !ok { + // TODO(nick): This should be an error as untyped strings and bools should be constant.Values. + return nil + } + + if !representableConst(c, t) { + typ := n.typ.TypeOf() + if isNumber(typ) && isNumber(t) { + // numeric conversion : error msg + // + // integer -> integer : overflows + // integer -> float : overflows (actually not possible) + // float -> integer : truncated + // float -> float : overflows + // + if !isInt(typ) && isInt(t) { + return n.cfgErrorf("%s truncated to %s", c.ExactString(), t.Kind().String()) + } + return n.cfgErrorf("%s overflows %s", c.ExactString(), t.Kind().String()) + } + return n.cfgErrorf("cannot convert %s to %s", c.ExactString(), t.Kind().String()) + } + return nil +} + +func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) { + if !v.IsValid() { + // TODO(nick): This should be an error as the const is in the frame which is undesirable. + return v, nil + } + c, ok := v.Interface().(constant.Value) + if !ok { + // TODO(nick): This should be an error as untyped strings and bools should be constant.Values. + return v, nil + } + + kind := t.Kind() + switch kind { + case reflect.Bool: + v = reflect.ValueOf(constant.BoolVal(c)) + case reflect.String: + v = reflect.ValueOf(constant.StringVal(c)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i, _ := constant.Int64Val(constant.ToInt(c)) + v = reflect.ValueOf(i).Convert(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + i, _ := constant.Uint64Val(constant.ToInt(c)) + v = reflect.ValueOf(i).Convert(t) + case reflect.Float32: + f, _ := constant.Float32Val(constant.ToFloat(c)) + v = reflect.ValueOf(f) + case reflect.Float64: + f, _ := constant.Float64Val(constant.ToFloat(c)) + v = reflect.ValueOf(f) + case reflect.Complex64: + r, _ := constant.Float32Val(constant.Real(c)) + i, _ := constant.Float32Val(constant.Imag(c)) + v = reflect.ValueOf(complex(r, i)).Convert(t) + case reflect.Complex128: + r, _ := constant.Float64Val(constant.Real(c)) + i, _ := constant.Float64Val(constant.Imag(c)) + v = reflect.ValueOf(complex(r, i)).Convert(t) + default: + return v, errCantConvert + } + return v, nil +} + +var bitlen = [...]int{ + reflect.Int: 64, + reflect.Int8: 8, + reflect.Int16: 16, + reflect.Int32: 32, + reflect.Int64: 64, + reflect.Uint: 64, + reflect.Uint8: 8, + reflect.Uint16: 16, + reflect.Uint32: 32, + reflect.Uint64: 64, + reflect.Uintptr: 64, +} + +func representableConst(c constant.Value, t reflect.Type) bool { + switch { + case isInt(t): + x := constant.ToInt(c) + if x.Kind() != constant.Int { + return false + } + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if _, ok := constant.Int64Val(x); !ok { + return false + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if _, ok := constant.Uint64Val(x); !ok { + return false + } + default: + return false + } + return constant.BitLen(x) <= bitlen[t.Kind()] + case isFloat(t): + x := constant.ToFloat(c) + if x.Kind() != constant.Float { + return false + } + switch t.Kind() { + case reflect.Float32: + f, _ := constant.Float32Val(x) + return !math.IsInf(float64(f), 0) + case reflect.Float64: + f, _ := constant.Float64Val(x) + return !math.IsInf(f, 0) + default: + return false + } + case isComplex(t): + x := constant.ToComplex(c) + if x.Kind() != constant.Complex { + return false + } + switch t.Kind() { + case reflect.Complex64: + r, _ := constant.Float32Val(constant.Real(x)) + i, _ := constant.Float32Val(constant.Imag(x)) + return !math.IsInf(float64(r), 0) && !math.IsInf(float64(i), 0) + case reflect.Complex128: + r, _ := constant.Float64Val(constant.Real(x)) + i, _ := constant.Float64Val(constant.Imag(x)) + return !math.IsInf(r, 0) && !math.IsInf(i, 0) + default: + return false + } + case isString(t): + return c.Kind() == constant.String + case isBoolean(t): + return c.Kind() == constant.Bool + default: + return false + } +} + +func isShiftAction(a action) bool { + switch a { + case aShl, aShr, aShlAssign, aShrAssign: + return true + } + return false +} + +func isComparisonAction(a action) bool { + switch a { + case aEqual, aNotEqual, aGreater, aGreaterEqual, aLower, aLowerEqual: + return true + } + return false +} diff --git a/internal/yaegi/interp/typestring.go b/internal/yaegi/interp/typestring.go new file mode 100644 index 00000000..346f9dac --- /dev/null +++ b/internal/yaegi/interp/typestring.go @@ -0,0 +1,40 @@ +package interp + +import "strings" + +func paramsTypeString(params []*itype) string { + strs := make([]string, 0, len(params)) + for _, param := range params { + strs = append(strs, param.str) + } + return strings.Join(strs, ",") +} + +func methodsTypeString(fields []structField) string { + strs := make([]string, 0, len(fields)) + for _, field := range fields { + if field.embed { + str := methodsTypeString(field.typ.field) + if str != "" { + strs = append(strs, str) + } + continue + } + strs = append(strs, field.name+field.typ.str[4:]) + } + return strings.Join(strs, "; ") +} + +func fieldsTypeString(fields []structField) string { + strs := make([]string, 0, len(fields)) + for _, field := range fields { + var repr strings.Builder + if !field.embed { + repr.WriteString(field.name) + repr.WriteByte(' ') + } + repr.WriteString(field.typ.str) + strs = append(strs, repr.String()) + } + return strings.Join(strs, "; ") +} diff --git a/internal/yaegi/interp/use.go b/internal/yaegi/interp/use.go new file mode 100644 index 00000000..e4a6b622 --- /dev/null +++ b/internal/yaegi/interp/use.go @@ -0,0 +1,261 @@ +package interp + +import ( + "flag" + "fmt" + "go/constant" + "log" + "math/bits" + "os" + "path" + "reflect" + + gen "github.com/traefik/yaegi/stdlib/generic" +) + +// Symbols returns a map of interpreter exported symbol values for the given +// import path. If the argument is the empty string, all known symbols are +// returned. +func (interp *Interpreter) Symbols(importPath string) Exports { + m := map[string]map[string]reflect.Value{} + interp.mutex.RLock() + defer interp.mutex.RUnlock() + + for k, v := range interp.srcPkg { + if importPath != "" && k != importPath { + continue + } + syms := map[string]reflect.Value{} + for n, s := range v { + if !canExport(n) { + // Skip private non-exported symbols. + continue + } + switch s.kind { + case constSym: + syms[n] = s.rval + case funcSym: + syms[n] = genFunctionWrapper(s.node)(interp.frame) + case varSym: + syms[n] = interp.frame.data[s.index] + case typeSym: + syms[n] = reflect.New(s.typ.TypeOf()) + } + } + + if len(syms) > 0 { + m[k] = syms + } + + if importPath != "" { + return m + } + } + + if importPath != "" && len(m) > 0 { + return m + } + + for k, v := range interp.binPkg { + if importPath != "" && k != importPath { + continue + } + m[k] = v + if importPath != "" { + return m + } + } + + return m +} + +// getWrapper returns the wrapper type of the corresponding interface, trying +// first the composed ones, or nil if not found. +func getWrapper(n *node, t reflect.Type) reflect.Type { + p, ok := n.interp.binPkg[t.PkgPath()] + if !ok { + return nil + } + w := p["_"+t.Name()] + lm := n.typ.methods() + + // mapTypes may contain composed interfaces wrappers to test against, from + // most complex to simplest (guaranteed by construction of mapTypes). Find the + // first for which the interpreter type has all the methods. + for _, rt := range n.interp.mapTypes[w] { + match := true + for i := 1; i < rt.NumField(); i++ { + // The interpreter type must have all required wrapper methods. + if _, ok := lm[rt.Field(i).Name[1:]]; !ok { + match = false + break + } + } + if match { + return rt + } + } + + // Otherwise return the direct "non-composed" interface. + return w.Type().Elem() +} + +// Use loads binary runtime symbols in the interpreter context so +// they can be used in interpreted code. +func (interp *Interpreter) Use(values Exports) error { + for k, v := range values { + importPath := path.Dir(k) + packageName := path.Base(k) + + if k == "." && v["MapTypes"].IsValid() { + // Use mapping for special interface wrappers. + for kk, vv := range v["MapTypes"].Interface().(map[reflect.Value][]reflect.Type) { + interp.mapTypes[kk] = vv + } + continue + } + + if importPath == "." { + return fmt.Errorf("export path %[1]q is missing a package name; did you mean '%[1]s/%[1]s'?", k) + } + + if importPath == selfPrefix { + interp.hooks.Parse(v) + continue + } + + if interp.binPkg[importPath] == nil { + interp.binPkg[importPath] = make(map[string]reflect.Value) + interp.pkgNames[importPath] = packageName + } + + for s, sym := range v { + interp.binPkg[importPath][s] = sym + } + if k == selfPath { + interp.binPkg[importPath]["Self"] = reflect.ValueOf(interp) + } + } + + // Checks if input values correspond to stdlib packages by looking for one + // well known stdlib package path. + if _, ok := values["fmt/fmt"]; ok { + fixStdlib(interp) + + // Load stdlib generic source. + for _, s := range gen.Sources { + if _, err := interp.Compile(s); err != nil { + return err + } + } + } + return nil +} + +// fixStdlib redefines interpreter stdlib symbols to use the standard input, +// output and errror assigned to the interpreter. The changes are limited to +// the interpreter only. +// Note that it is possible to escape the virtualized stdio by +// read/write directly to file descriptors 0, 1, 2. +func fixStdlib(interp *Interpreter) { + p := interp.binPkg["fmt"] + if p == nil { + return + } + + stdin, stdout, stderr := interp.stdin, interp.stdout, interp.stderr + + p["Print"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprint(stdout, a...) }) + p["Printf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fprintf(stdout, f, a...) }) + p["Println"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprintln(stdout, a...) }) + + p["Scan"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscan(stdin, a...) }) + p["Scanf"] = reflect.ValueOf(func(f string, a ...interface{}) (n int, err error) { return fmt.Fscanf(stdin, f, a...) }) + p["Scanln"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fscanln(stdin, a...) }) + + // Update mapTypes to virtualized symbols as well. + interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(fmt.Print)] + interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(fmt.Printf)] + interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(fmt.Println)] + interp.mapTypes[p["Scan"]] = interp.mapTypes[reflect.ValueOf(fmt.Scan)] + interp.mapTypes[p["Scanf"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanf)] + interp.mapTypes[p["Scanln"]] = interp.mapTypes[reflect.ValueOf(fmt.Scanln)] + + if p = interp.binPkg["flag"]; p != nil { + c := flag.NewFlagSet(os.Args[0], flag.PanicOnError) + c.SetOutput(stderr) + p["CommandLine"] = reflect.ValueOf(&c).Elem() + } + + if p = interp.binPkg["log"]; p != nil { + l := log.New(stderr, "", log.LstdFlags) + // Restrict Fatal symbols to panic instead of exit. + p["Fatal"] = reflect.ValueOf(l.Panic) + p["Fatalf"] = reflect.ValueOf(l.Panicf) + p["Fatalln"] = reflect.ValueOf(l.Panicln) + + p["Flags"] = reflect.ValueOf(l.Flags) + p["Output"] = reflect.ValueOf(l.Output) + p["Panic"] = reflect.ValueOf(l.Panic) + p["Panicf"] = reflect.ValueOf(l.Panicf) + p["Panicln"] = reflect.ValueOf(l.Panicln) + p["Prefix"] = reflect.ValueOf(l.Prefix) + p["Print"] = reflect.ValueOf(l.Print) + p["Printf"] = reflect.ValueOf(l.Printf) + p["Println"] = reflect.ValueOf(l.Println) + p["SetFlags"] = reflect.ValueOf(l.SetFlags) + p["SetOutput"] = reflect.ValueOf(l.SetOutput) + p["SetPrefix"] = reflect.ValueOf(l.SetPrefix) + p["Writer"] = reflect.ValueOf(l.Writer) + + // Update mapTypes to virtualized symbols as well. + interp.mapTypes[p["Print"]] = interp.mapTypes[reflect.ValueOf(log.Print)] + interp.mapTypes[p["Printf"]] = interp.mapTypes[reflect.ValueOf(log.Printf)] + interp.mapTypes[p["Println"]] = interp.mapTypes[reflect.ValueOf(log.Println)] + interp.mapTypes[p["Panic"]] = interp.mapTypes[reflect.ValueOf(log.Panic)] + interp.mapTypes[p["Panicf"]] = interp.mapTypes[reflect.ValueOf(log.Panicf)] + interp.mapTypes[p["Panicln"]] = interp.mapTypes[reflect.ValueOf(log.Panicln)] + } + + if p = interp.binPkg["os"]; p != nil { + p["Args"] = reflect.ValueOf(&interp.args).Elem() + if interp.specialStdio { + // Inherit streams from interpreter even if they do not have a file descriptor. + p["Stdin"] = reflect.ValueOf(&stdin).Elem() + p["Stdout"] = reflect.ValueOf(&stdout).Elem() + p["Stderr"] = reflect.ValueOf(&stderr).Elem() + } else { + // Inherits streams from interpreter only if they have a file descriptor and preserve original type. + if s, ok := stdin.(*os.File); ok { + p["Stdin"] = reflect.ValueOf(&s).Elem() + } + if s, ok := stdout.(*os.File); ok { + p["Stdout"] = reflect.ValueOf(&s).Elem() + } + if s, ok := stderr.(*os.File); ok { + p["Stderr"] = reflect.ValueOf(&s).Elem() + } + } + if !interp.unrestricted { + // In restricted mode, scripts can only access to a passed virtualized env, and can not write the real one. + getenv := func(key string) string { return interp.env[key] } + p["Clearenv"] = reflect.ValueOf(func() { interp.env = map[string]string{} }) + p["ExpandEnv"] = reflect.ValueOf(func(s string) string { return os.Expand(s, getenv) }) + p["Getenv"] = reflect.ValueOf(getenv) + p["LookupEnv"] = reflect.ValueOf(func(key string) (s string, ok bool) { s, ok = interp.env[key]; return }) + p["Setenv"] = reflect.ValueOf(func(key, value string) error { interp.env[key] = value; return nil }) + p["Unsetenv"] = reflect.ValueOf(func(key string) error { delete(interp.env, key); return nil }) + p["Environ"] = reflect.ValueOf(func() (a []string) { + for k, v := range interp.env { + a = append(a, k+"="+v) + } + return + }) + } + } + + if p = interp.binPkg["math/bits"]; p != nil { + // Do not trust extracted value maybe from another arch. + p["UintSize"] = reflect.ValueOf(constant.MakeInt64(bits.UintSize)) + } +} diff --git a/internal/yaegi/interp/value.go b/internal/yaegi/interp/value.go new file mode 100644 index 00000000..e179b750 --- /dev/null +++ b/internal/yaegi/interp/value.go @@ -0,0 +1,536 @@ +package interp + +import ( + "go/constant" + "reflect" +) + +const ( + notInFrame = -1 // value of node.findex for literal values (not in frame) + globalFrame = -1 // value of node.level for global symbols +) + +func valueGenerator(n *node, i int) func(*frame) reflect.Value { + switch n.level { + case globalFrame: + return func(f *frame) reflect.Value { return valueOf(f.root.data, i) } + case 0: + return func(f *frame) reflect.Value { return valueOf(f.data, i) } + case 1: + return func(f *frame) reflect.Value { return valueOf(f.anc.data, i) } + case 2: + return func(f *frame) reflect.Value { return valueOf(f.anc.anc.data, i) } + default: + return func(f *frame) reflect.Value { + for level := n.level; level > 0; level-- { + f = f.anc + } + return valueOf(f.data, i) + } + } +} + +// valueOf safely recovers the ith element of data. This is necessary +// because a cancellation prior to any evaluation result may leave +// the frame's data empty. +func valueOf(data []reflect.Value, i int) reflect.Value { + if i < 0 || i >= len(data) { + return reflect.Value{} + } + return data[i] +} + +func genValueRecv(n *node) func(*frame) reflect.Value { + var v func(*frame) reflect.Value + if n.recv.node == nil { + v = func(*frame) reflect.Value { return n.recv.val } + } else { + v = genValue(n.recv.node) + } + fi := n.recv.index + + if len(fi) == 0 { + return v + } + + return func(f *frame) reflect.Value { + r := v(f) + for _, i := range fi { + if r.Kind() == reflect.Ptr { + r = r.Elem() + } + // Note that we can't use reflect FieldByIndex method, as we may + // traverse valueInterface wrappers to access the embedded receiver. + r = r.Field(i) + vi, ok := r.Interface().(valueInterface) + if ok { + r = vi.value + } + } + return r + } +} + +func genValueAsFunctionWrapper(n *node) func(*frame) reflect.Value { + value := genValue(n) + typ := n.typ.TypeOf() + + return func(f *frame) reflect.Value { + v := value(f) + if v.IsNil() { + return reflect.New(typ).Elem() + } + if v.Kind() == reflect.Func { + return v + } + vn, ok := v.Interface().(*node) + if ok && vn.rval.Kind() == reflect.Func { + // The node value is already a callable func, no need to wrap it. + return vn.rval + } + return genFunctionWrapper(vn)(f) + } +} + +func genValueAs(n *node, t reflect.Type) func(*frame) reflect.Value { + value := genValue(n) + + return func(f *frame) reflect.Value { + v := value(f) + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice, reflect.UnsafePointer: + if v.IsNil() { + return reflect.New(t).Elem() + } + } + return v.Convert(t) + } +} + +func genValue(n *node) func(*frame) reflect.Value { + switch n.kind { + case basicLit: + convertConstantValue(n) + v := n.rval + if !v.IsValid() { + v = reflect.New(emptyInterfaceType).Elem() + } + return func(f *frame) reflect.Value { return v } + case funcDecl: + var v reflect.Value + if w, ok := n.val.(reflect.Value); ok { + v = w + } else { + v = reflect.ValueOf(n.val) + } + return func(f *frame) reflect.Value { return v } + default: + if n.rval.IsValid() { + convertConstantValue(n) + v := n.rval + return func(f *frame) reflect.Value { return v } + } + if n.sym != nil { + i := n.sym.index + if i < 0 && n != n.sym.node { + return genValue(n.sym.node) + } + if n.sym.global { + return func(f *frame) reflect.Value { return f.root.data[i] } + } + return valueGenerator(n, i) + } + if n.findex == notInFrame { + var v reflect.Value + if w, ok := n.val.(reflect.Value); ok { + v = w + } else { + v = reflect.ValueOf(n.val) + } + return func(f *frame) reflect.Value { return v } + } + return valueGenerator(n, n.findex) + } +} + +func genDestValue(typ *itype, n *node) func(*frame) reflect.Value { + convertLiteralValue(n, typ.TypeOf()) + switch { + case isInterfaceSrc(typ) && (!isEmptyInterface(typ) || len(n.typ.method) > 0): + return genValueInterface(n) + case isNamedFuncSrc(n.typ): + return genFunctionWrapper(n) + case isInterfaceBin(typ): + return genInterfaceWrapper(n, typ.rtype) + case n.kind == basicLit && n.val == nil: + return func(*frame) reflect.Value { return reflect.New(typ.rtype).Elem() } + case n.typ.untyped && isComplex(typ.TypeOf()): + return genValueComplex(n) + case n.typ.untyped && !typ.untyped: + return genValueAs(n, typ.TypeOf()) + } + return genValue(n) +} + +func genFuncValue(n *node) func(*frame) reflect.Value { + value := genValue(n) + return func(f *frame) reflect.Value { + v := value(f) + if nod, ok := v.Interface().(*node); ok { + return genFunctionWrapper(nod)(f) + } + return v + } +} + +func genValueArray(n *node) func(*frame) reflect.Value { + value := genValue(n) + // dereference array pointer, to support array operations on array pointer + if n.typ.TypeOf().Kind() == reflect.Ptr { + return func(f *frame) reflect.Value { + return value(f).Elem() + } + } + return value +} + +func genValueRangeArray(n *node) func(*frame) reflect.Value { + value := genValue(n) + + switch { + case n.typ.TypeOf().Kind() == reflect.Ptr: + // dereference array pointer, to support array operations on array pointer + return func(f *frame) reflect.Value { + return value(f).Elem() + } + case n.typ.val != nil && n.typ.val.cat == interfaceT: + if len(n.typ.val.field) > 0 { + return func(f *frame) reflect.Value { + val := value(f) + v := []valueInterface{} + for i := 0; i < val.Len(); i++ { + switch av := val.Index(i).Interface().(type) { + case []valueInterface: + v = append(v, av...) + case valueInterface: + v = append(v, av) + default: + panic(n.cfgErrorf("invalid type %v", val.Index(i).Type())) + } + } + return reflect.ValueOf(v) + } + } + // empty interface, do not wrap. + fallthrough + default: + return func(f *frame) reflect.Value { + // This is necessary to prevent changes in the returned + // reflect.Value being reflected back to the value used + // for the range expression. + return reflect.ValueOf(value(f).Interface()) + } + } +} + +func genValueInterface(n *node) func(*frame) reflect.Value { + value := genValue(n) + + return func(f *frame) reflect.Value { + v := value(f) + nod := n + + for v.IsValid() { + // traverse interface indirections to find out concrete type + vi, ok := v.Interface().(valueInterface) + if !ok { + break + } + v = vi.value + nod = vi.node + } + + // empty interface, do not wrap. + if nod != nil && isEmptyInterface(nod.typ) { + return v + } + + return reflect.ValueOf(valueInterface{nod, v}) + } +} + +func getConcreteValue(val reflect.Value) reflect.Value { + v := val + for { + vi, ok := v.Interface().(valueInterface) + if !ok { + break + } + v = vi.value + } + if v.NumMethod() > 0 { + return v + } + if v.Kind() != reflect.Struct { + return v + } + // Search a concrete value in fields of an emulated interface. + for i := v.NumField() - 1; i >= 0; i-- { + vv := v.Field(i) + if vv.Kind() == reflect.Interface { + vv = vv.Elem() + } + if vv.IsValid() { + return vv + } + } + return v +} + +func zeroInterfaceValue() reflect.Value { + n := &node{kind: basicLit, typ: &itype{cat: nilT, untyped: true, str: "nil"}} + v := reflect.New(emptyInterfaceType).Elem() + return reflect.ValueOf(valueInterface{n, v}) +} + +func wantEmptyInterface(n *node) bool { + return isEmptyInterface(n.typ) || + n.anc.action == aAssign && n.anc.typ.cat == interfaceT && len(n.anc.typ.field) == 0 || + n.anc.kind == returnStmt && n.anc.val.(*node).typ.ret[0].cat == interfaceT && len(n.anc.val.(*node).typ.ret[0].field) == 0 +} + +func genValueOutput(n *node, t reflect.Type) func(*frame) reflect.Value { + value := genValue(n) + switch { + case n.anc.action == aAssign && n.anc.typ.cat == interfaceT: + if len(n.anc.typ.field) == 0 { + // empty interface, do not wrap + return value + } + fallthrough + case n.anc.kind == returnStmt && n.anc.val.(*node).typ.ret[0].cat == interfaceT: + if nod, ok := n.anc.val.(*node); !ok || len(nod.typ.ret[0].field) == 0 { + // empty interface, do not wrap + return value + } + // The result of the builtin has to be returned as an interface type. + // Wrap it in a valueInterface and return the dereferenced value. + return func(f *frame) reflect.Value { + d := value(f) + v := reflect.New(t).Elem() + d.Set(reflect.ValueOf(valueInterface{n, v})) + return v + } + } + return value +} + +func getBinValue(getMapType func(*itype) reflect.Type, value func(*frame) reflect.Value, f *frame) reflect.Value { + v := value(f) + if getMapType == nil { + return v + } + val, ok := v.Interface().(valueInterface) + if !ok || val.node == nil { + return v + } + if rt := getMapType(val.node.typ); rt != nil { + return genInterfaceWrapper(val.node, rt)(f) + } + return v +} + +func valueInterfaceValue(v reflect.Value) reflect.Value { + for { + vv, ok := v.Interface().(valueInterface) + if !ok { + break + } + v = vv.value + } + return v +} + +func genValueInterfaceValue(n *node) func(*frame) reflect.Value { + value := genValue(n) + + return func(f *frame) reflect.Value { + v := value(f) + if vi, ok := v.Interface().(valueInterface); ok && vi.node == nil { + // Uninitialized interface value, set it to a correct zero value. + v.Set(zeroInterfaceValue()) + v = value(f) + } + return valueInterfaceValue(v) + } +} + +func vInt(v reflect.Value) (i int64) { + if c := vConstantValue(v); c != nil { + i, _ = constant.Int64Val(constant.ToInt(c)) + return i + } + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i = v.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + i = int64(v.Uint()) + case reflect.Float32, reflect.Float64: + i = int64(v.Float()) + case reflect.Complex64, reflect.Complex128: + i = int64(real(v.Complex())) + } + return +} + +func vUint(v reflect.Value) (i uint64) { + if c := vConstantValue(v); c != nil { + i, _ = constant.Uint64Val(constant.ToInt(c)) + return i + } + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i = uint64(v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + i = v.Uint() + case reflect.Float32, reflect.Float64: + i = uint64(v.Float()) + case reflect.Complex64, reflect.Complex128: + i = uint64(real(v.Complex())) + } + return +} + +func vComplex(v reflect.Value) (c complex128) { + if c := vConstantValue(v); c != nil { + c = constant.ToComplex(c) + rel, _ := constant.Float64Val(constant.Real(c)) + img, _ := constant.Float64Val(constant.Imag(c)) + return complex(rel, img) + } + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + c = complex(float64(v.Int()), 0) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + c = complex(float64(v.Uint()), 0) + case reflect.Float32, reflect.Float64: + c = complex(v.Float(), 0) + case reflect.Complex64, reflect.Complex128: + c = v.Complex() + } + return +} + +func vFloat(v reflect.Value) (i float64) { + if c := vConstantValue(v); c != nil { + i, _ = constant.Float64Val(constant.ToFloat(c)) + return i + } + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i = float64(v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + i = float64(v.Uint()) + case reflect.Float32, reflect.Float64: + i = v.Float() + case reflect.Complex64, reflect.Complex128: + i = real(v.Complex()) + } + return +} + +func vString(v reflect.Value) (s string) { + if c := vConstantValue(v); c != nil { + s = constant.StringVal(c) + return s + } + return v.String() +} + +func vConstantValue(v reflect.Value) (c constant.Value) { + if v.Type().Implements(constVal) { + c = v.Interface().(constant.Value) + } + return +} + +func genValueInt(n *node) func(*frame) (reflect.Value, int64) { + value := genValue(n) + + switch n.typ.TypeOf().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(f *frame) (reflect.Value, int64) { v := value(f); return v, v.Int() } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return func(f *frame) (reflect.Value, int64) { v := value(f); return v, int64(v.Uint()) } + case reflect.Float32, reflect.Float64: + return func(f *frame) (reflect.Value, int64) { v := value(f); return v, int64(v.Float()) } + case reflect.Complex64, reflect.Complex128: + if n.typ.untyped && n.rval.IsValid() && imag(n.rval.Complex()) == 0 { + return func(f *frame) (reflect.Value, int64) { v := value(f); return v, int64(real(v.Complex())) } + } + } + return nil +} + +func genValueUint(n *node) func(*frame) (reflect.Value, uint64) { + value := genValue(n) + + switch n.typ.TypeOf().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(f *frame) (reflect.Value, uint64) { v := value(f); return v, uint64(v.Int()) } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return func(f *frame) (reflect.Value, uint64) { v := value(f); return v, v.Uint() } + case reflect.Float32, reflect.Float64: + return func(f *frame) (reflect.Value, uint64) { v := value(f); return v, uint64(v.Float()) } + case reflect.Complex64, reflect.Complex128: + if n.typ.untyped && n.rval.IsValid() && imag(n.rval.Complex()) == 0 { + return func(f *frame) (reflect.Value, uint64) { v := value(f); return v, uint64(real(v.Complex())) } + } + } + return nil +} + +func genValueFloat(n *node) func(*frame) (reflect.Value, float64) { + value := genValue(n) + + switch n.typ.TypeOf().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(f *frame) (reflect.Value, float64) { v := value(f); return v, float64(v.Int()) } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return func(f *frame) (reflect.Value, float64) { v := value(f); return v, float64(v.Uint()) } + case reflect.Float32, reflect.Float64: + return func(f *frame) (reflect.Value, float64) { v := value(f); return v, v.Float() } + case reflect.Complex64, reflect.Complex128: + if n.typ.untyped && n.rval.IsValid() && imag(n.rval.Complex()) == 0 { + return func(f *frame) (reflect.Value, float64) { v := value(f); return v, real(v.Complex()) } + } + } + return nil +} + +func genValueComplex(n *node) func(*frame) reflect.Value { + vc := genComplex(n) + return func(f *frame) reflect.Value { return reflect.ValueOf(vc(f)) } +} + +func genComplex(n *node) func(*frame) complex128 { + value := genValue(n) + + switch n.typ.TypeOf().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return func(f *frame) complex128 { return complex(float64(value(f).Int()), 0) } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return func(f *frame) complex128 { return complex(float64(value(f).Uint()), 0) } + case reflect.Float32, reflect.Float64: + return func(f *frame) complex128 { return complex(value(f).Float(), 0) } + case reflect.Complex64, reflect.Complex128: + return func(f *frame) complex128 { return value(f).Complex() } + } + return nil +} + +func genValueString(n *node) func(*frame) (reflect.Value, string) { + value := genValue(n) + + return func(f *frame) (reflect.Value, string) { v := value(f); return v, v.String() } +} diff --git a/internal/yaegi/unsafe2/go1_20_unsafe.go b/internal/yaegi/unsafe2/go1_20_unsafe.go new file mode 100644 index 00000000..c437c013 --- /dev/null +++ b/internal/yaegi/unsafe2/go1_20_unsafe.go @@ -0,0 +1,65 @@ +//go:build !go1.21 +// +build !go1.21 + +// Package unsafe2 provides helpers to generate recursive struct types. +package unsafe2 + +import ( + "reflect" + "unsafe" +) + +type dummy struct{} + +// DummyType represents a stand-in for a recursive type. +var DummyType = reflect.TypeOf(dummy{}) + +// the following type sizes must match their original definition in Go src/reflect/type.go. + +type rtype struct { + _ uintptr + _ uintptr + _ uint32 + _ uint32 + _ uintptr + _ uintptr + _ uint32 + _ uint32 +} + +type emptyInterface struct { + typ *rtype + _ unsafe.Pointer +} + +type structField struct { + _ uintptr + typ *rtype + _ uintptr +} + +type structType struct { + rtype + _ uintptr + fields []structField +} + +// SetFieldType sets the type of the struct field at the given index, to the given type. +// +// The struct type must have been created at runtime. This is very unsafe. +func SetFieldType(s reflect.Type, idx int, t reflect.Type) { + if s.Kind() != reflect.Struct || idx >= s.NumField() { + return + } + + rtyp := unpackType(s) + styp := (*structType)(unsafe.Pointer(rtyp)) + f := styp.fields[idx] + f.typ = unpackType(t) + styp.fields[idx] = f +} + +func unpackType(t reflect.Type) *rtype { + v := reflect.New(t).Elem().Interface() + return (*emptyInterface)(unsafe.Pointer(&v)).typ +} diff --git a/internal/yaegi/unsafe2/go1_21_unsafe.go b/internal/yaegi/unsafe2/go1_21_unsafe.go new file mode 100644 index 00000000..cf0460d8 --- /dev/null +++ b/internal/yaegi/unsafe2/go1_21_unsafe.go @@ -0,0 +1,72 @@ +//go:build go1.21 +// +build go1.21 + +// Package unsafe2 provides helpers to generate recursive struct types. +package unsafe2 + +import ( + "reflect" + "unsafe" +) + +type dummy struct{} + +// DummyType represents a stand-in for a recursive type. +var DummyType = reflect.TypeOf(dummy{}) + +// The following type sizes must match their original definition in Go src/internal/abi/type.go. +type abiType struct { + _ uintptr + _ uintptr + _ uint32 + _ uint8 + _ uint8 + _ uint8 + _ uint8 + _ uintptr + _ uintptr + _ int32 + _ int32 +} + +type abiName struct { + Bytes *byte +} + +type abiStructField struct { + Name abiName + Typ *abiType + Offset uintptr +} + +type abiStructType struct { + abiType + PkgPath abiName + Fields []abiStructField +} + +type emptyInterface struct { + typ *abiType + _ unsafe.Pointer +} + +// SetFieldType sets the type of the struct field at the given index, to the given type. +// +// The struct type must have been created at runtime. This is very unsafe. +func SetFieldType(s reflect.Type, idx int, t reflect.Type) { + if s.Kind() != reflect.Struct || idx >= s.NumField() { + return + } + + rtyp := unpackType(s) + styp := (*abiStructType)(unsafe.Pointer(rtyp)) + f := styp.Fields[idx] + f.Typ = unpackType(t) + styp.Fields[idx] = f +} + +func unpackType(t reflect.Type) *abiType { + v := reflect.New(t).Elem().Interface() + eface := *(*emptyInterface)(unsafe.Pointer(&v)) + return eface.typ +} diff --git a/pkg/extensions/aliases_test.go b/pkg/extensions/aliases_test.go index 3f03b31d..dc937fb4 100644 --- a/pkg/extensions/aliases_test.go +++ b/pkg/extensions/aliases_test.go @@ -17,6 +17,7 @@ import ( var internalFiles = []string{ "events.go", "api.go", + "providers.go", "runner.go", "subagent.go", } @@ -28,6 +29,7 @@ var notExtensionFacing = map[string]bool{ // Runner bookkeeping: carries a live handler func, never constructed by // a test. "ShortcutEntry": true, + "ProviderOwner": true, } // TestEveryExtensionFacingTypeIsAliased fails when internal/extensions grows a diff --git a/pkg/extensions/providers.go b/pkg/extensions/providers.go new file mode 100644 index 00000000..71ad417f --- /dev/null +++ b/pkg/extensions/providers.go @@ -0,0 +1,145 @@ +package extensions + +import internalext "github.com/mark3labs/kit/internal/extensions" + +// ProviderDef is the concrete extension provider protocol type. +type ProviderDef = internalext.ProviderDef + +// ProviderCapabilities is the concrete extension provider protocol type. +type ProviderCapabilities = internalext.ProviderCapabilities + +// ProviderModel is the concrete extension provider protocol type. +type ProviderModel = internalext.ProviderModel + +// ProviderPrice is the concrete extension provider protocol type. +type ProviderPrice = internalext.ProviderPrice + +// ProviderAuth is the concrete extension provider protocol type. +type ProviderAuth = internalext.ProviderAuth + +// ProviderAuthMode is the concrete extension provider protocol type. +type ProviderAuthMode = internalext.ProviderAuthMode + +// ProviderAuthRequest is the concrete extension provider protocol type. +type ProviderAuthRequest = internalext.ProviderAuthRequest + +// ProviderAuthResult is the concrete extension provider protocol type. +type ProviderAuthResult = internalext.ProviderAuthResult + +// ProviderAuthStatus is the concrete extension provider protocol type. +type ProviderAuthStatus = internalext.ProviderAuthStatus + +// ProviderCreateRequest is the concrete extension provider protocol type. +type ProviderCreateRequest = internalext.ProviderCreateRequest + +// ProviderCreateResult is the concrete extension provider protocol type. +type ProviderCreateResult = internalext.ProviderCreateResult + +// ProviderRuntime is the concrete extension provider protocol type. +type ProviderRuntime = internalext.ProviderRuntime + +// ProviderResult is the concrete extension provider protocol type. +type ProviderResult = internalext.ProviderResult + +// ProviderError is the concrete extension provider protocol type. +type ProviderError = internalext.ProviderError + +// ProviderParameters is the concrete extension provider protocol type. +type ProviderParameters = internalext.ProviderParameters + +// ProviderRequest is the concrete extension provider protocol type. +type ProviderRequest = internalext.ProviderRequest + +// ProviderTool is the concrete extension provider protocol type. +type ProviderTool = internalext.ProviderTool + +// ProviderToolChoice is the concrete extension provider protocol type. +type ProviderToolChoice = internalext.ProviderToolChoice + +// ProviderToolChoiceType is the concrete extension provider protocol type. +type ProviderToolChoiceType = internalext.ProviderToolChoiceType + +// ProviderReplayData is the concrete extension provider protocol type. +type ProviderReplayData = internalext.ProviderReplayData + +// ProviderMessage is the concrete extension provider protocol type. +type ProviderMessage = internalext.ProviderMessage + +// ProviderPart is the concrete extension provider protocol type. +type ProviderPart = internalext.ProviderPart + +// ProviderPartType is the concrete extension provider protocol type. +type ProviderPartType = internalext.ProviderPartType + +// ProviderEvent is the concrete extension provider protocol type. +type ProviderEvent = internalext.ProviderEvent + +// ProviderEventType is the concrete extension provider protocol type. +type ProviderEventType = internalext.ProviderEventType + +// ProviderFinishReason is the concrete extension provider protocol type. +type ProviderFinishReason = internalext.ProviderFinishReason + +// ProviderUsage is the concrete extension provider protocol type. +type ProviderUsage = internalext.ProviderUsage + +const ( + // ProviderAuthNone is the corresponding extension protocol value. + ProviderAuthNone = internalext.ProviderAuthNone + // ProviderAuthAPIKey is the corresponding extension protocol value. + ProviderAuthAPIKey = internalext.ProviderAuthAPIKey + // ProviderAuthExternal is the corresponding extension protocol value. + ProviderAuthExternal = internalext.ProviderAuthExternal + // ProviderAuthConfigured is the corresponding extension protocol value. + ProviderAuthConfigured = internalext.ProviderAuthConfigured + // ProviderAuthUnconfigured is the corresponding extension protocol value. + ProviderAuthUnconfigured = internalext.ProviderAuthUnconfigured + // ProviderAuthUnknown is the corresponding extension protocol value. + ProviderAuthUnknown = internalext.ProviderAuthUnknown + // ProviderToolAuto is the corresponding extension protocol value. + ProviderToolAuto = internalext.ProviderToolAuto + // ProviderToolNone is the corresponding extension protocol value. + ProviderToolNone = internalext.ProviderToolNone + // ProviderToolRequired is the corresponding extension protocol value. + ProviderToolRequired = internalext.ProviderToolRequired + // ProviderToolNamed is the corresponding extension protocol value. + ProviderToolNamed = internalext.ProviderToolNamed + // ProviderPartText is the corresponding extension protocol value. + ProviderPartText = internalext.ProviderPartText + // ProviderPartReasoning is the corresponding extension protocol value. + ProviderPartReasoning = internalext.ProviderPartReasoning + // ProviderPartFile is the corresponding extension protocol value. + ProviderPartFile = internalext.ProviderPartFile + // ProviderPartToolCall is the corresponding extension protocol value. + ProviderPartToolCall = internalext.ProviderPartToolCall + // ProviderPartToolResult is the corresponding extension protocol value. + ProviderPartToolResult = internalext.ProviderPartToolResult + // ProviderTextStart is the corresponding extension protocol value. + ProviderTextStart = internalext.ProviderTextStart + // ProviderTextDelta is the corresponding extension protocol value. + ProviderTextDelta = internalext.ProviderTextDelta + // ProviderTextEnd is the corresponding extension protocol value. + ProviderTextEnd = internalext.ProviderTextEnd + // ProviderReasoningStart is the corresponding extension protocol value. + ProviderReasoningStart = internalext.ProviderReasoningStart + // ProviderReasoningDelta is the corresponding extension protocol value. + ProviderReasoningDelta = internalext.ProviderReasoningDelta + // ProviderReasoningEnd is the corresponding extension protocol value. + ProviderReasoningEnd = internalext.ProviderReasoningEnd + // ProviderToolStart is the corresponding extension protocol value. + ProviderToolStart = internalext.ProviderToolStart + // ProviderToolDelta is the corresponding extension protocol value. + ProviderToolDelta = internalext.ProviderToolDelta + // ProviderToolEnd is the corresponding extension protocol value. + ProviderToolEnd = internalext.ProviderToolEnd + // ProviderUsageEvent is the corresponding extension protocol value. + ProviderUsageEvent = internalext.ProviderUsageEvent + // ProviderFinish is the corresponding extension protocol value. + ProviderFinish = internalext.ProviderFinish + // ProviderFinishStop is the corresponding extension protocol value. + ProviderFinishStop = internalext.ProviderFinishStop + // ProviderFinishLength is the corresponding extension protocol value. + ProviderFinishLength = internalext.ProviderFinishLength + // ProviderFinishToolCalls is the corresponding extension protocol value. + ProviderFinishToolCalls = internalext.ProviderFinishToolCalls +) diff --git a/pkg/extensions/test/harness.go b/pkg/extensions/test/harness.go index 9a448343..829f9663 100644 --- a/pkg/extensions/test/harness.go +++ b/pkg/extensions/test/harness.go @@ -50,8 +50,8 @@ import ( "testing" internalext "github.com/mark3labs/kit/internal/extensions" + "github.com/mark3labs/kit/internal/yaegi/interp" "github.com/mark3labs/kit/pkg/extensions" - "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" "github.com/traefik/yaegi/stdlib/unrestricted" ) @@ -147,6 +147,9 @@ func (h *Harness) loadSource(src string, path string) *extensions.LoadedExtensio // Call Init to register handlers initFn(api) + if len(ext.RegistrationErrors) > 0 { + h.t.Fatalf("provider registration failed: %v", ext.RegistrationErrors) + } // Create runner with the loaded extension h.runner = internalext.NewRunner([]extensions.LoadedExtension{*ext}) diff --git a/pkg/extensions/test/provider_test.go b/pkg/extensions/test/provider_test.go new file mode 100644 index 00000000..8ef3aa86 --- /dev/null +++ b/pkg/extensions/test/provider_test.go @@ -0,0 +1,34 @@ +package test + +import ( + "testing" + + ext "github.com/mark3labs/kit/pkg/extensions" +) + +func TestProviderCallbacksThroughPublicHarness(t *testing.T) { + h := New(t) + loaded := h.LoadFile("../../../examples/extensions/fake-provider.go") + if len(loaded.Providers) != 1 { + t.Fatalf("providers=%d", len(loaded.Providers)) + } + created := loaded.Providers[0].Create(ext.ProviderCreateRequest{Model: "echo"}) + if created.Error != nil { + t.Fatal(created.Error) + } + text := "" + finished := false + result := created.Runtime.Stream(ext.ProviderRequest{}, func(event ext.ProviderEvent) bool { + text += event.Delta + if event.Type == ext.ProviderFinish { + finished = true + } + return true + }) + if result.Error != nil || text != "Hello from a fake provider." || !finished { + t.Fatalf("text=%q finish=%v error=%v", text, finished, result.Error) + } + if err := created.Runtime.Close(); err != nil { + t.Fatal(err) + } +} diff --git a/pkg/kit/decide.go b/pkg/kit/decide.go index 1dde82ec..92acea13 100644 --- a/pkg/kit/decide.go +++ b/pkg/kit/decide.go @@ -176,6 +176,9 @@ func Decide(ctx context.Context, req DecisionRequest) (*DecisionResult, error) { // and transient provider failures twice. Errors from the provider are // classified like other provider errors (see ClassifyProviderError). func (m *Kit) Decide(ctx context.Context, req DecisionRequest) (*DecisionResult, error) { + if provider, _, err := models.ParseModelString(req.Model); err == nil && m.instanceResolver().Owns(provider) { + return nil, fmt.Errorf("%w: extension providers do not support decisions", ErrNotDecisionModel) + } cfg := &models.ProviderConfig{ ModelString: req.Model, TLSSkipVerify: m.v.GetBool("tls-skip-verify"), diff --git a/pkg/kit/errors.go b/pkg/kit/errors.go index ca55088e..6856e26f 100644 --- a/pkg/kit/errors.go +++ b/pkg/kit/errors.go @@ -64,6 +64,19 @@ func ClassifyProviderError(err error) error { } } + var extension interface{ ExtensionErrorCode() string } + if errors.As(err, &extension) { + switch extension.ExtensionErrorCode() { + case "auth": + return wrapSentinel(ErrAuth, err) + case "rate_limit": + return wrapSentinel(ErrRateLimit, err) + case "transport": + return wrapSentinel(ErrProviderUnavailable, err) + case "unsupported", "protocol", "invalid_request": + return wrapSentinel(ErrInvalidRequest, err) + } + } if isRequestTooLarge(err) { return wrapSentinel(ErrContextOverflow, err) } diff --git a/pkg/kit/extension_provider_reload_test.go b/pkg/kit/extension_provider_reload_test.go new file mode 100644 index 00000000..e30e10a5 --- /dev/null +++ b/pkg/kit/extension_provider_reload_test.go @@ -0,0 +1,48 @@ +package kit + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestExtensionProviderReloadRetainsOldRuntimeAndRollsBackFailure(t *testing.T) { + source, err := os.ReadFile(filepath.Join("..", "..", "examples", "extensions", "fake-provider.go")) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), "provider.go") + if err := os.WriteFile(path, source, 0600); err != nil { + t.Fatal(err) + } + // Reuse the real fixture's Kit setup, then point reload at the mutable copy. + k := extensionProviderKit(t, "fake-provider.go", "fake/echo", nil) + k.v.Set("extension", []string{path}) + if err := os.WriteFile(path, []byte("package main\nfunc broken syntax"), 0600); err != nil { + t.Fatal(err) + } + if err := k.ReloadExtensions(); err == nil { + t.Fatal("invalid candidate accepted") + } + got, err := k.Prompt(context.Background(), "old works") + if err != nil || got != "Hello from a fake provider." { + t.Fatalf("old=%q err=%v", got, err) + } + replacement := strings.ReplaceAll(string(source), "Hello from a fake provider.", "New generation.") + if err := os.WriteFile(path, []byte(replacement), 0600); err != nil { + t.Fatal(err) + } + if err := k.ReloadExtensions(); err != nil { + t.Fatal(err) + } + got, err = k.Prompt(context.Background(), "existing runtime") + if err != nil || got != "Hello from a fake provider." { + t.Fatalf("retained=%q err=%v", got, err) + } + result, err := k.ExecuteCompletion(context.Background(), CompleteRequest{Model: "fake/echo", Prompt: "new runtime"}) + if err != nil || result.Text != "New generation." { + t.Fatalf("new=%+v err=%v", result, err) + } +} diff --git a/pkg/kit/extension_providers.go b/pkg/kit/extension_providers.go new file mode 100644 index 00000000..5e6653c5 --- /dev/null +++ b/pkg/kit/extension_providers.go @@ -0,0 +1,135 @@ +package kit + +import ( + "os" + "slices" + "strings" + + "github.com/mark3labs/kit/internal/auth" + "github.com/mark3labs/kit/internal/models" +) + +// LookupModel returns metadata in this Kit's catalog, including its extensions. +// The package-level LookupModel continues to query only the process catalog. +func (m *Kit) LookupModel(provider, modelID string) *ModelInfo { + m.providerMu.RLock() + resolver := m.resolver + m.providerMu.RUnlock() + return resolver.Lookup(provider, modelID) +} + +// GetModelsForProvider returns this Kit's declared models. Unknown-model lookup +// never inserts an undeclared name in this list. +func (m *Kit) GetModelsForProvider(provider string) (map[string]ModelInfo, error) { + if m.instanceResolver().Owns(provider) { + return m.instanceResolver().List()[strings.ToLower(provider)], nil + } + return GetModelsForProvider(provider) +} + +// GetLLMProviders lists providers in this Kit's catalog. +func (m *Kit) GetLLMProviders() []string { + result := GetLLMProviders() + for provider := range m.instanceResolver().List() { + result = append(result, provider) + } + slices.Sort(result) + return slices.Compact(result) +} + +// GetModelCapabilities resolves capabilities in the owning Kit catalog. +func (m *Kit) GetModelCapabilities(model string) (ModelCapabilities, string) { + if model == "" { + model = m.GetCurrentModel() + } + provider, id, err := ParseModelString(model) + if err != nil { + return ModelCapabilities{}, err.Error() + } + info := m.LookupModel(provider, id) + if info == nil { + return ModelCapabilities{}, "model not found in instance catalog" + } + streaming := true + if info.CapabilitiesKnown { + streaming = info.Streaming + } + return ModelCapabilities{Tools: info.Tools, CapabilitiesKnown: info.CapabilitiesKnown, ContextLimitKnown: info.Limit.Context > 0, OutputLimitKnown: info.Limit.Output > 0, Provider: provider, ModelID: id, ContextLimit: info.Limit.Context, OutputLimit: info.Limit.Output, Reasoning: info.Reasoning, Streaming: streaming, Pricing: modelPricingFrom(info)}, "" +} + +// CheckModelAvailable checks identity and metadata in this instance. +func (m *Kit) CheckModelAvailable(model string) bool { + provider, _, err := ParseModelString(model) + if err != nil { + return false + } + if m.instanceResolver().Owns(provider) { + if m.instanceResolver().Validate(model) != nil { + return false + } + if m.extRunner != nil { + for _, owner := range m.extRunner.Extensions() { + for _, def := range owner.Providers { + if def.ID == provider { + configured := false + if m.endpointOverridesApply(model) && m.v.GetString("provider-api-key") != "" { + configured = true + } + if !configured { + if manager, err := auth.NewCredentialManager(); err == nil { + key, _ := manager.GetProviderAPIKey(provider) + configured = key != "" + } + } + if !configured { + for _, variable := range def.Auth.Environment { + if os.Getenv(variable) != "" { + configured = true + break + } + } + } + return def.AuthStatus(configured).Status == "configured" + } + } + } + } + return false + } + return CheckModelAvailable(model) +} + +// ResolveModelChain selects the first valid model in the instance catalog. +func (m *Kit) ResolveModelChain(preferences []string) ModelResolutionResult { + result := ModelResolutionResult{} + for _, model := range preferences { + result.Attempted = append(result.Attempted, model) + if !m.CheckModelAvailable(model) { + continue + } + caps, err := m.GetModelCapabilities(model) + if err != "" { + continue + } + result.Model = model + result.Capabilities = caps + return result + } + result.Error = "no models in chain are available" + return result +} + +// providerSnapshot never holds a publication lock while invoking callbacks. +func (m *Kit) providerSnapshot() (*models.Resolver, map[string]ProviderFactory) { + m.providerMu.RLock() + defer m.providerMu.RUnlock() + return m.resolver, m.providers +} +func (m *Kit) instanceResolver() *models.Resolver { + resolver, _ := m.providerSnapshot() + return resolver +} +func (m *Kit) instanceFactories() map[string]ProviderFactory { + _, factories := m.providerSnapshot() + return factories +} diff --git a/pkg/kit/extension_providers_test.go b/pkg/kit/extension_providers_test.go new file mode 100644 index 00000000..a9e218dd --- /dev/null +++ b/pkg/kit/extension_providers_test.go @@ -0,0 +1,113 @@ +package kit + +import ( + "context" + "fmt" + "os" + "path/filepath" + "slices" + "sync/atomic" + "testing" + "time" +) + +func extensionProviderKit(t *testing.T, example, model string, extra []Tool) *Kit { + t.Helper() + source, err := filepath.Abs(filepath.Join("..", "..", "examples", "extensions", example)) + if err != nil { + t.Fatal(err) + } + config := filepath.Join(t.TempDir(), "config.yml") + if err := os.WriteFile(config, []byte(fmt.Sprintf("extension: [%q]\nno-skills: true\nno-agents: true\n", source)), 0600); err != nil { + t.Fatal(err) + } + k, err := New(context.Background(), &Options{Bare: true, ConfigFile: config, Model: model, Quiet: true, NoSession: true, DisableCoreTools: true, ExtraTools: extra}) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := k.Close(); err != nil { + t.Error(err) + } + }) + return k +} + +func TestExtensionProviderGreetingThroughKit(t *testing.T) { + before := RegisteredProviders() + k := extensionProviderKit(t, "fake-provider.go", "fake/echo", nil) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + got, err := k.Prompt(ctx, "hello") + if err != nil || got != "Hello from a fake provider." { + t.Fatalf("got=%q err=%v", got, err) + } + for _, model := range []string{"", "fake/echo"} { + response, err := k.ExecuteCompletion(ctx, CompleteRequest{Model: model, Prompt: "hello"}) + if err != nil || response.Text != "Hello from a fake provider." { + t.Fatalf("completion=%+v err=%v", response, err) + } + } + child, err := k.Subagent(ctx, SubagentConfig{Prompt: "hello", Model: "fake/echo", Tools: []Tool{}, NoSession: true}) + if err != nil || child.Response != "Hello from a fake provider." { + t.Fatalf("child=%+v err=%v", child, err) + } + got, err = k.Prompt(ctx, "parent still works") + if err != nil || got != "Hello from a fake provider." { + t.Fatalf("parent after child=%q err=%v", got, err) + } + if !slices.Equal(before, RegisteredProviders()) { + t.Fatal("extension changed global provider factories") + } + if LookupModel("fake", "echo") != nil { + t.Fatal("extension changed global catalog") + } +} + +func TestExtensionProviderToolRoundTrip(t *testing.T) { + var executions atomic.Int32 + var beforeCalls, afterCalls atomic.Int32 + type input struct { + A int `json:"a"` + B int `json:"b"` + } + tool := NewTool("add", "add numbers", func(_ context.Context, args input) (ToolOutput, error) { + executions.Add(1) + return ToolOutput{Content: fmt.Sprint(args.A + args.B)}, nil + }) + k := extensionProviderKit(t, "fake-tool-provider.go", "fake-tool/add", []Tool{tool}) + k.OnBeforeToolCall(HookPriorityNormal, func(BeforeToolCallHook) *BeforeToolCallResult { beforeCalls.Add(1); return nil }) + k.OnAfterToolResult(HookPriorityNormal, func(AfterToolResultHook) *AfterToolResultResult { afterCalls.Add(1); return nil }) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + got, err := k.Prompt(ctx, "add 2 and 3") + if err != nil || got != "5" { + t.Fatalf("got=%q err=%v", got, err) + } + if beforeCalls.Load() != 1 || afterCalls.Load() != 1 { + t.Fatalf("hook calls before=%d after=%d", beforeCalls.Load(), afterCalls.Load()) + } + if executions.Load() != 1 { + t.Fatalf("executions=%d", executions.Load()) + } + calls, results := 0, 0 + for _, msg := range k.GetStructuredMessages() { + for _, part := range msg.Parts { + switch item := part.(type) { + case ToolCall: + if item.ID != "add-call" || item.Input != `{"a":2,"b":3}` { + t.Fatalf("call=%+v", item) + } + calls++ + case ToolResult: + if calls != 1 || item.ToolCallID != "add-call" || item.Content != "5" { + t.Fatalf("result=%+v calls=%d", item, calls) + } + results++ + } + } + } + if calls != 1 || results != 1 { + t.Fatalf("stored calls=%d results=%d", calls, results) + } +} diff --git a/pkg/kit/extensions_bridge.go b/pkg/kit/extensions_bridge.go index 2de907da..8ede1d18 100644 --- a/pkg/kit/extensions_bridge.go +++ b/pkg/kit/extensions_bridge.go @@ -609,7 +609,7 @@ func llmUsageMeta(m *Kit, usage LLMUsage) (provider, modelID string, cost float6 return "", "", 0 } provider, modelID = p, id - info := models.GetGlobalRegistry().LookupModel(provider, modelID) + info := m.LookupModel(provider, modelID) if info == nil { return provider, modelID, 0 } diff --git a/pkg/kit/kit.go b/pkg/kit/kit.go index 48c966bc..289c9a2d 100644 --- a/pkg/kit/kit.go +++ b/pkg/kit/kit.go @@ -87,8 +87,12 @@ type Kit struct { // providers is this instance's snapshot of Options.Providers, keyed by // normalized provider name. Taken in New so later changes to the caller's // map do not affect this Kit. Nil when no instance factories are set. - providers map[string]ProviderFactory - httpTransport func(http.RoundTripper) http.RoundTripper + providerMu sync.RWMutex + retiredProviderOwners []*extensions.ProviderOwner + providerClosing bool + resolver *models.Resolver + providers map[string]ProviderFactory + httpTransport func(http.RoundTripper) http.RoundTripper // activeModelFromFactory is true when the active model was built by a // ProviderFactory. Set in New and SetModel. activeModelFromFactory bool @@ -816,6 +820,7 @@ func (m *Kit) setModelLocked(ctx context.Context, modelString string) error { return err } + resolver, factories := m.providerSnapshot() // Build a provider config from current settings, overriding the model. thinkingLevel := models.ParseThinkingLevel(m.v.GetString("thinking-level")) @@ -835,6 +840,12 @@ func (m *Kit) setModelLocked(ctx context.Context, modelString string) error { } } + if provider, id, err := ParseModelString(modelString); err == nil && resolver.Owns(provider) && thinkingLevel != models.ThinkingOff && modelString != m.modelString { + info := resolver.Lookup(provider, id) + if info == nil || !slices.Contains(info.ReasoningLevels, string(thinkingLevel)) { + thinkingLevel = models.ThinkingOff + } + } // With message-level caching, thinking and caching can work together. // No need to disable caching when thinking is enabled. mode := m.modelMode @@ -852,7 +863,8 @@ func (m *Kit) setModelLocked(ctx context.Context, modelString string) error { ConfigStore: m.v, SessionIDFunc: m.GetSessionID, // Instance provider factories (snapshot of Options.Providers). - ProviderFactories: m.providers, + Resolver: resolver, + ProviderFactories: factories, HTTPTransport: m.httpTransport, } m.applyEffectiveProviderSettings(cfg, modelString) @@ -1053,15 +1065,15 @@ func (m *Kit) GetCurrentModel() string { // not in the registry can still be used by specifying their provider/model // string. func (m *Kit) GetAvailableModels() []ModelInfoEntry { - registry := models.GetGlobalRegistry() var result []ModelInfoEntry - for _, providerID := range registry.GetLLMProviders() { - modelsMap, err := registry.GetModelsForProvider(providerID) + for _, providerID := range m.GetLLMProviders() { + modelsMap, err := m.GetModelsForProvider(providerID) if err != nil { continue } for modelID, info := range modelsMap { result = append(result, extensions.ModelInfoEntry{ + InputModalities: slices.Clone(info.InputModalities), Tools: info.Tools, Streaming: info.Streaming, CapabilitiesKnown: info.CapabilitiesKnown, ContextLimitKnown: info.Limit.Context > 0, OutputLimitKnown: info.Limit.Output > 0, Provider: providerID, ModelID: modelID, Name: info.Name, @@ -1083,22 +1095,57 @@ func (m *Kit) ReloadExtensions() error { return fmt.Errorf("no extensions loaded") } - // Emit shutdown to old extensions. - if m.extRunner.HasHandlers(extensions.SessionShutdown) { - _, _ = m.extRunner.Emit(extensions.SessionShutdownEvent{}) + for _, owner := range m.extRunner.Extensions() { + if err := owner.Owner.CheckReentrant(); err != nil { + return err + } } - // Re-load from disk. Bare mode is preserved across the reload so a hot // reload cannot pull in directory-discovered extensions that startup // deliberately skipped. extraPaths := m.v.GetStringSlice("extension") - loaded, err := extensions.LoadExtensionsScoped(extraPaths, m.opts != nil && m.opts.Bare) + loaded, err := extensions.LoadExtensionsStrict(extraPaths, m.opts != nil && m.opts.Bare) if err != nil { return fmt.Errorf("reloading extensions: %w", err) } - // Swap extensions on the runner (clears dynamic state). + priorResolver, priorFactories := m.providerSnapshot() + baseFactories := make(map[string]ProviderFactory) + for id, factory := range priorFactories { + if !priorResolver.Owns(id) { + baseFactories[id] = factory + } + } + factories, err := kitsetup.PrepareProviderFactories(loaded, baseFactories) + if err != nil { + return fmt.Errorf("reloading extension providers: %w", err) + } + resolver := kitsetup.PrepareProviderResolver(loaded) + old := m.extRunner.Extensions() + oldContext := m.extRunner.GetContext() + // Candidate validation is complete. Runtime owners retain old callbacks. + m.providerMu.Lock() + if m.providerClosing || m.resolver != priorResolver { + m.providerMu.Unlock() + return errors.New("extension provider generation changed during reload; retry reload") + } + m.providers = factories + m.resolver = resolver + for _, owner := range old { + if owner.Owner != nil { + m.retiredProviderOwners = append(m.retiredProviderOwners, owner.Owner) + } + } m.extRunner.Reload(loaded) + m.providerMu.Unlock() + for _, owner := range old { + retired := owner + retired.Owner.Retire(func() { + runner := extensions.NewRunner([]extensions.LoadedExtension{retired}) + runner.SetContext(oldContext) + _, _ = runner.Emit(extensions.SessionShutdownEvent{}) + }) + } m.extRunner.SetConfigStore(m.v) // Update extension tools on the agent so the LLM sees changes. @@ -1148,6 +1195,7 @@ func (m *Kit) ExecuteCompletion(ctx context.Context, req CompleteRequest) (Compl providerOps = m.agent.ProviderOptions() } } else { + resolver, factories := m.providerSnapshot() // Create a temporary provider for the requested model. config := &models.ProviderConfig{ UserAgent: m.v.GetString("internal-user-agent"), @@ -1156,7 +1204,8 @@ func (m *Kit) ExecuteCompletion(ctx context.Context, req CompleteRequest) (Compl ConfigStore: m.v, SessionIDFunc: m.GetSessionID, // Instance provider factories (snapshot of Options.Providers). - ProviderFactories: m.providers, + Resolver: resolver, + ProviderFactories: factories, HTTPTransport: m.httpTransport, } if req.MaxTokens > 0 { @@ -1270,6 +1319,8 @@ func (m *Kit) ExecuteCompletion(ctx context.Context, req CompleteRequest) (Compl // through to the precedence chain (env → .kit.yml → per-model defaults) // resolved within the instance's own store. type Options struct { + // inheritedResolver retains parent provider metadata without discovery. + inheritedResolver *models.Resolver // UserAgent identifies Kit-controlled HTTP requests. Empty means "KIT". // Codex and Copilot inference retain their fixed compatibility identities. UserAgent string @@ -1784,10 +1835,14 @@ func New(ctx context.Context, opts *Options) (*Kit, error) { return nil, fmt.Errorf("invalid Options.Providers: %w", err) } + if providers == nil { + providers = make(map[string]ProviderFactory) + } v, rc, err := resolveConfig(opts, providers) if err != nil { return nil, err } + providers = rc.providerConfig.ProviderFactories // ---- config snapshot complete — heavy I/O below ---- mcpConfig, err := resolveMCPConfig(v, opts) @@ -1837,6 +1892,7 @@ func New(ctx context.Context, opts *Options) (*Kit, error) { bufferedLogger: agentResult.BufferedLogger, authHandler: setupOpts.AuthHandler, opts: opts, + resolver: rc.providerConfig.Resolver, providers: providers, httpTransport: opts.HTTPTransport, mcpConfig: mcpConfig, @@ -2495,7 +2551,7 @@ func (m *Kit) Subagent(ctx context.Context, cfg SubagentConfig) (*SubagentResult // correcting a typo — instead of waiting for a full Kit.New() cycle // that silently falls back to the parent model. if model != m.modelString && !m.hasInstanceProvider(model) { - if err := models.GetGlobalRegistry().ValidateModelString(model); err != nil { + if err := m.instanceResolver().Validate(model); err != nil { return nil, fmt.Errorf("invalid subagent model %q: %w", model, err) } } @@ -2593,7 +2649,7 @@ func (m *Kit) Subagent(ctx context.Context, cfg SubagentConfig) (*SubagentResult inheritIsolationOptions(childOpts, m.opts) // Propagate instance provider factories so a child can use the same // application-provided backends as the parent. - childOpts.Providers = m.providers + childOpts.inheritedResolver, childOpts.Providers = m.providerSnapshot() // Register the run so that it can be listed and killed. runID, unregister := m.subagents.add(RunningSubagent{ @@ -3686,7 +3742,7 @@ func (m *Kit) GetModelInfo() *ModelInfo { if err != nil { return nil } - return LookupModel(provider, modelID) + return m.LookupModel(provider, modelID) } // IsReasoningModel returns true if the current model supports extended thinking / reasoning. @@ -3716,7 +3772,12 @@ func (m *Kit) SetThinkingLevel(ctx context.Context, level string) error { if err != nil { return err } - if !IsThinkingLevelSupported(provider, modelName, level) { + valid := IsThinkingLevelSupported(provider, modelName, level) + if m.instanceResolver().Owns(provider) { + info := m.LookupModel(provider, modelName) + valid = level == "off" || (info != nil && slices.Contains(info.ReasoningLevels, level)) + } + if !valid { return fmt.Errorf("model %s does not support thinking level %q; supported: %v", m.modelString, level, m.SupportedThinkingLevels()) } previous := m.v.GetString("thinking-level") @@ -3738,6 +3799,13 @@ func (m *Kit) SupportedThinkingLevels() []string { if err != nil { return ThinkingLevels() } + if m.instanceResolver().Owns(provider) { + levels := []string{"off"} + if info := m.LookupModel(provider, modelName); info != nil { + levels = append(levels, info.ReasoningLevels...) + } + return levels + } return SupportedThinkingLevels(provider, modelName) } @@ -3802,9 +3870,52 @@ func (m *Kit) Close() error { // best-effort basis: if it is already done when CloseContext is called, the // context error is returned after a best-effort cleanup pass. func (m *Kit) CloseContext(ctx context.Context) error { - // Emit SessionShutdown for extensions. - if m.extRunner != nil && m.extRunner.HasHandlers(extensions.SessionShutdown) { - _, _ = m.extRunner.Emit(extensions.SessionShutdownEvent{}) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + m.providerMu.Lock() + m.providerClosing = true + owners := slices.Clone(m.retiredProviderOwners) + m.providerMu.Unlock() + if m.extRunner != nil { + for _, owner := range m.extRunner.Extensions() { + owners = append(owners, owner.Owner) + } + } + var providerErrors []error + if m.opts == nil || m.opts.inheritedResolver == nil { + for _, owner := range owners { + if err := owner.CheckReentrant(); err != nil { + return err + } + } + for _, owner := range owners { + if err := owner.CancelOwnedWork(); err != nil { + return err + } + } + } + for _, owner := range owners { + if m.opts != nil && m.opts.inheritedResolver != nil { + break + } // Child agent closes only its own runtime. + if err := owner.CloseRuntimes(ctx); err != nil { + if strings.Contains(err.Error(), "own callback") { + return err + } + providerErrors = append(providerErrors, err) + } + } + // Retire generations first: active runtimes keep their owner until close. + if m.extRunner != nil { + oldContext := m.extRunner.GetContext() + for _, owner := range m.extRunner.Extensions() { + retired := owner + retired.Owner.Retire(func() { + runner := extensions.NewRunner([]extensions.LoadedExtension{retired}) + runner.SetContext(oldContext) + _, _ = runner.Emit(extensions.SessionShutdownEvent{}) + }) + } } if m.session != nil { _ = m.session.Close() @@ -3813,7 +3924,7 @@ func (m *Kit) CloseContext(ctx context.Context) error { if closer, ok := m.authHandler.(interface{ Close() error }); ok { _ = closer.Close() } - err := m.agent.Close() + err := errors.Join(append(providerErrors, m.agent.CloseContext(ctx))...) if ctxErr := ctx.Err(); ctxErr != nil && err == nil { return ctxErr } diff --git a/pkg/kit/new.go b/pkg/kit/new.go index 760883ce..a46c8f3e 100644 --- a/pkg/kit/new.go +++ b/pkg/kit/new.go @@ -23,23 +23,25 @@ import ( // step of the original monolithic constructor, extracted without changing // order or behaviour: // -// 1. resolveConfig — config snapshot, no heavy I/O: +// 1. resolveConfig — config/resources and trusted extension discovery: // a. newConfigStore — pick the per-instance or process-global store // b. loadConfigStore — defaults, config file, env vars // c. applyOptionOverrides — push Options fields into the store // d. loadProjectResources — cwd, AGENTS.md, skills, named agents // e. buildSystemPrompt — base prompt + context + skills + environment -// f. resolveModelConfig — provider config and scalar snapshots +// f. load extensions and validate instance provider definitions +// g. resolveModelConfig — provider config and scalar snapshots // 2. resolveMCPConfig — MCP servers, including in-process ones // 3. withSkillTool, buildAgentSetupOptions, then kitsetup.SetupAgent // 4. initSessionManager // 5. (*Kit).initExtensions // resolvedConfig is the snapshot of instance-derived values that [New] -// computes from the configuration store before any heavy I/O happens. +// computes before model/agent and MCP construction. type resolvedConfig struct { - providerConfig *models.ProviderConfig - modelString string + preparedExtensions []extensions.LoadedExtension + providerConfig *models.ProviderConfig + modelString string cwd string contextFiles []*ContextFile @@ -327,8 +329,8 @@ func buildSystemPrompt(v *viper.Viper, opts *Options, rc *resolvedConfig) { } // resolveConfig builds the instance configuration store and snapshots every -// instance-derived value New needs. It performs no network or subprocess -// I/O; MCP and agent setup happen afterwards. +// instance-derived value New needs. It executes trusted extension top-level +// code and Init; MCP and model/agent setup happen afterwards. func resolveConfig(opts *Options, providers map[string]ProviderFactory) (*viper.Viper, *resolvedConfig, error) { userAgent, err := httpclient.ResolveUserAgent(opts.UserAgent) if err != nil { @@ -346,6 +348,32 @@ func resolveConfig(opts *Options, providers map[string]ProviderFactory) (*viper. return nil, nil, err } buildSystemPrompt(v, opts, rc) + rc.preparedExtensions = []extensions.LoadedExtension{} + if !opts.NoExtensions && !v.GetBool("no-extensions") { + loaded, err := extensions.LoadExtensionsScoped(v.GetStringSlice("extension"), opts.Bare) + if err != nil { + return nil, nil, fmt.Errorf("extension registration: %w", err) + } + rc.preparedExtensions = append(rc.preparedExtensions, loaded...) + providerLoads := loaded + if opts.inheritedResolver != nil { + providerLoads = make([]extensions.LoadedExtension, len(loaded)) + copy(providerLoads, loaded) + for i := range providerLoads { + for _, def := range providerLoads[i].Providers { + if !opts.inheritedResolver.Owns(def.ID) || opts.inheritedResolver.Owner(def.ID) != providerLoads[i].Path { + return nil, nil, fmt.Errorf("child extension %s introduces a different provider owner for %s", providerLoads[i].Path, def.ID) + } + } + providerLoads[i].Providers = nil + } + } + factories, err := kitsetup.PrepareProviderFactories(providerLoads, providers) + if err != nil { + return nil, nil, err + } + providers = factories + } if err := resolveModelConfig(v, opts, providers, rc); err != nil { return nil, nil, err } @@ -408,6 +436,10 @@ func resolveModelConfig(v *viper.Viper, opts *Options, providers map[string]Prov if providerConfig.MaxTokens == 0 && opts.MaxTokens == 0 { providerConfig.MaxTokens = sdkDefaultMaxTokens } + providerConfig.Resolver = kitsetup.PrepareProviderResolver(rc.preparedExtensions) + if opts.inheritedResolver != nil { + providerConfig.Resolver = opts.inheritedResolver + } providerConfig.ProviderFactories = providers providerConfig.HTTPTransport = opts.HTTPTransport if providerConfig.ModelMode != "" { @@ -545,6 +577,7 @@ func buildAgentSetupOptions(v *viper.Viper, opts *Options, rc *resolvedConfig, m WorkDir: opts.WorkDir, CoreToolOptions: coreToolOpts, ToolWrapper: hookToolWrapper(hooks.beforeToolCall, hooks.afterToolResult), + PreparedExtensions: rc.preparedExtensions, ProviderConfig: rc.providerConfig, Debug: rc.debug, DebugLogger: opts.DebugLogger, diff --git a/pkg/kit/overflow.go b/pkg/kit/overflow.go index 13d850d7..3b911a86 100644 --- a/pkg/kit/overflow.go +++ b/pkg/kit/overflow.go @@ -25,6 +25,10 @@ import ( // isContextOverflow reports whether err is (or classifies as) a provider // context-window overflow. func isContextOverflow(err error) bool { + var extension interface{ ExtensionRetryable() bool } + if errors.As(err, &extension) { + return false + } // Extension callbacks are never replayed by overflow recovery. return err != nil && !agent.IsPreparationError(err) && errors.Is(ClassifyProviderError(err), ErrContextOverflow) } diff --git a/pkg/kit/providers.go b/pkg/kit/providers.go index faa21d9e..160389e8 100644 --- a/pkg/kit/providers.go +++ b/pkg/kit/providers.go @@ -141,7 +141,7 @@ func (m *Kit) factoryBacked(modelString string) bool { if err != nil { return false } - return models.HasProviderFactory(&models.ProviderConfig{ProviderFactories: m.providers}, provider) + return models.HasProviderFactory(&models.ProviderConfig{ProviderFactories: m.instanceFactories()}, provider) } // withMaxTokens wraps f so that it receives maxTokens in cfg.MaxTokens, @@ -158,12 +158,12 @@ func withMaxTokens(f ProviderFactory, maxTokens int) ProviderFactory { // hasInstanceProvider reports whether modelString names a provider that has // an instance factory. func (m *Kit) hasInstanceProvider(modelString string) bool { - if len(m.providers) == 0 { + if len(m.instanceFactories()) == 0 { return false } provider, _, err := models.ParseModelString(modelString) if err != nil { return false } - return models.HasProviderFactory(&models.ProviderConfig{ProviderFactories: m.providers}, provider) + return models.HasProviderFactory(&models.ProviderConfig{ProviderFactories: m.instanceFactories()}, provider) } diff --git a/www/pages/extensions/capabilities.md b/www/pages/extensions/capabilities.md index d36bec54..91c89319 100644 --- a/www/pages/extensions/capabilities.md +++ b/www/pages/extensions/capabilities.md @@ -6,6 +6,10 @@ ogImage: /og-image.png # Extension Capabilities +## Model providers + +`api.RegisterProvider(ext.ProviderDef{...})` declares concrete create/stream callbacks and model metadata. Kit remains the sole tool executor. See [model providers](/extensions/providers) for protocol details and remaining integration work. + ## Lifecycle events Extensions can hook into 31 lifecycle events: diff --git a/www/pages/extensions/examples.md b/www/pages/extensions/examples.md index 88e52adb..1ef79957 100644 --- a/www/pages/extensions/examples.md +++ b/www/pages/extensions/examples.md @@ -8,6 +8,13 @@ ogImage: /og-image.png Kit ships with a rich set of example extensions in the `examples/extensions/` directory. These serve as both documentation and starting points for your own extensions. +## Model providers + +- `fake-provider.go`: deterministic `Hello from a fake provider.` with no credentials. +- `fake-tool-provider.go`: history-driven `add({a:2,b:3})`, matching host result, then `5`; no global request counter. + +See the [provider guide](/extensions/providers) for commands and current implementation status. + ## UI and display | Extension | Description | diff --git a/www/pages/extensions/overview.md b/www/pages/extensions/overview.md index faa93c2b..88bd30b3 100644 --- a/www/pages/extensions/overview.md +++ b/www/pages/extensions/overview.md @@ -8,6 +8,8 @@ ogImage: /og-image.png Extensions are Go source files interpreted at runtime via [Yaegi](https://github.com/traefik/yaegi). They can add custom tools, slash commands, widgets, keyboard shortcuts, and intercept lifecycle events — all without recompiling Kit. +Callback-based [model providers](/extensions/providers) can also register instance-owned backends. See the guide for the current implementation status and lifecycle restrictions. + ## Minimal extension ```go diff --git a/www/pages/extensions/providers.md b/www/pages/extensions/providers.md new file mode 100644 index 00000000..b191abb0 --- /dev/null +++ b/www/pages/extensions/providers.md @@ -0,0 +1,124 @@ +--- +title: Model Providers +description: Register callback-based model backends through trusted extensions. +ogImage: /og-image.png +--- + +# Model providers (work in progress) + +An extension can declare a model backend with `api.RegisterProvider` during +`Init`. The API uses concrete request and event records. The compiled host +adapter implements the model interface; the interpreter does not implement a +host interface. + +## Offline example + +```sh +kit --bare -e examples/extensions/fake-provider.go --model fake/echo \ + --no-session --no-core-tools --quiet "hello" +``` + +The output is `Hello from a fake provider.` No API key or network is needed. +The history-driven `fake-tool-provider.go` fixture calls an exposed host `add` +tool with chunked arguments and answers `5` after its matching result. + +Declare helper functions before using their names in callbacks. This avoids +Yaegi's forward-reference bug. See the source examples for complete code. + +## Ownership and trust + +Extensions are trusted Go code, not a sandbox. Put backend resource allocation +in `Create`, not `Init`. A failed Create owns its partial resources. A successful +Create transfers the returned runtime to Kit. Kit serializes calls on one +runtime; separate runtimes can run concurrently. Extension package globals +remain the author's synchronization responsibility. + +Only Create receives transient credentials. Do not print or persist creation +requests, keys, or endpoint credentials. API keys resolve from scoped overrides, +stored credentials, then declared environment variables. Externally managed +authentication can provide a safe status callback, not a key. Status callbacks +receive a five-second cancellation signal; safe results have a 30-second cache. +No-auth providers require no key. Kit does not implement extension OAuth login. + +Provider IDs are lowercase ASCII identifiers, beginning with a letter and then +letters, digits, `-`, `_`, or `.`. Model IDs are trimmed and case-sensitive; `/` +is allowed. Definitions cannot override native/catalog or SDK factory providers. +Registration errors invalidate a load even if Init ignores them. + +## Requests and tools + +Requests contain selected-branch history, ordered concrete parts, parameters, +and schemas of the tools actually exposed by Kit. They contain no executable +tool callbacks. **Kit is the sole executor of tools.** The normal hooks, +permission checks and code-mode exposure rules still apply. + +Numeric parameter pointers preserve explicit zero. Limits of zero mean unknown. +Prices are USD per million tokens; `Known: true, Value: 0` means free, while +`Known: false` means unknown. Unlisted model IDs require `AllowUnknownModels`. +Unknown lookup does not add the model to the picker. Unknown models have no +invented limits or prices and no advanced capabilities unless declared. + +## Streaming protocol + +`Stream` calls synchronous `emit` and returns one `ProviderResult`. There is no +error event. Return `ProviderResult.Error` for failure. Stop after emit returns +false and join all emitters before returning. No concurrent emit calls are +allowed. Observe request `Done` for cancellation. + +Text and reasoning use start/delta/end events. Every block ID is nonempty and +unique in its request. Tool start names an exposed tool; tool deltas carry JSON +fragments; tool end carries no second full payload. Arguments must form a JSON +object. Distinct open blocks can interleave. + +Usage events are cumulative nondecreasing snapshots. Kit counts the final +snapshot once. Exactly one finish is required, with every block closed. Reasons +are `stop`, `length`, and `tool_calls`. Only `tool_calls` may contain completed +executable calls. Kit withholds executable calls and finish until Stream returns +successfully, so a late error or protocol violation cannot execute a tool. + +Retryable transport/rate-limit failures can be retried only before content or +tool events have been exposed. After exposure neither request retry nor step +resume repeats the request. Protocol, auth, cancellation and unsupported input +errors are not retried. Panics are converted to safe errors without panic values. + +## Catalog scope + +Package-level SDK catalog queries remain global. Kit receiver methods include +that Kit's extension metadata. `kit models` remains database-only by default. + +```sh +kit --bare -e examples/extensions/fake-provider.go models --extensions fake +``` + +`--extensions` executes trusted top-level extension code and Init, but does not +create a model, start MCP, or emit session lifecycle events. `--no-extensions` +disables it; `--bare` restricts discovery to explicit paths. + +## Lifecycle and persistence + +Runtime Close is idempotent and never runs concurrently with Stream. Shutdown +cancels active requests and has a default 30-second provider wait budget, +shortened by CloseContext's deadline. Arbitrary interpreted callbacks cannot be +killed safely. A timeout is not proof that resources have been released; Kit +retains ownership and closes once the callback returns. + +Reload validates a strict candidate before replacing registrations. Existing +runtimes retain their old callbacks; their generation's shutdown waits for those +runtimes to close. Arbitrary side effects in Init cannot be rolled back. + +Extension-origin history uses additive persistence fields to preserve exact IDs, +ordered text/reasoning, signature-only reasoning, filenames, and namespaced +version-1 JSON replay data. Do not store credentials in replay payloads. Unknown +replay versions or namespaces are unsupported input, not text fallbacks. + +Object generation, catalog modes and decision models are unsupported for +extension providers in this release. + +## Remaining integration work + +This guide describes the intended contract. The current branch is not ready for +release: transactional publication under concurrent reload, complete replay +round-trips (including tool-result and message-level metadata), auth refresh, +reentrant shutdown rejection, complete capability reporting and acceptance-test +coverage still need validation. Do not treat passing baseline tests as proof of +those guarantees. diff --git a/www/pages/extensions/testing.md b/www/pages/extensions/testing.md index b56e5537..8d7cf384 100644 --- a/www/pages/extensions/testing.md +++ b/www/pages/extensions/testing.md @@ -8,6 +8,10 @@ ogImage: /og-image.png Kit provides a testing package (`github.com/mark3labs/kit/pkg/extensions/test`) that enables you to write unit tests for your extensions. Tests run outside the Yaegi interpreter but load your extension code into an isolated interpreter instance, allowing you to verify behavior without running the full Kit TUI. +## Provider tests + +Provider examples must load through the real interpreter. Use offline deterministic fixtures and `go test -race`; compiled fakes alone do not test callback/channel behavior. The [provider guide](/extensions/providers) describes the protocol. Kit's regression tests include simultaneous runtime entry, cancellation barriers, consumer backpressure, invalid tool calls, and a history-driven host-tool round trip. + ## Overview Extension tests allow you to: diff --git a/www/pages/sdk/api-reference.md b/www/pages/sdk/api-reference.md index f44829cf..1a527abc 100644 --- a/www/pages/sdk/api-reference.md +++ b/www/pages/sdk/api-reference.md @@ -448,6 +448,12 @@ Guides: [overview](/sdk/overview), [options](/sdk/options), [decisions](/sdk/dec - [`Kit.DrainSteer`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.DrainSteer) — DrainSteer removes and returns all unconsumed steer messages. - [`Kit.ExecuteCompletion`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.ExecuteCompletion) — ExecuteCompletion makes a standalone LLM completion call for extensions. - [`Kit.FollowUp`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.FollowUp) — FollowUp continues the conversation without explicit new user input. +- [`Kit.GetModelCapabilities`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.GetModelCapabilities) — Query this instance's model capabilities. +- [`Kit.CheckModelAvailable`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.CheckModelAvailable) — Validate a model in this instance. +- [`Kit.ResolveModelChain`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.ResolveModelChain) — Resolve fallback preferences in this instance. +- [`Kit.LookupModel`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.LookupModel) — Look up metadata in this instance, including extension providers. +- [`Kit.GetModelsForProvider`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.GetModelsForProvider) — List this instance's declared models for a provider. +- [`Kit.GetLLMProviders`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.GetLLMProviders) — List providers available to this instance. - [`Kit.GetAvailableModels`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.GetAvailableModels) — GetAvailableModels returns a list of known models from the registry. - [`Kit.GetBufferedDebugMessages`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.GetBufferedDebugMessages) — GetBufferedDebugMessages returns any debug messages that were buffered during initialization, then clears the buffer. - [`Kit.GetContextFiles`](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#Kit.GetContextFiles) — GetContextFiles returns the context files.