@@ -15,19 +15,25 @@ type Build struct {
1515}
1616
1717func (b * Build ) MustBuild () {
18- prefix := "wrap_generated_"
19- b .Implementation ().MustWriteFile (prefix + b .Suffix + ".go" )
20- b .Tests ().MustWriteFile (prefix + b .Suffix + "_test.go" )
18+ prefix := "wrap_generated"
19+ if b .Suffix != "" {
20+ prefix += "_" + b .Suffix
21+ }
22+
23+ b .Implementation ().MustWriteFile (prefix + ".go" )
24+ b .Tests ().MustWriteFile (prefix + "_test.go" )
2125}
2226
2327func (b * Build ) writeHeader (g * Generator ) {
24- g .Printf (`
25- // +build %s
26- // Code generated by "httpsnoop/codegen"; DO NOT EDIT.
28+ if b .Tags != "" {
29+ g .Printf (`// +build %s
30+ ` , b .Tags )
31+ }
32+ g .buf .WriteString (`// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
2733
2834package httpsnoop
2935
30- ` , b . Tags )
36+ ` )
3137}
3238
3339func (b * Build ) Implementation () * Generator {
@@ -89,64 +95,110 @@ type Hooks struct {
8995// hooks can be used.
9096` , strings .Join (docList , "\n " ))
9197 g .Printf ("func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {\n " )
92- g .Printf ("rw := &rw{w: w, h: hooks}\n " )
98+ g .Printf ("state := &rwState{w: w}\n " )
99+
100+ // Precompute hook chains once per Wrap call and
101+ // build a uint8 combo index so the switch compiles to a jump table.
102+ g .Printf ("var combo uint16\n " )
103+ for _ , fn := range ifaces [0 ].Funcs {
104+ g .Printf ("if hooks.%s != nil {\n " , fn .Name )
105+ g .Printf ("state.%s = hooks.%s(w.%s)\n " , fieldName (fn .Name ), fn .Name , fn .Name )
106+ g .Printf ("}\n " )
107+ }
108+
93109 for i , iface := range subIfaces {
94- g .Printf ("_, i%d := w.(%s)\n " , i , iface .Name )
110+ g .Printf ("if t%[1]d, i%[1]d := w.(%s); i%[1]d {\n " , i , iface .Name )
111+ bit := len (subIfaces ) - i - 1
112+ g .Printf ("combo |= 1<<%d\n " , bit )
113+ for _ , fn := range iface .Funcs {
114+ g .Printf ("if hooks.%s != nil {\n " , fn .Name )
115+ g .Printf ("state.%s = hooks.%s(t%d.%s)\n " , fieldName (fn .Name ), fn .Name , i , fn .Name )
116+ g .Printf ("}\n " )
117+ }
118+ g .Printf ("}\n " )
95119 }
96- g .Printf ("switch {\n " )
120+
121+ g .Printf ("switch combo {\n " )
97122 combinations := 1 << uint (len (subIfaces ))
98- for i := 0 ; i < combinations ; i ++ {
99- conditions := make ([]string , len (subIfaces ))
100- fields := make ([]string , 0 , len (subIfaces ))
101- fields = append (fields , "Unwrapper" , "http.ResponseWriter" )
102- for j , iface := range subIfaces {
103- ok := i & (1 << uint (len (subIfaces )- j - 1 )) > 0
104- if ! ok {
105- conditions [j ] = "!"
106- } else {
107- fields = append (fields , iface .Name )
108- }
109- conditions [j ] += fmt .Sprintf ("i%d" , j )
110- }
111- values := make ([]string , len (fields ))
112- for i := range fields {
113- values [i ] = "rw"
114- }
115- g .Printf ("// combination %d/%d\n " , i + 1 , combinations )
116- g .Printf ("case %s:\n " , strings .Join (conditions , "&&" ))
117- fieldsS , valuesS := strings .Join (fields , "\n " ), strings .Join (values , "," )
118- g .Printf ("return struct{\n %s\n }{%s}\n " , fieldsS , valuesS )
123+ for c := 0 ; c < combinations ; c ++ {
124+ g .Printf ("case %d: return (*rw%d)(state)\n " , c , c )
119125 }
120126 g .Printf ("}\n " )
121127 g .Printf ("panic(\" unreachable\" )" )
122- g .Printf ("}\n " )
123-
124- // rw struct
125- g .Printf (`
126- type rw struct {
127- w http.ResponseWriter
128- h Hooks
129- }
128+ g .Printf ("}\n \n " )
130129
131- func (w *rw) Unwrap() http.ResponseWriter {
132- return w.w
133- }
130+ // rwState holds the underlying writer plus the precomputed hooks.
131+ // All variant types are type-definitions over rwState, so a single *rwState
132+ // allocation can be reinterpreted as any variant via pointer conversion.
133+ g .Printf ("type rwState struct {\n " )
134+ g .Printf ("w http.ResponseWriter\n " )
135+ for _ , iface := range ifaces {
136+ for _ , fn := range iface .Funcs {
137+ g .Printf ("%s %s\n " , fieldName (fn .Name ), fn .Type ())
138+ }
139+ }
140+ g .Printf ("}\n \n " )
134141
135- ` )
142+ // do<Name> helpers on *rwState
143+ // These actual dispatch logic, defined once and called by the variant types.
136144 for _ , iface := range ifaces {
137145 for _ , fn := range iface .Funcs {
138- g .Printf ("func (w *rw) %s(%s) (%s) {\n " , fn .Name , fn .Args , fn .Returns )
139- g .Printf ("f := w.w.(%s).%s\n " , iface .Name , fn .Name )
140- g .Printf ("if w.h.%s != nil {\n " , fn .Name )
141- g .Printf ("f = w.h.%s(f)\n " , fn .Name )
142- g .Printf ("}\n " )
146+ g .Printf ("func (r *rwState) do%s(%s) (%s) {\n " , fn .Name , fn .Args , fn .Returns )
147+ g .Printf ("if r.%s != nil {\n " , fieldName (fn .Name ))
143148 if fn .Returns != "" {
144- g .Printf ("return " )
149+ g .Printf ("return r.%s(%s)\n " , fieldName (fn .Name ), fn .Args .Names ())
150+ } else {
151+ g .Printf ("r.%s(%s)\n " , fieldName (fn .Name ), fn .Args .Names ())
152+ g .Printf ("return\n " )
145153 }
146- g .Printf ("f(%s)\n " , fn .Args .Names ())
147154 g .Printf ("}\n " )
148- g .Printf ("\n " )
155+
156+ receiver := "r.w"
157+ if iface .Name != "http.ResponseWriter" {
158+ receiver = fmt .Sprintf ("r.w.(%s)" , iface .Name )
159+ }
160+ if fn .Returns != "" {
161+ g .Printf ("return %s.%s(%s)\n " , receiver , fn .Name , fn .Args .Names ())
162+ } else {
163+ g .Printf ("%s.%s(%s)\n " , receiver , fn .Name , fn .Args .Names ())
164+ }
165+ g .Printf ("}\n \n " )
166+ }
167+ }
168+
169+ // Variant types, each is a type with the same memory layout as rwState,
170+ // but exposing exactly the method set required by its combination of interfaces.
171+ // This allows (*rwN)(state) to be a zero-cost pointer conversion.
172+ emitVariantMethod := func (c int , fn * InterfaceFunc ) {
173+ g .Printf ("func (w *rw%d) %s(%s) (%s) {\n " , c , fn .Name , fn .Args , fn .Returns )
174+ if fn .Returns != "" {
175+ g .Printf ("return (*rwState)(w).do%s(%s)\n " , fn .Name , fn .Args .Names ())
176+ } else {
177+ g .Printf ("(*rwState)(w).do%s(%s)\n " , fn .Name , fn .Args .Names ())
149178 }
179+ g .Printf ("}\n " )
180+ }
181+ for c := 0 ; c < combinations ; c ++ {
182+ supported := []string {"http.ResponseWriter" }
183+ for j , iface := range subIfaces {
184+ if c & (1 << uint (len (subIfaces )- j - 1 )) > 0 {
185+ supported = append (supported , iface .Name )
186+ }
187+ }
188+ g .Printf ("// combination %d/%d: %s\n " , c + 1 , combinations , strings .Join (supported , ", " ))
189+ g .Printf ("type rw%d rwState\n " , c )
190+ g .Printf ("func (w *rw%d) Unwrap() http.ResponseWriter { return w.w }\n " , c )
191+ for _ , fn := range ifaces [0 ].Funcs {
192+ emitVariantMethod (c , fn )
193+ }
194+ for j , iface := range subIfaces {
195+ if c & (1 << uint (len (subIfaces )- j - 1 )) > 0 {
196+ for _ , fn := range iface .Funcs {
197+ emitVariantMethod (c , fn )
198+ }
199+ }
200+ }
201+ g .Printf ("\n " )
150202 }
151203 g .Printf (`
152204type Unwrapper interface {
@@ -159,9 +211,8 @@ func Unwrap(w http.ResponseWriter) http.ResponseWriter {
159211 if rw, ok := w.(Unwrapper); ok {
160212 // recurse until rw.Unwrap() returns a non-Unwrapper
161213 return Unwrap(rw.Unwrap())
162- } else {
163- return w
164214 }
215+ return w
165216}
166217` )
167218 return & g
@@ -263,12 +314,19 @@ func (fn *InterfaceFunc) Type() string {
263314 return fn .Name + "Func"
264315}
265316
317+ func fieldName (s string ) string {
318+ if s == "" {
319+ return s
320+ }
321+ return strings .ToLower (s [:1 ]) + s [1 :]
322+ }
323+
266324type Generator struct {
267325 buf bytes.Buffer
268326}
269327
270328func (g * Generator ) Printf (s string , args ... interface {}) {
271- fmt .Fprintf (& g .buf , s , args ... )
329+ _ , _ = fmt .Fprintf (& g .buf , s , args ... )
272330}
273331
274332func (g * Generator ) WriteFile (name string ) error {
@@ -311,6 +369,12 @@ func main() {
311369 {"Flush" , nil , "" },
312370 },
313371 },
372+ {
373+ Name : "httpFlushError" , // Introduced in Go 1.20.
374+ Funcs : []* InterfaceFunc {
375+ {"FlushError" , nil , "error" },
376+ },
377+ },
314378 {
315379 Name : "http.CloseNotifier" ,
316380 Funcs : []* InterfaceFunc {
@@ -342,33 +406,32 @@ func main() {
342406 {"EnableFullDuplex" , nil , "error" },
343407 },
344408 },
409+ {
410+ Name : "http.Pusher" ,
411+ Funcs : []* InterfaceFunc {
412+ {"Push" , FuncArgs {
413+ {"target" , "string" },
414+ {"opts" , "*http.PushOptions" },
415+ }, "error" },
416+ },
417+ }, {
418+ Name : "io.StringWriter" ,
419+ Funcs : []* InterfaceFunc {
420+ {"WriteString" , FuncArgs {{"s" , "string" }}, "int, error" },
421+ },
422+ },
345423 }
346424 builds := []Build {
347425 {
348- Suffix : "lt_1.8" ,
349- Tags : "!go1.8" ,
350426 Interfaces : ifaces ,
351427 },
352- {
353- Suffix : "gteq_1.8" ,
354- Tags : "go1.8" ,
355- Interfaces : append (ifaces , & Interface {
356- Name : "http.Pusher" ,
357- Funcs : []* InterfaceFunc {
358- {"Push" , FuncArgs {
359- {"target" , "string" },
360- {"opts" , "*http.PushOptions" },
361- }, "error" },
362- },
363- }),
364- },
365428 }
366429 for _ , build := range builds {
367430 build .MustBuild ()
368431 }
369432}
370433
371434func fatalf (s string , args ... interface {}) {
372- fmt .Fprintf (os .Stderr , s + "\n " , args ... )
435+ _ , _ = fmt .Fprintf (os .Stderr , s + "\n " , args ... )
373436 os .Exit (1 )
374437}
0 commit comments