+
Skip to content

Consider strict function names for reflect to support dead code elimination #547

@alecthomas

Description

@alecthomas

Discussed in #517

Originally posted by chlunde April 8, 2025
Background: https://appliedgo.net/spotlight/reflection-binary-size/

Kong currently prevents DCE due to the reflection usage.
This is significant when writing CLIs that use SDKs like the AWS SDK:

ls -lh s3ls s3ls.dce
6.3M Apr  7 21:10 s3ls.dce*
 21M Apr  7 21:30 s3ls*

Example program:

package main

import (
        "context"
        "fmt"
        "log"

        "github.com/alecthomas/kong"
        "github.com/aws/aws-sdk-go-v2/config"
        "github.com/aws/aws-sdk-go-v2/service/s3"
)

var _ = kong.Must(&s3ls{})

type s3ls struct {
        Status listCmd `cmd:"" help:"List files."`
}

type listCmd struct {
        Verbose bool `short:"v" help:"Show bucket objects."`
}

func (s *listCmd) Run(ctx *kong.Context) error {
        // Load the SDK's configuration from environment and shared config, and
        // create the client with this.
        cfg, err := config.LoadDefaultConfig(context.TODO())
        if err != nil {
                log.Fatalf("failed to load SDK configuration, %v", err)
        }

        client := s3.NewFromConfig(cfg)

        bucketName := "my-bucket" // Replace with your bucket name

        // Set the parameters based on the CLI flag inputs.
        params := &s3.ListObjectsV2Input{
                Bucket: &bucketName,
        }

        // Create the Paginator for the ListObjectsV2 operation.
        p := s3.NewListObjectsV2Paginator(client, params, func(o *s3.ListObjectsV2PaginatorOptions) {})

        // Iterate through the S3 object pages, printing each object returned.
        var i int
        log.Println("Objects:")
        for p.HasMorePages() {
                i++

                // Next Page takes a new context for each page retrieval. This is where
                // you could add timeouts or deadlines.
                page, err := p.NextPage(context.TODO())
                if err != nil {
                        log.Fatalf("failed to get page %v, %v", i, err)
                }

                // Log the objects found
                for _, obj := range page.Contents {
                        fmt.Println("Object:", *obj.Key)
                }
        }

        return nil
}

func main() {
        ctx := kong.Parse(&s3ls{},
                kong.Description("s3ls - A simple S3 client"),
                kong.UsageOnError(),
        )
        ctx.FatalIfErrorf(ctx.Run())
}

With a few hacks, it might be possible to enable DCE. I did not find and example for Provide, but if adapt the code to only allow a specific name like Provide() without a suffix, it should allow for DCE:

diff -ruwbp vendor/github.com/alecthomas/kong/callbacks.go vendor.mod/github.com/alecthomas/kong/callbacks.go
--- vendor/github.com/alecthomas/kong/callbacks.go      2025-04-07 21:20:36
+++ vendor.mod/github.com/alecthomas/kong/callbacks.go  2025-04-07 21:12:41
@@ -117,11 +117,30 @@ func (b bindings) merge(other bindings) bindings {
        return b
 }

+func getMethodByName(value reflect.Value, name string) reflect.Value {
+       switch name {
+       case "BeforeReset":
+               return value.MethodByName("BeforeReset")
+       case "BeforeResolve":
+               return value.MethodByName("BeforeResolve")
+       case "BeforeApply":
+               return value.MethodByName("BeforeApply")
+       case "AfterApply":
+               return value.MethodByName("AfterApply")
+       case "AfterRun":
+               return value.MethodByName("AfterRun")
+       case "Run":
+               return value.MethodByName("Run")
+       default:
+               panic(fmt.Sprintf("unknown method %s", name))
+       }
+}
+
 func getMethod(value reflect.Value, name string) reflect.Value {
-       method := value.MethodByName(name)
+       method := getMethodByName(value, name)
        if !method.IsValid() {
                if value.CanAddr() {
-                       method = value.Addr().MethodByName(name)
+                       method = getMethodByName(value.Addr(), name)
                }
        }
        return method
diff -ruwbp vendor/github.com/alecthomas/kong/context.go vendor.mod/github.com/alecthomas/kong/context.go
--- vendor/github.com/alecthomas/kong/context.go        2025-04-07 21:20:36
+++ vendor.mod/github.com/alecthomas/kong/context.go    2025-04-07 21:10:35
@@ -825,16 +825,17 @@ func (c *Context) RunNode(node *Node, binds ...any) (e
                        for _, p := range []reflect.Value{p.Target, p.Target.Addr()} {
                                t := p.Type()
                                for i := 0; i < p.NumMethod(); i++ {
-                                       methodt := t.Method(i)
-                                       if strings.HasPrefix(methodt.Name, "Provide") {
-                                               method := p.Method(i)
-                                               if err := methodBinds.addProvider(method.Interface(), false /* singleton */); err != nil {
-                                                       return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err)
+                                       _ = t
+                                       // methodt := t.Method(i) // TODO: improve this
+                                       // if strings.HasPrefix(methodt.Name, "Provide") {
+                                       //      method := p.Method(i)
+                                       //      if err := methodBinds.addProvider(method.Interface(), false /* singleton */); err != nil {
+                                       //              return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err)
+                                       //      }
+                                       // }
                                                }
                                        }
                                }
-                       }
-               }
                if method.IsValid() {
                        methods = append(methods, targetMethod{node, method, methodBinds})
                }
```</div>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载