mirror of
https://github.com/golang/go.git
synced 2026-06-27 19:30:52 +00:00
cmd/compile: disallow nointerface method satisfying type constraint
Type constraint satisfaction is interface satisfaction. If a type does not satisfy a regular interface, it should also not satisfy the same interface as a type constraint. Otherwise, if the type satisfies the type constraint, one can use that to construct a (dynamic) interface value with a type that doesn't actually satisfy the interface. The go:nointerface directive tells the compiler that the method does not satisfy an interface. Therefore it should also not satisfy a type constraint. Fixes #74626. Change-Id: I7c64c76044a665755e4e74035085daff42447f9e Reviewed-on: https://go-review.googlesource.com/c/go/+/772620 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
063f8b07c1
commit
8ddf0031cf
8 changed files with 60 additions and 4 deletions
|
|
@ -175,6 +175,10 @@ type pragmas struct {
|
|||
WasmExport *WasmExport
|
||||
}
|
||||
|
||||
func (p *pragmas) Nointerface() bool {
|
||||
return p.Flag&ir.Nointerface != 0
|
||||
}
|
||||
|
||||
// WasmImport stores metadata associated with the //go:wasmimport pragma
|
||||
type WasmImport struct {
|
||||
Pos syntax.Pos
|
||||
|
|
|
|||
|
|
@ -684,6 +684,12 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
|
|||
fdecl := decl.fdecl
|
||||
check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type)
|
||||
|
||||
if fdecl.Pragma != nil {
|
||||
if p, ok := fdecl.Pragma.(interface{ Nointerface() bool }); ok && p.Nointerface() {
|
||||
obj.nointerface = true
|
||||
}
|
||||
}
|
||||
|
||||
// Set the scope's extent to the complete "func (...) { ... }"
|
||||
// so that Scope.Innermost works correctly.
|
||||
sig.scope.pos = fdecl.Pos()
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
|||
ambigSel
|
||||
ptrRecv
|
||||
field
|
||||
nointerface
|
||||
)
|
||||
|
||||
state := ok
|
||||
|
|
@ -453,6 +454,11 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
|||
check.objDecl(f)
|
||||
}
|
||||
|
||||
if f.nointerface {
|
||||
state = nointerface
|
||||
break
|
||||
}
|
||||
|
||||
if !equivalent(f.typ, m.typ) {
|
||||
state = wrongSig
|
||||
break
|
||||
|
|
@ -512,6 +518,8 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
|||
*cause = check.sprintf("(method %s has pointer receiver)", m.Name())
|
||||
case field:
|
||||
*cause = check.sprintf("(%s.%s is a field, not a method)", V, m.Name())
|
||||
case nointerface:
|
||||
*cause = check.sprintf("(%s method is marked 'nointerface')", m.Name())
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -396,8 +396,9 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
|
|||
// An abstract method may belong to many interfaces due to embedding.
|
||||
type Func struct {
|
||||
object
|
||||
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
|
||||
origin *Func // if non-nil, the Func from which this one was instantiated
|
||||
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
|
||||
nointerface bool
|
||||
}
|
||||
|
||||
// NewFunc returns a new function with the given signature, representing
|
||||
|
|
@ -412,7 +413,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func {
|
|||
// as this would violate object.{Type,color} invariants.
|
||||
// TODO(adonovan): propose to disallow NewFunc with nil *Signature.
|
||||
}
|
||||
return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
|
||||
return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, nil, false, false}
|
||||
}
|
||||
|
||||
// Signature returns the signature (type) of the function or method.
|
||||
|
|
|
|||
|
|
@ -771,6 +771,9 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
|
|||
fdecl := decl.fdecl
|
||||
check.funcType(sig, fdecl.Recv, fdecl.Type)
|
||||
|
||||
// types2 handles go:nointerface pragma here by setting obj.nointerface.
|
||||
// go/types currently doesn't handle pragmas.
|
||||
|
||||
// Set the scope's extent to the complete "func (...) { ... }"
|
||||
// so that Scope.Innermost works correctly.
|
||||
sig.scope.pos = fdecl.Pos()
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
|||
ambigSel
|
||||
ptrRecv
|
||||
field
|
||||
nointerface
|
||||
)
|
||||
|
||||
state := ok
|
||||
|
|
@ -456,6 +457,11 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
|||
check.objDecl(f)
|
||||
}
|
||||
|
||||
if f.nointerface {
|
||||
state = nointerface
|
||||
break
|
||||
}
|
||||
|
||||
if !equivalent(f.typ, m.typ) {
|
||||
state = wrongSig
|
||||
break
|
||||
|
|
@ -515,6 +521,8 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y
|
|||
*cause = check.sprintf("(method %s has pointer receiver)", m.Name())
|
||||
case field:
|
||||
*cause = check.sprintf("(%s.%s is a field, not a method)", V, m.Name())
|
||||
case nointerface:
|
||||
*cause = check.sprintf("(%s method is marked 'nointerface')", m.Name())
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -399,8 +399,9 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
|
|||
// An abstract method may belong to many interfaces due to embedding.
|
||||
type Func struct {
|
||||
object
|
||||
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
|
||||
origin *Func // if non-nil, the Func from which this one was instantiated
|
||||
hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read
|
||||
nointerface bool
|
||||
}
|
||||
|
||||
// NewFunc returns a new function with the given signature, representing
|
||||
|
|
@ -415,7 +416,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
|
|||
// as this would violate object.{Type,color} invariants.
|
||||
// TODO(adonovan): propose to disallow NewFunc with nil *Signature.
|
||||
}
|
||||
return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
|
||||
return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, nil, false, false}
|
||||
}
|
||||
|
||||
// Signature returns the signature (type) of the function or method.
|
||||
|
|
|
|||
25
test/fixedbugs/issue74626.go
Normal file
25
test/fixedbugs/issue74626.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// errorcheck -goexperiment fieldtrack
|
||||
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
type Fooer interface {
|
||||
Foo() string
|
||||
}
|
||||
|
||||
type FooImpl struct{}
|
||||
|
||||
//go:nointerface
|
||||
func (FooImpl) Foo() string { return "foo" }
|
||||
|
||||
func toInterface[T Fooer](fooer T) Fooer {
|
||||
return fooer
|
||||
}
|
||||
|
||||
func main() {
|
||||
var iface Fooer = toInterface(FooImpl{}) // ERROR "does not satisfy Fooer"
|
||||
iface.Foo()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue