wire: Add wire.InterfaceValue, required instead of wire.Value if the value is an interface value (google/go-cloud#322)

* Add wire.InterfaceValue, required instead of wire.Value if the value is an interface value.

* Update guestbook sample to use InterfaceValue where appropriate.

* Remove unnecessary ok := true

* Addressing comments from code review.
This commit is contained in:
Robert van Gent
2018-08-14 14:55:28 -07:00
committed by Ross Light
parent eedae3d8d0
commit cd32a686b1
20 changed files with 296 additions and 3 deletions

View File

@@ -317,6 +317,14 @@ package; references to variables will be evaluated during the injector
package's initialization. Wire will emit an error if the expression calls package's initialization. Wire will emit an error if the expression calls
any functions or receives from any channels. any functions or receives from any channels.
For interface values, use `InterfaceValue`:
```go
func injectReader() io.Reader {
wire.Build(wire.InterfaceValue(new(io.Reader), os.Stdin))
return Foo{}
}
```
### Cleanup functions ### Cleanup functions
If a provider creates a value that needs to be cleaned up (e.g. closing a file), If a provider creates a value that needs to be cleaned up (e.g. closing a file),

View File

@@ -454,6 +454,12 @@ func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr, varNa
return nil, []error{notePosition(exprPos, err)} return nil, []error{notePosition(exprPos, err)}
} }
return v, nil return v, nil
case "InterfaceValue":
v, err := processInterfaceValue(oc.prog.Fset, &pkg.Info, call)
if err != nil {
return nil, []error{notePosition(exprPos, err)}
}
return v, nil
default: default:
return nil, []error{notePosition(exprPos, errors.New("unknown pattern"))} return nil, []error{notePosition(exprPos, errors.New("unknown pattern"))}
} }
@@ -739,6 +745,11 @@ func processValue(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*V
if !ok { if !ok {
return nil, notePosition(fset.Position(call.Pos()), errors.New("argument to Value is too complex")) return nil, notePosition(fset.Position(call.Pos()), errors.New("argument to Value is too complex"))
} }
// Result type can't be an interface type; use wire.InterfaceValue for that.
argType := info.TypeOf(call.Args[0])
if _, isInterfaceType := argType.Underlying().(*types.Interface); isInterfaceType {
return nil, notePosition(fset.Position(call.Pos()), fmt.Errorf("argument to Value may not be an interface value (found %s); use InterfaceValue instead", types.TypeString(argType, nil)))
}
return &Value{ return &Value{
Pos: call.Args[0].Pos(), Pos: call.Args[0].Pos(),
Out: info.TypeOf(call.Args[0]), Out: info.TypeOf(call.Args[0]),
@@ -747,6 +758,35 @@ func processValue(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*V
}, nil }, nil
} }
// processInterfaceValue creates a value from a wire.InterfaceValue call.
func processInterfaceValue(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*Value, error) {
// Assumes that call.Fun is wire.InterfaceValue.
if len(call.Args) != 2 {
return nil, notePosition(fset.Position(call.Pos()), errors.New("call to InterfaceValue takes exactly two arguments"))
}
ifaceArgType := info.TypeOf(call.Args[0])
ifacePtr, ok := ifaceArgType.(*types.Pointer)
if !ok {
return nil, notePosition(fset.Position(call.Pos()), fmt.Errorf("first argument to InterfaceValue must be a pointer to an interface type; found %s", types.TypeString(ifaceArgType, nil)))
}
iface := ifacePtr.Elem()
methodSet, ok := iface.Underlying().(*types.Interface)
if !ok {
return nil, notePosition(fset.Position(call.Pos()), fmt.Errorf("first argument to InterfaceValue must be a pointer to an interface type; found %s", types.TypeString(ifaceArgType, nil)))
}
provided := info.TypeOf(call.Args[1])
if !types.Implements(provided, methodSet) {
return nil, notePosition(fset.Position(call.Pos()), fmt.Errorf("%s does not implement %s", types.TypeString(provided, nil), types.TypeString(ifaceArgType, nil)))
}
return &Value{
Pos: call.Args[1].Pos(),
Out: iface,
expr: call.Args[1],
info: info,
}, nil
}
// isInjector checks whether a given function declaration is an // isInjector checks whether a given function declaration is an
// injector template, returning the wire.Build call. It returns nil if // injector template, returning the wire.Build call. It returns nil if
// the function is not an injector template. // the function is not an injector template.
@@ -845,7 +885,7 @@ func (pv ProviderOrValue) Provider() *Provider {
return pv.p return pv.p
} }
// Provider returns pv as a Value pointer. It panics if pv points to a // Value returns pv as a Value pointer. It panics if pv points to a
// Provider. // Provider.
func (pv ProviderOrValue) Value() *Value { func (pv ProviderOrValue) Value() *Value {
if pv.p != nil { if pv.p != nil {

View File

@@ -0,0 +1,26 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
package main
import (
"fmt"
"io/ioutil"
)
func main() {
r := injectedReader()
buf, _ := ioutil.ReadAll(r)
fmt.Println(string(buf))
}

View File

@@ -0,0 +1,29 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
//+build wireinject
package main
import (
"io"
"strings"
"github.com/google/go-cloud/wire"
)
func injectedReader() io.Reader {
wire.Build(wire.InterfaceValue(new(io.Reader), strings.NewReader("hello world")))
return nil
}

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1 @@
hello world

View File

@@ -0,0 +1,22 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
import (
io "io"
strings "strings"
)
// Injectors from wire.go:
func injectedReader() io.Reader {
reader := _wireReaderValue
return reader
}
var (
_wireReaderValue = strings.NewReader("hello world")
)

View File

@@ -0,0 +1,21 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
package main
import "fmt"
func main() {
fmt.Println(injectedMessage())
}

View File

@@ -0,0 +1,26 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
//+build wireinject
package main
import (
"github.com/google/go-cloud/wire"
)
func injectedMessage() string {
wire.Build(wire.InterfaceValue("foo", "bar"))
return ""
}

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1 @@
first argument to InterfaceValue must be a pointer to an interface type; found string

View File

@@ -0,0 +1,21 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
package main
import "fmt"
func main() {
fmt.Println(injectedMessage())
}

View File

@@ -0,0 +1,26 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
//+build wireinject
package main
import (
"github.com/google/go-cloud/wire"
)
func injectedMessage() string {
wire.Build(wire.InterfaceValue("foo"))
return ""
}

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1 @@
too few arguments in call to wire.InterfaceValue

View File

@@ -0,0 +1,27 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
package main
import (
"fmt"
"io/ioutil"
"strings"
)
func main() {
r := injectedReader(strings.NewReader("hello world"))
buf, _ := ioutil.ReadAll(r)
fmt.Println(string(buf))
}

View File

@@ -0,0 +1,29 @@
// Copyright 2018 The Go Cloud Authors
//
// 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
//
// https://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.
//+build wireinject
package main
import (
"io"
"strings"
"github.com/google/go-cloud/wire"
)
func injectedReader(r *strings.Reader) io.Reader {
wire.Build(wire.Value(io.Reader(r)))
return nil
}

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1 @@
argument to Value may not be an interface value (found io.Reader); use InterfaceValue instead

14
wire.go
View File

@@ -20,8 +20,8 @@ type ProviderSet struct{}
// NewSet creates a new provider set that includes the providers in // NewSet creates a new provider set that includes the providers in
// its arguments. Each argument is either an exported function value, // its arguments. Each argument is either an exported function value,
// an exported struct (zero) value, a provider set, a call to Bind, or // an exported struct (zero) value, a provider set, a call to Bind, a
// a call to Value. // a call to Value, or a call to InterfaceValue.
func NewSet(...interface{}) ProviderSet { func NewSet(...interface{}) ProviderSet {
return ProviderSet{} return ProviderSet{}
} }
@@ -72,6 +72,7 @@ func Bind(iface, to interface{}) Binding {
type ProvidedValue struct{} type ProvidedValue struct{}
// Value binds an expression to provide the type of the expression. // Value binds an expression to provide the type of the expression.
// The expression may not be an interface value; use InterfaceValue for that.
// //
// Example: // Example:
// //
@@ -79,3 +80,12 @@ type ProvidedValue struct{}
func Value(interface{}) ProvidedValue { func Value(interface{}) ProvidedValue {
return ProvidedValue{} return ProvidedValue{}
} }
// InterfaceValue binds an expression to provide a specific interface type.
//
// Example:
//
// var MySet = wire.NewSet(wire.InterfaceValue(new(io.Reader), os.Stdin))
func InterfaceValue(typ interface{}, x interface{}) ProvidedValue {
return ProvidedValue{}
}