libgo: update to Go1.18rc1 release

Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/386594
This commit is contained in:
Ian Lance Taylor 2022-02-18 13:10:34 -08:00
parent 1931cbad49
commit 20a33efdf3
188 changed files with 3718 additions and 2994 deletions

View File

@ -1,4 +1,4 @@
90ed127ef053b758288af9c4e43473e257770bc3
fade776395ffe5497d8aae5c0e6bd6d15e09e04a
The first line of this file holds the git revision number of the last
merge done from the gofrontend repository.

View File

@ -1,4 +1,4 @@
41f485b9a7d8fd647c415be1d11b612063dff21c
cb5a598d7f2ebd276686403d141a97c026d33458
The first line of this file holds the git revision number of the
last merge done from the master library sources.

View File

@ -817,6 +817,7 @@ libgo_go_objs = \
syscall/errno.lo \
syscall/signame.lo \
syscall/wait.lo \
runtime/internal/syscall/errno.lo \
os/dir_gccgo_c.lo \
$(golangorg_x_net_lif_lo) \
$(golangorg_x_net_route_lo) \
@ -1180,6 +1181,11 @@ syscall/wait.lo: go/syscall/wait.c runtime.inc
@$(MKDIR_P) syscall
$(LTCOMPILE) -c -o $@ $(srcdir)/go/syscall/wait.c
# Some runtime/internal/syscall functions are written in C.
runtime/internal/syscall/errno.lo: go/runtime/internal/syscall/errno.c runtime.inc
@$(MKDIR_P) runtime/internal/syscall
$(LTCOMPILE) -c -o $@ $(srcdir)/go/runtime/internal/syscall/errno.c
# An os function is written in C.
os/dir_gccgo_c.lo: go/os/dir_gccgo_c.c runtime.inc
@$(MKDIR_P) os

View File

@ -225,7 +225,8 @@ LTLIBRARIES = $(toolexeclib_LTLIBRARIES)
am__DEPENDENCIES_3 = $(addsuffix .lo,$(PACKAGES)) \
internal/bytealg/bytealg.lo reflect/makefunc_ffi_c.lo \
$(am__DEPENDENCIES_1) syscall/errno.lo syscall/signame.lo \
syscall/wait.lo os/dir_gccgo_c.lo $(golangorg_x_net_lif_lo) \
syscall/wait.lo runtime/internal/syscall/errno.lo \
os/dir_gccgo_c.lo $(golangorg_x_net_lif_lo) \
$(golangorg_x_net_route_lo) log/syslog/syslog_c.lo \
runtime/internal/atomic_c.lo sync/atomic_c.lo \
internal/cpu/cpu_gccgo.lo $(am__DEPENDENCIES_2)
@ -955,6 +956,7 @@ libgo_go_objs = \
syscall/errno.lo \
syscall/signame.lo \
syscall/wait.lo \
runtime/internal/syscall/errno.lo \
os/dir_gccgo_c.lo \
$(golangorg_x_net_lif_lo) \
$(golangorg_x_net_route_lo) \
@ -3091,6 +3093,11 @@ syscall/wait.lo: go/syscall/wait.c runtime.inc
@$(MKDIR_P) syscall
$(LTCOMPILE) -c -o $@ $(srcdir)/go/syscall/wait.c
# Some runtime/internal/syscall functions are written in C.
runtime/internal/syscall/errno.lo: go/runtime/internal/syscall/errno.c runtime.inc
@$(MKDIR_P) runtime/internal/syscall
$(LTCOMPILE) -c -o $@ $(srcdir)/go/runtime/internal/syscall/errno.c
# An os function is written in C.
os/dir_gccgo_c.lo: go/os/dir_gccgo_c.c runtime.inc
@$(MKDIR_P) os

View File

@ -1 +1 @@
go1.18beta2
go1.18rc1

View File

@ -95,11 +95,11 @@ type rune = int32
type any = interface{}
// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, interfaces,
// arrays of comparable types, structs whose fields are all comparable types).
// (booleans, numbers, strings, pointers, channels, arrays of comparable types,
// structs whose fields are all comparable types).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable comparable
type comparable interface{ comparable }
// iota is a predeclared identifier representing the untyped integer ordinal
// number of the current const specification in a (usually parenthesized)

View File

@ -372,6 +372,8 @@ func genSplit(s, sep []byte, sepSave, n int) [][]byte {
// n > 0: at most n subslices; the last subslice will be the unsplit remainder.
// n == 0: the result is nil (zero subslices)
// n < 0: all subslices
//
// To split around the first instance of a separator, see Cut.
func SplitN(s, sep []byte, n int) [][]byte { return genSplit(s, sep, 0, n) }
// SplitAfterN slices s into subslices after each instance of sep and
@ -389,6 +391,8 @@ func SplitAfterN(s, sep []byte, n int) [][]byte {
// the subslices between those separators.
// If sep is empty, Split splits after each UTF-8 sequence.
// It is equivalent to SplitN with a count of -1.
//
// To split around the first instance of a separator, see Cut.
func Split(s, sep []byte) [][]byte { return genSplit(s, sep, 0, -1) }
// SplitAfter slices s into all subslices after each instance of sep and

View File

@ -177,14 +177,6 @@
// directory, but it is not accessed. When -modfile is specified, an
// alternate go.sum file is also used: its path is derived from the
// -modfile flag by trimming the ".mod" extension and appending ".sum".
// -workfile file
// in module aware mode, use the given go.work file as a workspace file.
// By default or when -workfile is "auto", the go command searches for a
// file named go.work in the current directory and then containing directories
// until one is found. If a valid go.work file is found, the modules
// specified will collectively be used as the main modules. If -workfile
// is "off", or a go.work file is not found in "auto" mode, workspace
// mode is disabled.
// -overlay file
// read a JSON config file that provides an overlay for build operations.
// The file is a JSON struct with a single field, named 'Replace', that
@ -1379,7 +1371,7 @@
// builds from local modules.
//
// go.work files are line-oriented. Each line holds a single directive,
// made up of a keyword followed by aruments. For example:
// made up of a keyword followed by arguments. For example:
//
// go 1.18
//
@ -1472,19 +1464,14 @@
// The -json flag prints the final go.work file in JSON format instead of
// writing it back to go.mod. The JSON output corresponds to these Go types:
//
// type Module struct {
// Path string
// Version string
// }
//
// type GoWork struct {
// Go string
// Directory []Directory
// Replace []Replace
// Go string
// Use []Use
// Replace []Replace
// }
//
// type Use struct {
// Path string
// DiskPath string
// ModulePath string
// }
//
@ -1493,6 +1480,11 @@
// New Module
// }
//
// type Module struct {
// Path string
// Version string
// }
//
// See the workspaces design proposal at
// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
// more information.
@ -2036,6 +2028,8 @@
// GOENV
// The location of the Go environment configuration file.
// Cannot be set using 'go env -w'.
// Setting GOENV=off in the environment disables the use of the
// default configuration file.
// GOFLAGS
// A space-separated list of -flag=value settings to apply
// to go commands by default, when the given flag is known by
@ -2073,6 +2067,14 @@
// GOVCS
// Lists version control commands that may be used with matching servers.
// See 'go help vcs'.
// GOWORK
// In module aware mode, use the given go.work file as a workspace file.
// By default or when GOWORK is "auto", the go command searches for a
// file named go.work in the current directory and then containing directories
// until one is found. If a valid go.work file is found, the modules
// specified will collectively be used as the main modules. If GOWORK
// is "off", or a go.work file is not found in "auto" mode, workspace
// mode is disabled.
//
// Environment variables for use with cgo:
//

View File

@ -133,7 +133,7 @@ func TestMain(m *testing.M) {
}
gotool, err := testenv.GoTool()
if err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "locating go tool: ", err)
os.Exit(2)
}

View File

@ -62,13 +62,6 @@ func AddModFlag(flags *flag.FlagSet) {
flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "")
}
// AddWorkfileFlag adds the workfile flag to the flag set. It enables workspace
// mode for commands that support it by resetting the cfg.WorkFile variable
// to "" (equivalent to auto) rather than off.
func AddWorkfileFlag(flags *flag.FlagSet) {
flags.Var(explicitStringFlag{value: &cfg.WorkFile, explicit: &cfg.WorkFileExplicit}, "workfile", "")
}
// AddModCommonFlags adds the module-related flags common to build commands
// and 'go mod' subcommands.
func AddModCommonFlags(flags *flag.FlagSet) {

View File

@ -49,10 +49,8 @@ var (
BuildWork bool // -work flag
BuildX bool // -x flag
ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag
WorkFile string // -workfile flag
WorkFileExplicit bool // whether -workfile was set explicitly
ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag
CmdName string // "build", "install", "list", "mod tidy", etc.

View File

@ -154,6 +154,10 @@ func ExtraEnvVars() []cfg.EnvVar {
}
modload.InitWorkfile()
gowork := modload.WorkFilePath()
// As a special case, if a user set off explicitly, report that in GOWORK.
if cfg.Getenv("GOWORK") == "off" {
gowork = "off"
}
return []cfg.EnvVar{
{Name: "GOMOD", Value: gomod},
{Name: "GOWORK", Value: gowork},

View File

@ -506,6 +506,8 @@ General-purpose environment variables:
GOENV
The location of the Go environment configuration file.
Cannot be set using 'go env -w'.
Setting GOENV=off in the environment disables the use of the
default configuration file.
GOFLAGS
A space-separated list of -flag=value settings to apply
to go commands by default, when the given flag is known by
@ -543,6 +545,14 @@ General-purpose environment variables:
GOVCS
Lists version control commands that may be used with matching servers.
See 'go help vcs'.
GOWORK
In module aware mode, use the given go.work file as a workspace file.
By default or when GOWORK is "auto", the go command searches for a
file named go.work in the current directory and then containing directories
until one is found. If a valid go.work file is found, the modules
specified will collectively be used as the main modules. If GOWORK
is "off", or a go.work file is not found in "auto" mode, workspace
mode is disabled.
Environment variables for use with cgo:

View File

@ -316,7 +316,6 @@ For more about modules, see https://golang.org/ref/mod.
func init() {
CmdList.Run = runList // break init cycle
work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
base.AddWorkfileFlag(&CmdList.Flag)
}
var (

View File

@ -819,11 +819,11 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo
}
r := resolvedImportCache.Do(importKey, func() any {
var r resolvedImport
if build.IsLocalImport(path) {
if cfg.ModulesEnabled {
r.dir, r.path, r.err = modload.Lookup(parentPath, parentIsStd, path)
} else if build.IsLocalImport(path) {
r.dir = filepath.Join(parentDir, path)
r.path = dirToImportPath(r.dir)
} else if cfg.ModulesEnabled {
r.dir, r.path, r.err = modload.Lookup(parentPath, parentIsStd, path)
} else if mode&ResolveImport != 0 {
// We do our own path resolution, because we want to
// find out the key to use in packageCache without the
@ -1113,6 +1113,7 @@ func dirAndRoot(path string, dir, root string) (string, string) {
}
if !str.HasFilePathPrefix(dir, root) || len(dir) <= len(root) || dir[len(root)] != filepath.Separator || path != "command-line-arguments" && !build.IsLocalImport(path) && filepath.Join(root, path) != dir {
debug.PrintStack()
base.Fatalf("unexpected directory layout:\n"+
" import path: %s\n"+
" root: %s\n"+
@ -2235,13 +2236,17 @@ func (p *Package) setBuildInfo() {
var debugModFromModinfo func(*modinfo.ModulePublic) *debug.Module
debugModFromModinfo = func(mi *modinfo.ModulePublic) *debug.Module {
version := mi.Version
if version == "" {
version = "(devel)"
}
dm := &debug.Module{
Path: mi.Path,
Version: mi.Version,
Version: version,
}
if mi.Replace != nil {
dm.Replace = debugModFromModinfo(mi.Replace)
} else {
} else if mi.Version != "" {
dm.Sum = modfetch.Sum(module.Version{Path: mi.Path, Version: mi.Version})
}
return dm
@ -2424,12 +2429,7 @@ func (p *Package) setBuildInfo() {
appendSetting("vcs.modified", strconv.FormatBool(st.Uncommitted))
}
text, err := info.MarshalText()
if err != nil {
setPkgErrorf("error formatting build info: %v", err)
return
}
p.Internal.BuildInfo = string(text)
p.Internal.BuildInfo = info.String()
}
// SafeArg reports whether arg is a "safe" command-line argument,

View File

@ -70,7 +70,6 @@ func init() {
// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
base.AddModCommonFlags(&cmdDownload.Flag)
base.AddWorkfileFlag(&cmdDownload.Flag)
}
type moduleJSON struct {

View File

@ -42,7 +42,6 @@ var (
func init() {
cmdGraph.Flag.Var(&graphGo, "go", "")
base.AddModCommonFlags(&cmdGraph.Flag)
base.AddWorkfileFlag(&cmdGraph.Flag)
}
func runGraph(ctx context.Context, cmd *base.Command, args []string) {

View File

@ -39,7 +39,6 @@ See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'.
func init() {
base.AddModCommonFlags(&cmdVerify.Flag)
base.AddWorkfileFlag(&cmdVerify.Flag)
}
func runVerify(ctx context.Context, cmd *base.Command, args []string) {

View File

@ -59,7 +59,6 @@ var (
func init() {
cmdWhy.Run = runWhy // break init cycle
base.AddModCommonFlags(&cmdWhy.Flag)
base.AddWorkfileFlag(&cmdWhy.Flag)
}
func runWhy(ctx context.Context, cmd *base.Command, args []string) {

View File

@ -298,16 +298,13 @@ func (r *codeRepo) Latest() (*RevInfo, error) {
// If statVers is a valid module version, it is used for the Version field.
// Otherwise, the Version is derived from the passed-in info and recent tags.
func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, error) {
info2 := &RevInfo{
Name: info.Name,
Short: info.Short,
Time: info.Time,
}
// If this is a plain tag (no dir/ prefix)
// and the module path is unversioned,
// and if the underlying file tree has no go.mod,
// then allow using the tag with a +incompatible suffix.
//
// (If the version is +incompatible, then the go.mod file must not exist:
// +incompatible is not an ongoing opt-out from semantic import versioning.)
var canUseIncompatible func() bool
canUseIncompatible = func() bool {
var ok bool
@ -321,19 +318,12 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
return ok
}
invalidf := func(format string, args ...any) error {
return &module.ModuleError{
Path: r.modPath,
Err: &module.InvalidVersionError{
Version: info2.Version,
Err: fmt.Errorf(format, args...),
},
}
}
// checkGoMod verifies that the go.mod file for the module exists or does not
// exist as required by info2.Version and the module path represented by r.
checkGoMod := func() (*RevInfo, error) {
// checkCanonical verifies that the canonical version v is compatible with the
// module path represented by r, adding a "+incompatible" suffix if needed.
//
// If statVers is also canonical, checkCanonical also verifies that v is
// either statVers or statVers with the added "+incompatible" suffix.
checkCanonical := func(v string) (*RevInfo, error) {
// If r.codeDir is non-empty, then the go.mod file must exist: the module
// author — not the module consumer, — gets to decide how to carve up the repo
// into modules.
@ -344,73 +334,91 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
// r.findDir verifies both of these conditions. Execute it now so that
// r.Stat will correctly return a notExistError if the go.mod location or
// declared module path doesn't match.
_, _, _, err := r.findDir(info2.Version)
_, _, _, err := r.findDir(v)
if err != nil {
// TODO: It would be nice to return an error like "not a module".
// Right now we return "missing go.mod", which is a little confusing.
return nil, &module.ModuleError{
Path: r.modPath,
Err: &module.InvalidVersionError{
Version: info2.Version,
Version: v,
Err: notExistError{err: err},
},
}
}
// If the version is +incompatible, then the go.mod file must not exist:
// +incompatible is not an ongoing opt-out from semantic import versioning.
if strings.HasSuffix(info2.Version, "+incompatible") {
if !canUseIncompatible() {
if r.pathMajor != "" {
return nil, invalidf("+incompatible suffix not allowed: module path includes a major version suffix, so major version must match")
} else {
return nil, invalidf("+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required")
}
}
if err := module.CheckPathMajor(strings.TrimSuffix(info2.Version, "+incompatible"), r.pathMajor); err == nil {
return nil, invalidf("+incompatible suffix not allowed: major version %s is compatible", semver.Major(info2.Version))
invalidf := func(format string, args ...any) error {
return &module.ModuleError{
Path: r.modPath,
Err: &module.InvalidVersionError{
Version: v,
Err: fmt.Errorf(format, args...),
},
}
}
return info2, nil
// Add the +incompatible suffix if needed or requested explicitly, and
// verify that its presence or absence is appropriate for this version
// (which depends on whether it has an explicit go.mod file).
if v == strings.TrimSuffix(statVers, "+incompatible") {
v = statVers
}
base := strings.TrimSuffix(v, "+incompatible")
var errIncompatible error
if !module.MatchPathMajor(base, r.pathMajor) {
if canUseIncompatible() {
v = base + "+incompatible"
} else {
if r.pathMajor != "" {
errIncompatible = invalidf("module path includes a major version suffix, so major version must match")
} else {
errIncompatible = invalidf("module contains a go.mod file, so module path must match major version (%q)", path.Join(r.pathPrefix, semver.Major(v)))
}
}
} else if strings.HasSuffix(v, "+incompatible") {
errIncompatible = invalidf("+incompatible suffix not allowed: major version %s is compatible", semver.Major(v))
}
if statVers != "" && statVers == module.CanonicalVersion(statVers) {
// Since the caller-requested version is canonical, it would be very
// confusing to resolve it to anything but itself, possibly with a
// "+incompatible" suffix. Error out explicitly.
if statBase := strings.TrimSuffix(statVers, "+incompatible"); statBase != base {
return nil, &module.ModuleError{
Path: r.modPath,
Err: &module.InvalidVersionError{
Version: statVers,
Err: fmt.Errorf("resolves to version %v (%s is not a tag)", v, statBase),
},
}
}
}
if errIncompatible != nil {
return nil, errIncompatible
}
return &RevInfo{
Name: info.Name,
Short: info.Short,
Time: info.Time,
Version: v,
}, nil
}
// Determine version.
//
// If statVers is canonical, then the original call was repo.Stat(statVers).
// Since the version is canonical, we must not resolve it to anything but
// itself, possibly with a '+incompatible' annotation: we do not need to do
// the work required to look for an arbitrary pseudo-version.
if statVers != "" && statVers == module.CanonicalVersion(statVers) {
info2.Version = statVers
if module.IsPseudoVersion(info2.Version) {
if err := r.validatePseudoVersion(info, info2.Version); err != nil {
return nil, err
}
return checkGoMod()
if module.IsPseudoVersion(statVers) {
if err := r.validatePseudoVersion(info, statVers); err != nil {
return nil, err
}
if err := module.CheckPathMajor(info2.Version, r.pathMajor); err != nil {
if canUseIncompatible() {
info2.Version += "+incompatible"
return checkGoMod()
} else {
if vErr, ok := err.(*module.InvalidVersionError); ok {
// We're going to describe why the version is invalid in more detail,
// so strip out the existing “invalid version” wrapper.
err = vErr.Err
}
return nil, invalidf("module contains a go.mod file, so major version must be compatible: %v", err)
}
}
return checkGoMod()
return checkCanonical(statVers)
}
// statVers is empty or non-canonical, so we need to resolve it to a canonical
// version or pseudo-version.
// statVers is not a pseudo-version, so we need to either resolve it to a
// canonical version or verify that it is already a canonical tag
// (not a branch).
// Derive or verify a version from a code repo tag.
// Tag must have a prefix matching codeDir.
@ -441,71 +449,62 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
if v == "" || !strings.HasPrefix(trimmed, v) {
return "", false // Invalid or incomplete version (just vX or vX.Y).
}
if isRetracted(v) {
return "", false
}
if v == trimmed {
tagIsCanonical = true
}
if err := module.CheckPathMajor(v, r.pathMajor); err != nil {
if canUseIncompatible() {
return v + "+incompatible", tagIsCanonical
}
return "", false
}
return v, tagIsCanonical
}
// If the VCS gave us a valid version, use that.
if v, tagIsCanonical := tagToVersion(info.Version); tagIsCanonical {
info2.Version = v
return checkGoMod()
if info, err := checkCanonical(v); err == nil {
return info, err
}
}
// Look through the tags on the revision for either a usable canonical version
// or an appropriate base for a pseudo-version.
var pseudoBase string
var (
highestCanonical string
pseudoBase string
)
for _, pathTag := range info.Tags {
v, tagIsCanonical := tagToVersion(pathTag)
if tagIsCanonical {
if statVers != "" && semver.Compare(v, statVers) == 0 {
// The user requested a non-canonical version, but the tag for the
// canonical equivalent refers to the same revision. Use it.
info2.Version = v
return checkGoMod()
if statVers != "" && semver.Compare(v, statVers) == 0 {
// The tag is equivalent to the version requested by the user.
if tagIsCanonical {
// This tag is the canonical form of the requested version,
// not some other form with extra build metadata.
// Use this tag so that the resolved version will match exactly.
// (If it isn't actually allowed, we'll error out in checkCanonical.)
return checkCanonical(v)
} else {
// Save the highest canonical tag for the revision. If we don't find a
// better match, we'll use it as the canonical version.
// The user explicitly requested something equivalent to this tag. We
// can't use the version from the tag directly: since the tag is not
// canonical, it could be ambiguous. For example, tags v0.0.1+a and
// v0.0.1+b might both exist and refer to different revisions.
//
// NOTE: Do not replace this with semver.Max. Despite the name,
// semver.Max *also* canonicalizes its arguments, which uses
// semver.Canonical instead of module.CanonicalVersion and thereby
// strips our "+incompatible" suffix.
if semver.Compare(info2.Version, v) < 0 {
info2.Version = v
}
// The tag is otherwise valid for the module, so we can at least use it as
// the base of an unambiguous pseudo-version.
//
// If multiple tags match, tagToVersion will canonicalize them to the same
// base version.
pseudoBase = v
}
}
// Save the highest non-retracted canonical tag for the revision.
// If we don't find a better match, we'll use it as the canonical version.
if tagIsCanonical && semver.Compare(highestCanonical, v) < 0 && !isRetracted(v) {
if module.MatchPathMajor(v, r.pathMajor) || canUseIncompatible() {
highestCanonical = v
}
} else if v != "" && semver.Compare(v, statVers) == 0 {
// The user explicitly requested something equivalent to this tag. We
// can't use the version from the tag directly: since the tag is not
// canonical, it could be ambiguous. For example, tags v0.0.1+a and
// v0.0.1+b might both exist and refer to different revisions.
//
// The tag is otherwise valid for the module, so we can at least use it as
// the base of an unambiguous pseudo-version.
//
// If multiple tags match, tagToVersion will canonicalize them to the same
// base version.
pseudoBase = v
}
}
// If we found any canonical tag for the revision, return it.
// If we found a valid canonical tag for the revision, return it.
// Even if we found a good pseudo-version base, a canonical version is better.
if info2.Version != "" {
return checkGoMod()
if highestCanonical != "" {
return checkCanonical(highestCanonical)
}
// Find the highest tagged version in the revision's history, subject to
@ -528,11 +527,10 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor("v0"))
}
}
pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid
pseudoBase, _ = tagToVersion(tag)
}
info2.Version = module.PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short)
return checkGoMod()
return checkCanonical(module.PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short))
}
// validatePseudoVersion checks that version has a major version compatible with
@ -556,10 +554,6 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string)
}
}()
if err := module.CheckPathMajor(version, r.pathMajor); err != nil {
return err
}
rev, err := module.PseudoVersionRev(version)
if err != nil {
return err

View File

@ -418,171 +418,204 @@ var codeRepoTests = []codeRepoTest{
zipSum: "h1:JItBZ+gwA5WvtZEGEbuDL4lUttGtLrs53lmdurq3bOg=",
zipFileHash: "9ea9ae1673cffcc44b7fdd3cc89953d68c102449b46c982dbf085e4f2e394da5",
},
{
// Git branch with a semver name, +incompatible version, and no go.mod file.
vcs: "git",
path: "vcs-test.golang.org/go/mod/gitrepo1",
rev: "v2.3.4+incompatible",
err: `resolves to version v2.0.1+incompatible (v2.3.4 is not a tag)`,
},
{
// Git branch with a semver name, matching go.mod file, and compatible version.
vcs: "git",
path: "vcs-test.golang.org/git/semver-branch.git",
rev: "v1.0.0",
err: `resolves to version v0.1.1-0.20220202191944-09c4d8f6938c (v1.0.0 is not a tag)`,
},
{
// Git branch with a semver name, matching go.mod file, and disallowed +incompatible version.
// The version/tag mismatch takes precedence over the +incompatible mismatched.
vcs: "git",
path: "vcs-test.golang.org/git/semver-branch.git",
rev: "v2.0.0+incompatible",
err: `resolves to version v0.1.0 (v2.0.0 is not a tag)`,
},
{
// Git branch with a semver name, matching go.mod file, and mismatched version.
// The version/tag mismatch takes precedence over the +incompatible mismatched.
vcs: "git",
path: "vcs-test.golang.org/git/semver-branch.git",
rev: "v2.0.0",
err: `resolves to version v0.1.0 (v2.0.0 is not a tag)`,
},
{
// v3.0.0-devel is the same as tag v4.0.0-beta.1, but v4.0.0-beta.1 would
// not be allowed because it is incompatible and a go.mod file exists.
// The error message should refer to a valid pseudo-version, not the
// unusable semver tag.
vcs: "git",
path: "vcs-test.golang.org/git/semver-branch.git",
rev: "v3.0.0-devel",
err: `resolves to version v0.1.1-0.20220203155313-d59622f6e4d7 (v3.0.0-devel is not a tag)`,
},
}
func TestCodeRepo(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
tmpdir := t.TempDir()
tmpdir, err := os.MkdirTemp("", "modfetch-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
for _, tt := range codeRepoTests {
f := func(tt codeRepoTest) func(t *testing.T) {
return func(t *testing.T) {
t.Parallel()
if tt.vcs != "mod" {
testenv.MustHaveExecPath(t, tt.vcs)
}
t.Run("parallel", func(t *testing.T) {
for _, tt := range codeRepoTests {
f := func(tt codeRepoTest) func(t *testing.T) {
return func(t *testing.T) {
t.Parallel()
if tt.vcs != "mod" {
testenv.MustHaveExecPath(t, tt.vcs)
}
repo := Lookup("direct", tt.path)
repo := Lookup("direct", tt.path)
if tt.mpath == "" {
tt.mpath = tt.path
}
if mpath := repo.ModulePath(); mpath != tt.mpath {
t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
}
if tt.mpath == "" {
tt.mpath = tt.path
}
if mpath := repo.ModulePath(); mpath != tt.mpath {
t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
}
info, err := repo.Stat(tt.rev)
if err != nil {
if tt.err != "" {
if !strings.Contains(err.Error(), tt.err) {
t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err)
}
return
}
t.Fatalf("repo.Stat(%q): %v", tt.rev, err)
}
info, err := repo.Stat(tt.rev)
if err != nil {
if tt.err != "" {
t.Errorf("repo.Stat(%q): success, wanted error", tt.rev)
}
if info.Version != tt.version {
t.Errorf("info.Version = %q, want %q", info.Version, tt.version)
}
if info.Name != tt.name {
t.Errorf("info.Name = %q, want %q", info.Name, tt.name)
}
if info.Short != tt.short {
t.Errorf("info.Short = %q, want %q", info.Short, tt.short)
}
if !info.Time.Equal(tt.time) {
t.Errorf("info.Time = %v, want %v", info.Time, tt.time)
if !strings.Contains(err.Error(), tt.err) {
t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err)
}
return
}
t.Fatalf("repo.Stat(%q): %v", tt.rev, err)
}
if tt.err != "" {
t.Errorf("repo.Stat(%q): success, wanted error", tt.rev)
}
if info.Version != tt.version {
t.Errorf("info.Version = %q, want %q", info.Version, tt.version)
}
if info.Name != tt.name {
t.Errorf("info.Name = %q, want %q", info.Name, tt.name)
}
if info.Short != tt.short {
t.Errorf("info.Short = %q, want %q", info.Short, tt.short)
}
if !info.Time.Equal(tt.time) {
t.Errorf("info.Time = %v, want %v", info.Time, tt.time)
}
if tt.gomod != "" || tt.gomodErr != "" {
data, err := repo.GoMod(tt.version)
if err != nil && tt.gomodErr == "" {
t.Errorf("repo.GoMod(%q): %v", tt.version, err)
} else if err != nil && tt.gomodErr != "" {
if err.Error() != tt.gomodErr {
t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomodErr)
}
} else if tt.gomodErr != "" {
t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomodErr)
} else if string(data) != tt.gomod {
t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod)
}
}
needHash := !testing.Short() && (tt.zipFileHash != "" || tt.zipSum != "")
if tt.zip != nil || tt.zipErr != "" || needHash {
f, err := os.CreateTemp(tmpdir, tt.version+".zip.")
if err != nil {
t.Fatalf("os.CreateTemp: %v", err)
}
zipfile := f.Name()
defer func() {
f.Close()
os.Remove(zipfile)
}()
var w io.Writer
var h hash.Hash
if needHash {
h = sha256.New()
w = io.MultiWriter(f, h)
} else {
w = f
}
err = repo.Zip(w, tt.version)
f.Close()
if err != nil {
if tt.zipErr != "" {
if err.Error() == tt.zipErr {
return
}
t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.zipErr)
}
t.Fatalf("repo.Zip(%q): %v", tt.version, err)
}
if tt.zipErr != "" {
t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.zipErr)
}
if tt.zip != nil {
prefix := tt.path + "@" + tt.version + "/"
z, err := zip.OpenReader(zipfile)
if err != nil {
t.Fatalf("open zip %s: %v", zipfile, err)
}
var names []string
for _, file := range z.File {
if !strings.HasPrefix(file.Name, prefix) {
t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix)
continue
}
names = append(names, file.Name[len(prefix):])
}
z.Close()
if !reflect.DeepEqual(names, tt.zip) {
t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
}
}
if needHash {
sum, err := dirhash.HashZip(zipfile, dirhash.Hash1)
if err != nil {
t.Errorf("repo.Zip(%q): %v", tt.version, err)
} else if sum != tt.zipSum {
t.Errorf("repo.Zip(%q): got file with sum %q, want %q", tt.version, sum, tt.zipSum)
} else if zipFileHash := hex.EncodeToString(h.Sum(nil)); zipFileHash != tt.zipFileHash {
t.Errorf("repo.Zip(%q): got file with hash %q, want %q (but content has correct sum)", tt.version, zipFileHash, tt.zipFileHash)
}
if tt.gomod != "" || tt.gomodErr != "" {
data, err := repo.GoMod(tt.version)
if err != nil && tt.gomodErr == "" {
t.Errorf("repo.GoMod(%q): %v", tt.version, err)
} else if err != nil && tt.gomodErr != "" {
if err.Error() != tt.gomodErr {
t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomodErr)
}
} else if tt.gomodErr != "" {
t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomodErr)
} else if string(data) != tt.gomod {
t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod)
}
}
}
t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f(tt))
if strings.HasPrefix(tt.path, vgotest1git) {
for vcs, alt := range altVgotests {
altTest := tt
altTest.vcs = vcs
altTest.path = alt + strings.TrimPrefix(altTest.path, vgotest1git)
if strings.HasPrefix(altTest.mpath, vgotest1git) {
altTest.mpath = alt + strings.TrimPrefix(altTest.mpath, vgotest1git)
needHash := !testing.Short() && (tt.zipFileHash != "" || tt.zipSum != "")
if tt.zip != nil || tt.zipErr != "" || needHash {
f, err := os.CreateTemp(tmpdir, tt.version+".zip.")
if err != nil {
t.Fatalf("os.CreateTemp: %v", err)
}
var m map[string]string
if alt == vgotest1hg {
m = hgmap
zipfile := f.Name()
defer func() {
f.Close()
os.Remove(zipfile)
}()
var w io.Writer
var h hash.Hash
if needHash {
h = sha256.New()
w = io.MultiWriter(f, h)
} else {
w = f
}
err = repo.Zip(w, tt.version)
f.Close()
if err != nil {
if tt.zipErr != "" {
if err.Error() == tt.zipErr {
return
}
t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.zipErr)
}
t.Fatalf("repo.Zip(%q): %v", tt.version, err)
}
if tt.zipErr != "" {
t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.zipErr)
}
if tt.zip != nil {
prefix := tt.path + "@" + tt.version + "/"
z, err := zip.OpenReader(zipfile)
if err != nil {
t.Fatalf("open zip %s: %v", zipfile, err)
}
var names []string
for _, file := range z.File {
if !strings.HasPrefix(file.Name, prefix) {
t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix)
continue
}
names = append(names, file.Name[len(prefix):])
}
z.Close()
if !reflect.DeepEqual(names, tt.zip) {
t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
}
}
if needHash {
sum, err := dirhash.HashZip(zipfile, dirhash.Hash1)
if err != nil {
t.Errorf("repo.Zip(%q): %v", tt.version, err)
} else if sum != tt.zipSum {
t.Errorf("repo.Zip(%q): got file with sum %q, want %q", tt.version, sum, tt.zipSum)
} else if zipFileHash := hex.EncodeToString(h.Sum(nil)); zipFileHash != tt.zipFileHash {
t.Errorf("repo.Zip(%q): got file with hash %q, want %q (but content has correct sum)", tt.version, zipFileHash, tt.zipFileHash)
}
}
altTest.version = remap(altTest.version, m)
altTest.name = remap(altTest.name, m)
altTest.short = remap(altTest.short, m)
altTest.rev = remap(altTest.rev, m)
altTest.err = remap(altTest.err, m)
altTest.gomodErr = remap(altTest.gomodErr, m)
altTest.zipErr = remap(altTest.zipErr, m)
altTest.zipSum = ""
altTest.zipFileHash = ""
t.Run(strings.ReplaceAll(altTest.path, "/", "_")+"/"+altTest.rev, f(altTest))
}
}
}
})
t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f(tt))
if strings.HasPrefix(tt.path, vgotest1git) {
for vcs, alt := range altVgotests {
altTest := tt
altTest.vcs = vcs
altTest.path = alt + strings.TrimPrefix(altTest.path, vgotest1git)
if strings.HasPrefix(altTest.mpath, vgotest1git) {
altTest.mpath = alt + strings.TrimPrefix(altTest.mpath, vgotest1git)
}
var m map[string]string
if alt == vgotest1hg {
m = hgmap
}
altTest.version = remap(altTest.version, m)
altTest.name = remap(altTest.name, m)
altTest.short = remap(altTest.short, m)
altTest.rev = remap(altTest.rev, m)
altTest.err = remap(altTest.err, m)
altTest.gomodErr = remap(altTest.gomodErr, m)
altTest.zipErr = remap(altTest.zipErr, m)
altTest.zipSum = ""
altTest.zipFileHash = ""
t.Run(strings.ReplaceAll(altTest.path, "/", "_")+"/"+altTest.rev, f(altTest))
}
}
}
}
var hgmap = map[string]string{

View File

@ -319,7 +319,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e
//
// If the hash does not match go.sum (or the sumdb if enabled), hashZip returns
// an error and does not write ziphashfile.
func hashZip(mod module.Version, zipfile, ziphashfile string) error {
func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) {
hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
if err != nil {
return err
@ -331,16 +331,17 @@ func hashZip(mod module.Version, zipfile, ziphashfile string) error {
if err != nil {
return err
}
defer func() {
if closeErr := hf.Close(); err == nil && closeErr != nil {
err = closeErr
}
}()
if err := hf.Truncate(int64(len(hash))); err != nil {
return err
}
if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
return err
}
if err := hf.Close(); err != nil {
return err
}
return nil
}

View File

@ -248,12 +248,26 @@ func (e *invalidImportError) Unwrap() error {
// return the module, its root directory, and a list of other modules that
// lexically could have provided the package but did not.
func importFromModules(ctx context.Context, path string, rs *Requirements, mg *ModuleGraph) (m module.Version, dir string, altMods []module.Version, err error) {
invalidf := func(format string, args ...interface{}) (module.Version, string, []module.Version, error) {
return module.Version{}, "", nil, &invalidImportError{
importPath: path,
err: fmt.Errorf(format, args...),
}
}
if strings.Contains(path, "@") {
return module.Version{}, "", nil, fmt.Errorf("import path should not have @version")
return invalidf("import path %q should not have @version", path)
}
if build.IsLocalImport(path) {
return module.Version{}, "", nil, fmt.Errorf("relative import not supported")
return invalidf("%q is relative, but relative import paths are not supported in module mode", path)
}
if filepath.IsAbs(path) {
return invalidf("%q is not a package path; see 'go help packages'", path)
}
if search.IsMetaPackage(path) {
return invalidf("%q is not an importable package; see 'go help packages'", path)
}
if path == "C" {
// There's no directory for import "C".
return module.Version{}, "", nil, nil

View File

@ -288,20 +288,20 @@ func BinDir() string {
// operate in workspace mode. It should not be called by other commands,
// for example 'go mod tidy', that don't operate in workspace mode.
func InitWorkfile() {
switch cfg.WorkFile {
switch gowork := cfg.Getenv("GOWORK"); gowork {
case "off":
workFilePath = ""
case "", "auto":
workFilePath = findWorkspaceFile(base.Cwd())
default:
if !filepath.IsAbs(cfg.WorkFile) {
base.Fatalf("the path provided to -workfile must be an absolute path")
if !filepath.IsAbs(gowork) {
base.Fatalf("the path provided to GOWORK must be an absolute path")
}
workFilePath = cfg.WorkFile
workFilePath = gowork
}
}
// WorkFilePath returns the path of the go.work file, or "" if not in
// WorkFilePath returns the absolute path of the go.work file, or "" if not in
// workspace mode. WorkFilePath must be called after InitWorkfile.
func WorkFilePath() string {
return workFilePath
@ -610,6 +610,9 @@ func UpdateWorkFile(wf *modfile.WorkFile) {
missingModulePaths := map[string]string{} // module directory listed in file -> abspath modroot
for _, d := range wf.Use {
if d.Path == "" {
continue // d is marked for deletion.
}
modRoot := d.Path
if d.ModulePath == "" {
missingModulePaths[d.Path] = modRoot
@ -1030,11 +1033,25 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile
for _, r := range modFiles[i].Replace {
if replacedByWorkFile[r.Old.Path] {
continue
} else if prev, ok := replacements[r.Old]; ok && !curModuleReplaces[r.Old] && prev != r.New {
base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v\nuse \"go work edit -replace %v=[override]\" to resolve", r.Old, prev, r.New, r.Old)
}
var newV module.Version = r.New
if WorkFilePath() != "" && newV.Version == "" && !filepath.IsAbs(newV.Path) {
// Since we are in a workspace, we may be loading replacements from
// multiple go.mod files. Relative paths in those replacement are
// relative to the go.mod file, not the workspace, so the same string
// may refer to two different paths and different strings may refer to
// the same path. Convert them all to be absolute instead.
//
// (We could do this outside of a workspace too, but it would mean that
// replacement paths in error strings needlessly differ from what's in
// the go.mod file.)
newV.Path = filepath.Join(rootDirs[i], newV.Path)
}
if prev, ok := replacements[r.Old]; ok && !curModuleReplaces[r.Old] && prev != newV {
base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v\nuse \"go work edit -replace %v=[override]\" to resolve", r.Old, prev, newV, r.Old)
}
curModuleReplaces[r.Old] = true
replacements[r.Old] = r.New
replacements[r.Old] = newV
v, ok := mainModules.highestReplaced[r.Old.Path]
if !ok || semver.Compare(r.Old.Version, v) > 0 {
@ -1092,7 +1109,7 @@ func setDefaultBuildMod() {
if inWorkspaceMode() && cfg.BuildMod != "readonly" {
base.Fatalf("go: -mod may only be set to readonly when in workspace mode, but it is set to %q"+
"\n\tRemove the -mod flag to use the default readonly value,"+
"\n\tor set -workfile=off to disable workspace mode.", cfg.BuildMod)
"\n\tor set GOWORK=off to disable workspace mode.", cfg.BuildMod)
}
// Don't override an explicit '-mod=' argument.
return

View File

@ -479,7 +479,11 @@ func matchLocalDirs(ctx context.Context, modRoots []string, m *search.Match, rs
}
if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
m.Dirs = []string{}
m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
scope := "main module or its selected dependencies"
if inWorkspaceMode() {
scope = "modules listed in go.work or their selected dependencies"
}
m.AddError(fmt.Errorf("directory prefix %s does not contain %s", base.ShortPath(absDir), scope))
return
}
}
@ -601,7 +605,11 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
pkg := pathInModuleCache(ctx, absDir, rs)
if pkg == "" {
return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir))
scope := "main module or its selected dependencies"
if inWorkspaceMode() {
scope = "modules listed in go.work or their selected dependencies"
}
return "", fmt.Errorf("directory %s outside %s", base.ShortPath(absDir), scope)
}
return pkg, nil
}
@ -1667,24 +1675,6 @@ func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (ch
// load loads an individual package.
func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
if strings.Contains(pkg.path, "@") {
// Leave for error during load.
return
}
if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) {
// Leave for error during load.
// (Module mode does not allow local imports.)
return
}
if search.IsMetaPackage(pkg.path) {
pkg.err = &invalidImportError{
importPath: pkg.path,
err: fmt.Errorf("%q is not an importable package; see 'go help packages'", pkg.path),
}
return
}
var mg *ModuleGraph
if ld.requirements.pruning == unpruned {
var err error

View File

@ -65,7 +65,6 @@ func init() {
CmdRun.Run = runRun // break init loop
work.AddBuildFlags(CmdRun, work.DefaultBuildFlags)
base.AddWorkfileFlag(&CmdRun.Flag)
CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "")
}

View File

@ -28,7 +28,6 @@ import (
func init() {
work.AddBuildFlags(CmdTest, work.OmitVFlag)
base.AddWorkfileFlag(&CmdTest.Flag)
cf := CmdTest.Flag
cf.BoolVar(&testC, "c", false, "")

View File

@ -6,7 +6,6 @@
package version
import (
"bytes"
"context"
"debug/buildinfo"
"errors"
@ -156,12 +155,8 @@ func scanFile(file string, info fs.FileInfo, mustPrint bool) {
fmt.Printf("%s: %s\n", file, bi.GoVersion)
bi.GoVersion = "" // suppress printing go version again
mod, err := bi.MarshalText()
if err != nil {
fmt.Fprintf(os.Stderr, "%s: formatting build info: %v\n", file, err)
return
}
mod := bi.String()
if *versionM && len(mod) > 0 {
fmt.Printf("\t%s\n", bytes.ReplaceAll(mod[:len(mod)-1], []byte("\n"), []byte("\n\t")))
fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t"))
}
}

View File

@ -13,6 +13,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/trace"
"cmd/go/internal/work"
)
@ -54,6 +55,7 @@ See also: go fmt, go fix.
func runVet(ctx context.Context, cmd *base.Command, args []string) {
vetFlags, pkgArgs := vetFlags(args)
modload.InitWorkfile() // The vet command does custom flag processing; initialize workspaces after that.
if cfg.DebugTrace != "" {
var close func() error

View File

@ -130,14 +130,6 @@ and test commands:
directory, but it is not accessed. When -modfile is specified, an
alternate go.sum file is also used: its path is derived from the
-modfile flag by trimming the ".mod" extension and appending ".sum".
-workfile file
in module aware mode, use the given go.work file as a workspace file.
By default or when -workfile is "auto", the go command searches for a
file named go.work in the current directory and then containing directories
until one is found. If a valid go.work file is found, the modules
specified will collectively be used as the main modules. If -workfile
is "off", or a go.work file is not found in "auto" mode, workspace
mode is disabled.
-overlay file
read a JSON config file that provides an overlay for build operations.
The file is a JSON struct with a single field, named 'Replace', that
@ -217,7 +209,6 @@ func init() {
AddBuildFlags(CmdBuild, DefaultBuildFlags)
AddBuildFlags(CmdInstall, DefaultBuildFlags)
base.AddWorkfileFlag(&CmdBuild.Flag)
}
// Note that flags consulted by other parts of the code

View File

@ -2015,6 +2015,7 @@ func (b *Builder) showOutput(a *Action, dir, desc, out string) {
if reldir := base.ShortPath(dir); reldir != dir {
suffix = strings.ReplaceAll(suffix, " "+dir, " "+reldir)
suffix = strings.ReplaceAll(suffix, "\n"+dir, "\n"+reldir)
suffix = strings.ReplaceAll(suffix, "\n\t"+dir, "\n\t"+reldir)
}
suffix = strings.ReplaceAll(suffix, " "+b.WorkDir, " $WORK")

View File

@ -131,6 +131,7 @@ var validCompilerFlagsWithNextArg = []string{
"-D",
"-U",
"-I",
"-F",
"-framework",
"-include",
"-isysroot",

View File

@ -15,6 +15,7 @@ var goodCompilerFlags = [][]string{
{"-Ufoo"},
{"-Ufoo1"},
{"-F/Qt"},
{"-F", "/Qt"},
{"-I/"},
{"-I/etc/passwd"},
{"-I."},

View File

@ -63,19 +63,14 @@ writing it back to go.mod.
The -json flag prints the final go.work file in JSON format instead of
writing it back to go.mod. The JSON output corresponds to these Go types:
type Module struct {
Path string
Version string
}
type GoWork struct {
Go string
Directory []Directory
Replace []Replace
Go string
Use []Use
Replace []Replace
}
type Use struct {
Path string
DiskPath string
ModulePath string
}
@ -84,6 +79,11 @@ writing it back to go.mod. The JSON output corresponds to these Go types:
New Module
}
type Module struct {
Path string
Version string
}
See the workspaces design proposal at
https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
more information.
@ -110,22 +110,9 @@ func init() {
cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "")
cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
base.AddWorkfileFlag(&cmdEdit.Flag)
}
func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
anyFlags :=
*editGo != "" ||
*editJSON ||
*editPrint ||
*editFmt ||
len(workedits) > 0
if !anyFlags {
base.Fatalf("go: no flags specified (see 'go help work edit').")
}
if *editJSON && *editPrint {
base.Fatalf("go: cannot use both -json and -print")
}
@ -147,6 +134,21 @@ func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
}
}
if gowork == "" {
base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
}
anyFlags :=
*editGo != "" ||
*editJSON ||
*editPrint ||
*editFmt ||
len(workedits) > 0
if !anyFlags {
base.Fatalf("go: no flags specified (see 'go help work edit').")
}
workFile, err := modload.ReadWorkFile(gowork)
if err != nil {
base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)

View File

@ -33,7 +33,6 @@ current go version will also be listed in the go.work file.
func init() {
base.AddModCommonFlags(&cmdInit.Flag)
base.AddWorkfileFlag(&cmdInit.Flag)
}
func runInit(ctx context.Context, cmd *base.Command, args []string) {
@ -41,12 +40,10 @@ func runInit(ctx context.Context, cmd *base.Command, args []string) {
modload.ForceUseModules = true
// TODO(matloob): support using the -workfile path
// To do that properly, we'll have to make the module directories
// make dirs relative to workFile path before adding the paths to
// the directory entries
workFile := filepath.Join(base.Cwd(), "go.work")
workFile := modload.WorkFilePath()
if workFile == "" {
workFile = filepath.Join(base.Cwd(), "go.work")
}
modload.CreateWorkFile(ctx, workFile, args)
}

View File

@ -39,13 +39,14 @@ that in each workspace module.
func init() {
base.AddModCommonFlags(&cmdSync.Flag)
base.AddWorkfileFlag(&cmdSync.Flag)
}
func runSync(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
modload.ForceUseModules = true
modload.InitWorkfile()
if modload.WorkFilePath() == "" {
base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
}
workGraph := modload.LoadModGraph(ctx, "")
_ = workGraph

View File

@ -10,7 +10,10 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/fsys"
"cmd/go/internal/modload"
"cmd/go/internal/str"
"context"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
@ -39,47 +42,50 @@ func init() {
cmdUse.Run = runUse // break init cycle
base.AddModCommonFlags(&cmdUse.Flag)
base.AddWorkfileFlag(&cmdUse.Flag)
}
func runUse(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
modload.ForceUseModules = true
var gowork string
modload.InitWorkfile()
gowork = modload.WorkFilePath()
if gowork == "" {
base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
}
workFile, err := modload.ReadWorkFile(gowork)
if err != nil {
base.Fatalf("go: %v", err)
}
workDir := filepath.Dir(gowork) // Absolute, since gowork itself is absolute.
haveDirs := make(map[string]bool)
for _, dir := range workFile.Use {
haveDirs[filepath.Join(filepath.Dir(gowork), filepath.FromSlash(dir.Path))] = true
haveDirs := make(map[string][]string) // absolute → original(s)
for _, use := range workFile.Use {
var abs string
if filepath.IsAbs(use.Path) {
abs = filepath.Clean(use.Path)
} else {
abs = filepath.Join(workDir, use.Path)
}
haveDirs[abs] = append(haveDirs[abs], use.Path)
}
addDirs := make(map[string]bool)
removeDirs := make(map[string]bool)
// keepDirs maps each absolute path to keep to the literal string to use for
// that path (either an absolute or a relative path), or the empty string if
// all entries for the absolute path should be removed.
keepDirs := make(map[string]string)
// lookDir updates the entry in keepDirs for the directory dir,
// which is either absolute or relative to the current working directory
// (not necessarily the directory containing the workfile).
lookDir := func(dir string) {
absDir := filepath.Join(base.Cwd(), dir)
// If the path is absolute, keep it absolute. If it's relative,
// make it relative to the go.work file rather than the working directory.
if !filepath.IsAbs(dir) {
rel, err := filepath.Rel(filepath.Dir(gowork), absDir)
if err == nil {
dir = rel
}
}
fi, err := os.Stat(filepath.Join(dir, "go.mod"))
absDir, dir := pathRel(workDir, dir)
fi, err := os.Stat(filepath.Join(absDir, "go.mod"))
if err != nil {
if os.IsNotExist(err) {
if haveDirs[absDir] {
removeDirs[dir] = true
}
keepDirs[absDir] = ""
return
}
base.Errorf("go: %v", err)
@ -89,31 +95,96 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) {
base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod"))
}
if !haveDirs[absDir] {
addDirs[dir] = true
if dup := keepDirs[absDir]; dup != "" && dup != dir {
base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
}
keepDirs[absDir] = dir
}
for _, useDir := range args {
if *useR {
fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() {
return nil
}
lookDir(path)
return nil
})
if !*useR {
lookDir(useDir)
continue
}
lookDir(useDir)
// Add or remove entries for any subdirectories that still exist.
err := fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() {
if info.Mode()&fs.ModeSymlink != 0 {
if target, err := fsys.Stat(path); err == nil && target.IsDir() {
fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
}
}
return nil
}
lookDir(path)
return nil
})
if err != nil && !errors.Is(err, os.ErrNotExist) {
base.Errorf("go: %v", err)
}
// Remove entries for subdirectories that no longer exist.
// Because they don't exist, they will be skipped by Walk.
absArg, _ := pathRel(workDir, useDir)
for absDir, _ := range haveDirs {
if str.HasFilePathPrefix(absDir, absArg) {
if _, ok := keepDirs[absDir]; !ok {
keepDirs[absDir] = "" // Mark for deletion.
}
}
}
}
for dir := range removeDirs {
workFile.DropUse(filepath.ToSlash(dir))
}
for dir := range addDirs {
workFile.AddUse(filepath.ToSlash(dir), "")
base.ExitIfErrors()
for absDir, keepDir := range keepDirs {
nKept := 0
for _, dir := range haveDirs[absDir] {
if dir == keepDir { // (note that dir is always non-empty)
nKept++
} else {
workFile.DropUse(dir)
}
}
if keepDir != "" && nKept != 1 {
// If we kept more than one copy, delete them all.
// We'll recreate a unique copy with AddUse.
if nKept > 1 {
workFile.DropUse(keepDir)
}
workFile.AddUse(keepDir, "")
}
}
modload.UpdateWorkFile(workFile)
modload.WriteWorkFile(gowork, workFile)
}
// pathRel returns the absolute and canonical forms of dir for use in a
// go.work file located in directory workDir.
//
// If dir is relative, it is intepreted relative to base.Cwd()
// and its canonical form is relative to workDir if possible.
// If dir is absolute or cannot be made relative to workDir,
// its canonical form is absolute.
//
// Canonical absolute paths are clean.
// Canonical relative paths are clean and slash-separated.
func pathRel(workDir, dir string) (abs, canonical string) {
if filepath.IsAbs(dir) {
abs = filepath.Clean(dir)
return abs, abs
}
abs = filepath.Join(base.Cwd(), dir)
rel, err := filepath.Rel(workDir, abs)
if err != nil {
// The path can't be made relative to the go.work file,
// so it must be kept absolute instead.
return abs, abs
}
// Normalize relative paths to use slashes, so that checked-in go.work
// files with relative paths within the repo are platform-independent.
return abs, filepath.ToSlash(rel)
}

View File

@ -27,7 +27,7 @@ workspace that does not specify modules to be used cannot be used to do
builds from local modules.
go.work files are line-oriented. Each line holds a single directive,
made up of a keyword followed by aruments. For example:
made up of a keyword followed by arguments. For example:
go 1.18

View File

@ -142,6 +142,8 @@ var extraEnvKeys = []string{
"SYSTEMROOT", // must be preserved on Windows to find DLLs; golang.org/issue/25210
"WINDIR", // must be preserved on Windows to be able to run PowerShell command; golang.org/issue/30711
"LD_LIBRARY_PATH", // must be preserved on Unix systems to find shared libraries
"LIBRARY_PATH", // allow override of non-standard static library paths
"C_INCLUDE_PATH", // allow override non-standard include paths
"CC", // don't lose user settings when invoking cgo
"GO_TESTING_GOTOOLS", // for gccgo testing
"GCCGO", // for gccgo testing
@ -648,9 +650,9 @@ func (ts *testScript) doCmdCmp(want simpleStatus, args []string, env, quiet bool
}
case successOrFailure:
if eq {
fmt.Fprintf(&ts.log, "%s and %s do not differ", name1, name2)
fmt.Fprintf(&ts.log, "%s and %s do not differ\n", name1, name2)
} else {
fmt.Fprintf(&ts.log, "%s and %s differ", name1, name2)
fmt.Fprintf(&ts.log, "%s and %s differ\n", name1, name2)
}
default:
ts.fatalf("unsupported: %v cmp", want)
@ -902,7 +904,7 @@ func (ts *testScript) cmdStale(want simpleStatus, args []string) {
tmpl := "{{if .Error}}{{.ImportPath}}: {{.Error.Err}}{{else}}"
switch want {
case failure:
tmpl += "{{if .Stale}}{{.ImportPath}} is unexpectedly stale{{end}}"
tmpl += "{{if .Stale}}{{.ImportPath}} is unexpectedly stale: {{.StaleReason}}{{end}}"
case success:
tmpl += "{{if not .Stale}}{{.ImportPath}} is unexpectedly NOT stale{{end}}"
default:

View File

@ -10,8 +10,10 @@ stderr 'internal'
# Test internal packages outside GOROOT are respected
cd ../testinternal2
env GO111MODULE=off
! go build -v .
stderr 'p\.go:3:8: use of internal package .*internal/w not allowed'
env GO111MODULE=''
[gccgo] skip # gccgo does not have GOROOT
cd ../testinternal

View File

@ -15,12 +15,13 @@ cp empty $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.partial
go mod verify
# 'go list' should not load packages from the directory.
# NOTE: the message "directory $dir outside available modules" is reported
# for directories not in the main module, active modules in the module cache,
# or local replacements. In this case, the directory is in the right place,
# but it's incomplete, so 'go list' acts as if it's not an active module.
# NOTE: the message "directory $dir outside main module or its selected dependencies"
# is reported for directories not in the main module, active modules in the
# module cache, or local replacements. In this case, the directory is in the
# right place, but it's incomplete, so 'go list' acts as if it's not an
# active module.
! go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.2
stderr 'outside available modules'
stderr 'outside main module or its selected dependencies'
# 'go list -m' should not print the directory.
go list -m -f '{{.Dir}}' rsc.io/quote

View File

@ -51,11 +51,11 @@ stdout '^at$'
# a package path.
cd ../badat/bad@
! go list .
stderr 'directory . outside available modules'
stderr 'directory . outside main module or its selected dependencies'
! go list $PWD
stderr 'directory . outside available modules'
stderr 'directory . outside main module or its selected dependencies'
! go list $PWD/...
stderr 'directory . outside available modules'
stderr 'directory . outside main module or its selected dependencies'
-- x/go.mod --
module m

View File

@ -194,10 +194,10 @@ cp go.mod.orig go.mod
go mod edit -require github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d+incompatible
cd outside
! go list -m github.com/pierrec/lz4
stderr 'go: example.com@v0.0.0 requires\n\tgithub.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
stderr '^go: example.com@v0.0.0 requires\n\tgithub.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
cd ..
! go list -m github.com/pierrec/lz4
stderr 'github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
stderr '^go: github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
# A +incompatible pseudo-version is valid for a revision of the module
# that lacks a go.mod file.
@ -222,7 +222,7 @@ stdout 'github.com/pierrec/lz4 v2.0.5\+incompatible'
# not resolve to a pseudo-version with a different major version.
cp go.mod.orig go.mod
! go get github.com/pierrec/lz4@v2.0.8
stderr 'go: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2'
stderr 'go: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
# An invalid +incompatible suffix for a canonical version should error out,
# not resolve to a pseudo-version.
@ -233,10 +233,10 @@ cp go.mod.orig go.mod
go mod edit -require github.com/pierrec/lz4@v2.0.8+incompatible
cd outside
! go list -m github.com/pierrec/lz4
stderr 'github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
stderr '^go: github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
cd ..
! go list -m github.com/pierrec/lz4
stderr 'github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
stderr '^go: github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
-- go.mod.orig --
module example.com

View File

@ -24,7 +24,7 @@ go get rsc.io/sampler@v1.3.1
go list -f '{{.ImportPath}}' $GOPATH/pkg/mod/rsc.io/sampler@v1.3.1
stdout '^rsc.io/sampler$'
! go list -f '{{.ImportPath}}' $GOPATH/pkg/mod/rsc.io/sampler@v1.3.0
stderr 'outside available modules'
stderr 'outside main module or its selected dependencies'
-- go.mod --
module x

View File

@ -9,7 +9,7 @@ go get
go mod download rsc.io/quote@v1.5.2
! go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.2
stderr '^directory ..[/\\]pkg[/\\]mod[/\\]rsc.io[/\\]quote@v1.5.2 outside available modules$'
stderr '^directory ..[/\\]pkg[/\\]mod[/\\]rsc.io[/\\]quote@v1.5.2 outside main module or its selected dependencies$'
go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.1
stdout 'rsc.io/quote'

View File

@ -0,0 +1,54 @@
# Regression test for https://go.dev/issue/51125:
# Relative import paths (a holdover from GOPATH) were accidentally allowed in module mode.
cd $WORK
# Relative imports should not be allowed with a go.mod file.
! go run driver.go
stderr '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$'
go list -e -f '{{with .Error}}{{.}}{{end}}' -deps driver.go
stdout '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$'
! stderr .
# Relative imports should not be allowed in module mode even without a go.mod file.
rm go.mod
! go run driver.go
stderr '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$'
go list -e -f '{{with .Error}}{{.}}{{end}}' -deps driver.go
stdout '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$'
! stderr .
# In GOPATH mode, they're still allowed (but only outside of GOPATH/src).
env GO111MODULE=off
[!short] go run driver.go
go list -deps driver.go
-- $WORK/go.mod --
module example
go 1.17
-- $WORK/driver.go --
package main
import "./mypkg"
func main() {
mypkg.MyFunc()
}
-- $WORK/mypkg/code.go --
package mypkg
import "fmt"
func MyFunc() {
fmt.Println("Hello, world!")
}

View File

@ -0,0 +1,52 @@
[!fuzz] skip
[short] skip
# This test checks that cached corpus loading properly handles duplicate entries (this can
# happen when a f.Add value has a duplicate entry in the cached corpus.) Duplicate entries
# should be discarded, and the rest of the cache should be loaded as normal.
env GOCACHE=$WORK/cache
env GODEBUG=fuzzdebug=1
mkdir -p $GOCACHE/fuzz/fuzztest/FuzzTarget
go run ./populate $GOCACHE/fuzz/fuzztest/FuzzTarget
go test -fuzz=FuzzTarget -fuzztime=10x .
stdout 'entries: 5'
-- go.mod --
module fuzztest
go 1.17
-- fuzz_test.go --
package fuzz
import "testing"
func FuzzTarget(f *testing.F) {
f.Add(int(0))
f.Fuzz(func(t *testing.T, _ int) {})
}
-- populate/main.go --
package main
import (
"path/filepath"
"fmt"
"os"
)
func main() {
for i := 0; i < 10; i++ {
b := byte(0)
if i > 5 {
b = byte(i)
}
tmpl := "go test fuzz v1\nint(%d)\n"
if err := os.WriteFile(filepath.Join(os.Args[1], fmt.Sprint(i)), []byte(fmt.Sprintf(tmpl, b)), 0777); err != nil {
panic(err)
}
}
}

View File

@ -0,0 +1,19 @@
[short] skip
! go test .
stdout '^panic: testing: fuzz target must not return a value \[recovered\]$'
-- go.mod --
module test
go 1.18
-- x_test.go --
package test
import "testing"
func FuzzReturnErr(f *testing.F) {
f.Add("hello, validation!")
f.Fuzz(func(t *testing.T, in string) string {
return in
})
}

View File

@ -1,5 +1,7 @@
# Relative imports in command line package
env GO111MODULE=off
# Run tests outside GOPATH.
env GO111MODULE=off
env GOPATH=$WORK/tmp

View File

@ -32,7 +32,9 @@ stdout 'example.com/b'
go list -mod=readonly all
! go list -mod=mod all
stderr '^go: -mod may only be set to readonly when in workspace mode'
go list -mod=mod -workfile=off all
env GOWORK=off
go list -mod=mod all
env GOWORK=
# Test that duplicates in the use list return an error
cp go.work go.work.backup
@ -53,7 +55,9 @@ go run example.com/d
# This exercises the code that determines which module command-line-arguments
# belongs to.
go list ./b/main.go
go build -n -workfile=off -o foo foo.go
env GOWORK=off
go build -n -o foo foo.go
env GOWORK=
go build -n -o foo foo.go
-- go.work.dup --

View File

@ -30,7 +30,8 @@ cmp stdout go.work.want_print
go work edit -json -go 1.19 -use b -dropuse c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
cmp stdout go.work.want_json
go work edit -print -fmt -workfile $GOPATH/src/unformatted
env GOWORK=$GOPATH/src/unformatted
go work edit -print -fmt
cmp stdout formatted
-- m/go.mod --

View File

@ -13,6 +13,10 @@ cd src
go env GOWORK
stdout 'go.work'
env GOWORK='off'
go env GOWORK
stdout 'off'
! go env -w GOWORK=off
stderr '^go: GOWORK cannot be modified$'

View File

@ -0,0 +1,24 @@
env GOWORK=stop.work
! go list a # require absolute path
! stderr panic
env GOWORK=doesnotexist
! go list a
! stderr panic
env GOWORK=$GOPATH/src/stop.work
go list -n a
go build -n a
go test -n a
-- stop.work --
go 1.18
use ./a
-- a/a.go --
package a
-- a/a_test.go --
package a
-- a/go.mod --
module a
go 1.18

View File

@ -0,0 +1,19 @@
# Test that the GOWORK environment variable flag is used by go work init.
! exists go.work
go work init
exists go.work
env GOWORK=$GOPATH/src/foo/foo.work
! exists foo/foo.work
go work init
exists foo/foo.work
env GOWORK=
cd foo/bar
! go work init
stderr 'already exists'
# Create directories to make go.work files in.
-- foo/dummy.txt --
-- foo/bar/dummy.txt --

View File

@ -0,0 +1,57 @@
go work sync
go list -f '{{.Dir}}' example.com/test
stdout '^'$PWD${/}test'$'
-- go.work --
go 1.18
use (
./test2
./test2/sub
)
-- test/go.mod --
module example.com/test
go 1.18
-- test/file.go --
package test
func DoSomething() {
}
-- test2/go.mod --
module example.com/test2
go 1.18
replace example.com/test => ../test
require example.com/test v0.0.0-00010101000000-000000000000
-- test2/file.go --
package test2
import (
"example.com/test"
)
func DoSomething() {
test.DoSomething()
}
-- test2/sub/go.mod --
module example.com/test2/sub
go 1.18
replace example.com/test => ../../test
require example.com/test v0.0.0
-- test2/sub/file.go --
package test2
import (
"example.com/test"
)
func DoSomething() {
test.DoSomething()
}

View File

@ -0,0 +1,25 @@
# This is a regression test for issue #49632.
# The Go command should mention go.work if the user
# tries to load a local package that's in a module
# that's not in go.work and can't be resolved.
! go list ./...
stderr 'pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies'
! go list ./a
stderr 'directory a outside modules listed in go.work'
-- go.work --
go 1.18
use ./b
-- a/go.mod --
module example.com/a
go 1.18
-- a/a.go --
package a
-- b/go.mod --
module example.com/b
go 1.18

View File

@ -0,0 +1,20 @@
! go work use
stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$'
! go work use .
stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$'
! go work edit
stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$'
! go work edit -go=1.18
stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$'
! go work sync
stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$'
-- go.mod --
module example
go 1.18
-- README.txt --
There is no go.work file here.

View File

@ -2,7 +2,7 @@
# overriding it in the go.work file.
! go list -m example.com/dep
stderr 'go: conflicting replacements for example.com/dep@v1.0.0:\n\t./dep1\n\t./dep2\nuse "go work edit -replace example.com/dep@v1.0.0=\[override\]" to resolve'
stderr 'go: conflicting replacements for example.com/dep@v1.0.0:\n\t'$PWD${/}'dep1\n\t'$PWD${/}'dep2\nuse "go work edit -replace example.com/dep@v1.0.0=\[override\]" to resolve'
go work edit -replace example.com/dep@v1.0.0=./dep1
go list -m example.com/dep
stdout 'example.com/dep v1.0.0 => ./dep1'
@ -15,7 +15,7 @@ use n
module example.com/m
require example.com/dep v1.0.0
replace example.com/dep v1.0.0 => ./dep1
replace example.com/dep v1.0.0 => ../dep1
-- m/m.go --
package m
@ -28,7 +28,7 @@ func F() {
module example.com/n
require example.com/dep v1.0.0
replace example.com/dep v1.0.0 => ./dep2
replace example.com/dep v1.0.0 => ../dep2
-- n/n.go --
package n

View File

@ -0,0 +1,22 @@
go work use -r .
cmp go.work go.work.want
-- go.work --
go 1.18
use (
.
sub
sub/dir/deleted
)
-- go.work.want --
go 1.18
use sub/dir
-- sub/README.txt --
A go.mod file has been deleted from this directory.
In addition, the entire subdirectory sub/dir/deleted
has been deleted, along with sub/dir/deleted/go.mod.
-- sub/dir/go.mod --
module example/sub/dir
go 1.18

View File

@ -0,0 +1,49 @@
cp go.work go.work.orig
# If the current directory contains a go.mod file,
# 'go work use .' should add an entry for it.
cd bar/baz
go work use .
cmp ../../go.work ../../go.work.rel
# If the current directory lacks a go.mod file, 'go work use .'
# should remove its entry.
mv go.mod go.mod.bak
go work use .
cmp ../../go.work ../../go.work.orig
# If the path is absolute, it should remain absolute.
mv go.mod.bak go.mod
go work use $PWD
grep -count=1 '^use ' ../../go.work
grep '^use ["]?'$PWD'["]?$' ../../go.work
# An absolute path should replace an entry for the corresponding relative path
# and vice-versa.
go work use .
cmp ../../go.work ../../go.work.rel
go work use $PWD
grep -count=1 '^use ' ../../go.work
grep '^use ["]?'$PWD'["]?$' ../../go.work
# If both the absolute and relative paths are named, 'go work use' should error
# out: we don't know which one to use, and shouldn't add both because the
# resulting workspace would contain a duplicate module.
cp ../../go.work.orig ../../go.work
! go work use $PWD .
stderr '^go: already added "bar/baz" as "'$PWD'"$'
cmp ../../go.work ../../go.work.orig
-- go.mod --
module example
go 1.18
-- go.work --
go 1.18
-- go.work.rel --
go 1.18
use bar/baz
-- bar/baz/go.mod --
module example/bar/baz
go 1.18

View File

@ -0,0 +1,17 @@
go work use -r .
cmp go.work go.work.want
-- go.mod --
module example
go 1.18
-- go.work --
go 1.18
use sub
-- go.work.want --
go 1.18
use .
-- sub/README.txt --
This directory no longer contains a go.mod file.

View File

@ -0,0 +1,19 @@
! go vet ./a
stderr 'fmt.Println call has possible formatting directive'
-- go.work --
go 1.18
use ./a
-- a/go.mod --
module example.com/a
go 1.18
-- a/a.go --
package a
import "fmt"
func A() {
fmt.Println("%s")
}

View File

@ -1,21 +0,0 @@
! go list -workfile=stop.work a # require absolute path
! stderr panic
! go list -workfile=doesnotexist a
! stderr panic
go list -n -workfile=$GOPATH/src/stop.work a
go build -n -workfile=$GOPATH/src/stop.work a
go test -n -workfile=$GOPATH/src/stop.work a
-- stop.work --
go 1.18
use ./a
-- a/a.go --
package a
-- a/a_test.go --
package a
-- a/go.mod --
module a
go 1.18

View File

@ -52,6 +52,16 @@ const (
printerNormalizeNumbers = 1 << 30
)
// fdSem guards the number of concurrently-open file descriptors.
//
// For now, this is arbitrarily set to 200, based on the observation that many
// platforms default to a kernel limit of 256. Ideally, perhaps we should derive
// it from rlimit on platforms that support that system call.
//
// File descriptors opened from outside of this package are not tracked,
// so this limit may be approximate.
var fdSem = make(chan bool, 200)
var (
rewrite func(*token.FileSet, *ast.File) *ast.File
parserMode parser.Mode
@ -213,51 +223,9 @@ func (r *reporter) ExitCode() int {
// If info == nil, we are formatting stdin instead of a file.
// If in == nil, the source is the contents of the file with the given filename.
func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) error {
if in == nil {
var err error
in, err = os.Open(filename)
if err != nil {
return err
}
}
// Compute the file's size and read its contents with minimal allocations.
//
// If the size is unknown (or bogus, or overflows an int), fall back to
// a size-independent ReadAll.
var src []byte
size := -1
if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() {
size = int(info.Size())
}
if size+1 > 0 {
// If we have the FileInfo from filepath.WalkDir, use it to make
// a buffer of the right size and avoid ReadAll's reallocations.
//
// We try to read size+1 bytes so that we can detect modifications: if we
// read more than size bytes, then the file was modified concurrently.
// (If that happens, we could, say, append to src to finish the read, or
// proceed with a truncated buffer — but the fact that it changed at all
// indicates a possible race with someone editing the file, so we prefer to
// stop to avoid corrupting it.)
src = make([]byte, size+1)
n, err := io.ReadFull(in, src)
if err != nil && err != io.ErrUnexpectedEOF {
return err
}
if n < size {
return fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n)
} else if n > size {
return fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src))
}
src = src[:n]
} else {
// The file is not known to be regular, so we don't have a reliable size for it.
var err error
src, err = io.ReadAll(in)
if err != nil {
return err
}
src, err := readFile(filename, info, in)
if err != nil {
return err
}
fileSet := token.NewFileSet()
@ -306,7 +274,9 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e
if err != nil {
return err
}
fdSem <- true
err = os.WriteFile(filename, res, perm)
<-fdSem
if err != nil {
os.Rename(bakname, filename)
return err
@ -333,6 +303,65 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e
return err
}
// readFile reads the contents of filename, described by info.
// If in is non-nil, readFile reads directly from it.
// Otherwise, readFile opens and reads the file itself,
// with the number of concurrently-open files limited by fdSem.
func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) {
if in == nil {
fdSem <- true
var err error
f, err := os.Open(filename)
if err != nil {
return nil, err
}
in = f
defer func() {
f.Close()
<-fdSem
}()
}
// Compute the file's size and read its contents with minimal allocations.
//
// If we have the FileInfo from filepath.WalkDir, use it to make
// a buffer of the right size and avoid ReadAll's reallocations.
//
// If the size is unknown (or bogus, or overflows an int), fall back to
// a size-independent ReadAll.
size := -1
if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() {
size = int(info.Size())
}
if size+1 <= 0 {
// The file is not known to be regular, so we don't have a reliable size for it.
var err error
src, err := io.ReadAll(in)
if err != nil {
return nil, err
}
return src, nil
}
// We try to read size+1 bytes so that we can detect modifications: if we
// read more than size bytes, then the file was modified concurrently.
// (If that happens, we could, say, append to src to finish the read, or
// proceed with a truncated buffer — but the fact that it changed at all
// indicates a possible race with someone editing the file, so we prefer to
// stop to avoid corrupting it.)
src := make([]byte, size+1)
n, err := io.ReadFull(in, src)
if err != nil && err != io.ErrUnexpectedEOF {
return nil, err
}
if n < size {
return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n)
} else if n > size {
return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src))
}
return src[:n], nil
}
func main() {
// Arbitrarily limit in-flight work to 2MiB times the number of threads.
//
@ -354,12 +383,16 @@ func gofmtMain(s *sequencer) {
flag.Parse()
if *cpuprofile != "" {
fdSem <- true
f, err := os.Create(*cpuprofile)
if err != nil {
s.AddReport(fmt.Errorf("creating cpu profile: %s", err))
return
}
defer f.Close()
defer func() {
f.Close()
<-fdSem
}()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
@ -474,6 +507,9 @@ const chmodSupported = runtime.GOOS != "windows"
// with <number randomly chosen such that the file name is unique. backupFile returns
// the chosen file name.
func backupFile(filename string, data []byte, perm fs.FileMode) (string, error) {
fdSem <- true
defer func() { <-fdSem }()
// create backup file
f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename))
if err != nil {

View File

@ -23,6 +23,7 @@ const (
FUNCDATA_OpenCodedDeferInfo = 4
FUNCDATA_ArgInfo = 5
FUNCDATA_ArgLiveInfo = 6
FUNCDATA_WrapInfo = 7
// ArgsSizeUnknown is set in Func.argsize to mark all functions
// whose argument size is unknown (C vararg functions, and

View File

@ -3,28 +3,21 @@
// license that can be found in the LICENSE file.
// Package ecdsa implements the Elliptic Curve Digital Signature Algorithm, as
// defined in FIPS 186-3.
// defined in FIPS 186-4 and SEC 1, Version 2.0.
//
// This implementation derives the nonce from an AES-CTR CSPRNG keyed by:
//
// SHA2-512(priv.D || entropy || hash)[:32]
//
// The CSPRNG key is indifferentiable from a random oracle as shown in
// [Coron], the AES-CTR stream is indifferentiable from a random oracle
// under standard cryptographic assumptions (see [Larsson] for examples).
//
// References:
// [Coron]
// https://cs.nyu.edu/~dodis/ps/merkle.pdf
// [Larsson]
// https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf
// Signatures generated by this package are not deterministic, but entropy is
// mixed with the private key and the message, achieving the same level of
// security in case of randomness source failure.
package ecdsa
// Further references:
// [NSA]: Suite B implementer's guide to FIPS 186-3
// https://apps.nsa.gov/iaarchive/library/ia-guidance/ia-solutions-for-classified/algorithm-guidance/suite-b-implementers-guide-to-fips-186-3-ecdsa.cfm
// [SECG]: SECG, SEC1
// http://www.secg.org/sec1-v2.pdf
// [FIPS 186-4] references ANSI X9.62-2005 for the bulk of the ECDSA algorithm.
// That standard is not freely available, which is a problem in an open source
// implementation, because not only the implementer, but also any maintainer,
// contributor, reviewer, auditor, and learner needs access to it. Instead, this
// package references and follows the equivalent [SEC 1, Version 2.0].
//
// [FIPS 186-4]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
// [SEC 1, Version 2.0]: https://www.secg.org/sec1-v2.pdf
import (
"crypto"
@ -41,15 +34,16 @@ import (
"golang.org/x/crypto/cryptobyte/asn1"
)
// A invertible implements fast inverse mod Curve.Params().N
// A invertible implements fast inverse in GF(N).
type invertible interface {
// Inverse returns the inverse of k in GF(P)
// Inverse returns the inverse of k mod Params().N.
Inverse(k *big.Int) *big.Int
}
// combinedMult implements fast multiplication S1*g + S2*p (g - generator, p - arbitrary point)
// A combinedMult implements fast combined multiplication for verification.
type combinedMult interface {
CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []byte) (x, y *big.Int)
// CombinedMult returns [s1]G + [s2]P where G is the generator.
CombinedMult(Px, Py *big.Int, s1, s2 []byte) (x, y *big.Int)
}
const (
@ -111,7 +105,7 @@ func (priv *PrivateKey) Equal(x crypto.PrivateKey) bool {
//
// This method implements crypto.Signer, which is an interface to support keys
// where the private part is kept in, for example, a hardware module. Common
// uses should use the Sign function in this package directly.
// uses can use the SignASN1 function in this package directly.
func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
r, s, err := Sign(rand, priv, digest)
if err != nil {
@ -128,11 +122,13 @@ func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOp
var one = new(big.Int).SetInt64(1)
// randFieldElement returns a random element of the field underlying the given
// curve using the procedure given in [NSA] A.2.1.
// randFieldElement returns a random element of the order of the given
// curve using the procedure given in FIPS 186-4, Appendix B.5.1.
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
params := c.Params()
b := make([]byte, params.BitSize/8+8)
// Note that for P-521 this will actually be 63 bits more than the order, as
// division rounds down, but the extra bit is inconsequential.
b := make([]byte, params.BitSize/8+8) // TODO: use params.N.BitLen()
_, err = io.ReadFull(rand, b)
if err != nil {
return
@ -159,12 +155,9 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
return priv, nil
}
// hashToInt converts a hash value to an integer. There is some disagreement
// about how this is done. [NSA] suggests that this is done in the obvious
// manner, but [SECG] truncates the hash to the bit-length of the curve order
// first. We follow [SECG] because that's what OpenSSL does. Additionally,
// OpenSSL right shifts excess bits from the number if the hash is too large
// and we mirror that too.
// hashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4,
// we use the left-most bits of the hash to match the bit-length of the order of
// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3.
func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
orderBits := c.Params().N.BitLen()
orderBytes := (orderBits + 7) / 8
@ -180,10 +173,11 @@ func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
return ret
}
// fermatInverse calculates the inverse of k in GF(P) using Fermat's method.
// This has better constant-time properties than Euclid's method (implemented
// in math/big.Int.ModInverse) although math/big itself isn't strictly
// constant-time so it's not perfect.
// fermatInverse calculates the inverse of k in GF(P) using Fermat's method
// (exponentiation modulo P - 2, per Euler's theorem). This has better
// constant-time properties than Euclid's method (implemented in
// math/big.Int.ModInverse and FIPS 186-4, Appendix C.1) although math/big
// itself isn't strictly constant-time so it's not perfect.
func fermatInverse(k, N *big.Int) *big.Int {
two := big.NewInt(2)
nMinus2 := new(big.Int).Sub(N, two)
@ -195,11 +189,22 @@ var errZeroParam = errors.New("zero parameter")
// Sign signs a hash (which should be the result of hashing a larger message)
// using the private key, priv. If the hash is longer than the bit-length of the
// private key's curve order, the hash will be truncated to that length. It
// returns the signature as a pair of integers. The security of the private key
// depends on the entropy of rand.
// returns the signature as a pair of integers. Most applications should use
// SignASN1 instead of dealing directly with r, s.
func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
randutil.MaybeReadByte(rand)
// This implementation derives the nonce from an AES-CTR CSPRNG keyed by:
//
// SHA2-512(priv.D || entropy || hash)[:32]
//
// The CSPRNG key is indifferentiable from a random oracle as shown in
// [Coron], the AES-CTR stream is indifferentiable from a random oracle
// under standard cryptographic assumptions (see [Larsson] for examples).
//
// [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf
// [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf
// Get 256 bits of entropy from rand.
entropy := make([]byte, 32)
_, err = io.ReadFull(rand, entropy)
@ -207,7 +212,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
return
}
// Initialize an SHA-512 hash context; digest ...
// Initialize an SHA-512 hash context; digest...
md := sha512.New()
md.Write(priv.D.Bytes()) // the private key,
md.Write(entropy) // the entropy,
@ -228,12 +233,12 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
S: cipher.NewCTR(block, []byte(aesIV)),
}
// See [NSA] 3.4.1
c := priv.PublicKey.Curve
return sign(priv, &csprng, c, hash)
}
func signGeneric(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) {
// SEC 1, Version 2.0, Section 4.1.3
N := c.Params().N
if N.Sign() == 0 {
return nil, nil, errZeroParam
@ -276,16 +281,15 @@ func signGeneric(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve
// SignASN1 signs a hash (which should be the result of hashing a larger message)
// using the private key, priv. If the hash is longer than the bit-length of the
// private key's curve order, the hash will be truncated to that length. It
// returns the ASN.1 encoded signature. The security of the private key
// depends on the entropy of rand.
// returns the ASN.1 encoded signature.
func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
return priv.Sign(rand, hash, nil)
}
// Verify verifies the signature in r, s of hash using the public key, pub. Its
// return value records whether the signature is valid.
// return value records whether the signature is valid. Most applications should
// use VerifyASN1 instead of dealing directly with r, s.
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
// See [NSA] 3.4.2
c := pub.Curve
N := c.Params().N
@ -299,6 +303,7 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
}
func verifyGeneric(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool {
// SEC 1, Version 2.0, Section 4.1.4
e := hashToInt(hash, c)
var w *big.Int
N := c.Params().N

View File

@ -2,17 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package elliptic implements several standard elliptic curves over prime
// fields.
// Package elliptic implements the standard NIST P-224, P-256, P-384, and P-521
// elliptic curves over prime fields.
package elliptic
// This package operates, internally, on Jacobian coordinates. For a given
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
// calculation can be performed within the transform (as in ScalarMult and
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
// reverse the transform than to operate in affine coordinates.
import (
"io"
"math/big"
@ -21,12 +14,12 @@ import (
// A Curve represents a short-form Weierstrass curve with a=-3.
//
// The output of Add, Double, and ScalarMult when the input is not a point on
// The behavior of Add, Double, and ScalarMult when the input is not a point on
// the curve is undefined.
//
// Note that the conventional point at infinity (0, 0) is not considered on the
// curve, although it can be returned by Add, Double, ScalarMult, or
// ScalarBaseMult (but not Unmarshal or UnmarshalCompressed).
// ScalarBaseMult (but not the Unmarshal or UnmarshalCompressed functions).
type Curve interface {
// Params returns the parameters for the curve.
Params() *CurveParams
@ -67,6 +60,13 @@ func (curve *CurveParams) Params() *CurveParams {
return curve
}
// CurveParams operates, internally, on Jacobian coordinates. For a given
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
// calculation can be performed within the transform (as in ScalarMult and
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
// reverse the transform than to operate in affine coordinates.
// polynomial returns x³ - 3x + b.
func (curve *CurveParams) polynomial(x *big.Int) *big.Int {
x3 := new(big.Int).Mul(x, x)
@ -89,6 +89,11 @@ func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
return specific.IsOnCurve(x, y)
}
if x.Sign() < 0 || x.Cmp(curve.P) >= 0 ||
y.Sign() < 0 || y.Cmp(curve.P) >= 0 {
return false
}
// y² = x³ - 3x + b
y2 := new(big.Int).Mul(y, y)
y2.Mod(y2, curve.P)
@ -353,7 +358,8 @@ func GenerateKey(curve Curve, rand io.Reader) (priv []byte, x, y *big.Int, err e
}
// Marshal converts a point on the curve into the uncompressed form specified in
// section 4.3.6 of ANSI X9.62.
// SEC 1, Version 2.0, Section 2.3.3. If the point is not on the curve (or is
// the conventional point at infinity), the behavior is undefined.
func Marshal(curve Curve, x, y *big.Int) []byte {
byteLen := (curve.Params().BitSize + 7) / 8
@ -367,7 +373,8 @@ func Marshal(curve Curve, x, y *big.Int) []byte {
}
// MarshalCompressed converts a point on the curve into the compressed form
// specified in section 4.3.6 of ANSI X9.62.
// specified in SEC 1, Version 2.0, Section 2.3.3. If the point is not on the
// curve (or is the conventional point at infinity), the behavior is undefined.
func MarshalCompressed(curve Curve, x, y *big.Int) []byte {
byteLen := (curve.Params().BitSize + 7) / 8
compressed := make([]byte, 1+byteLen)
@ -376,9 +383,9 @@ func MarshalCompressed(curve Curve, x, y *big.Int) []byte {
return compressed
}
// Unmarshal converts a point, serialized by Marshal, into an x, y pair.
// It is an error if the point is not in uncompressed form or is not on the curve.
// On error, x = nil.
// Unmarshal converts a point, serialized by Marshal, into an x, y pair. It is
// an error if the point is not in uncompressed form, is not on the curve, or is
// the point at infinity. On error, x = nil.
func Unmarshal(curve Curve, data []byte) (x, y *big.Int) {
byteLen := (curve.Params().BitSize + 7) / 8
if len(data) != 1+2*byteLen {
@ -399,9 +406,9 @@ func Unmarshal(curve Curve, data []byte) (x, y *big.Int) {
return
}
// UnmarshalCompressed converts a point, serialized by MarshalCompressed, into an x, y pair.
// It is an error if the point is not in compressed form or is not on the curve.
// On error, x = nil.
// UnmarshalCompressed converts a point, serialized by MarshalCompressed, into
// an x, y pair. It is an error if the point is not in compressed form, is not
// on the curve, or is the point at infinity. On error, x = nil.
func UnmarshalCompressed(curve Curve, data []byte) (x, y *big.Int) {
byteLen := (curve.Params().BitSize + 7) / 8
if len(data) != 1+byteLen {

View File

@ -182,6 +182,61 @@ func testUnmarshalToLargeCoordinates(t *testing.T, curve Curve) {
}
}
// TestInvalidCoordinates tests big.Int values that are not valid field elements
// (negative or bigger than P). They are expected to return false from
// IsOnCurve, all other behavior is undefined.
func TestInvalidCoordinates(t *testing.T) {
testAllCurves(t, testInvalidCoordinates)
}
func testInvalidCoordinates(t *testing.T, curve Curve) {
checkIsOnCurveFalse := func(name string, x, y *big.Int) {
if curve.IsOnCurve(x, y) {
t.Errorf("IsOnCurve(%s) unexpectedly returned true", name)
}
}
p := curve.Params().P
_, x, y, _ := GenerateKey(curve, rand.Reader)
xx, yy := new(big.Int), new(big.Int)
// Check if the sign is getting dropped.
xx.Neg(x)
checkIsOnCurveFalse("-x, y", xx, y)
yy.Neg(y)
checkIsOnCurveFalse("x, -y", x, yy)
// Check if negative values are reduced modulo P.
xx.Sub(x, p)
checkIsOnCurveFalse("x-P, y", xx, y)
yy.Sub(y, p)
checkIsOnCurveFalse("x, y-P", x, yy)
// Check if positive values are reduced modulo P.
xx.Add(x, p)
checkIsOnCurveFalse("x+P, y", xx, y)
yy.Add(y, p)
checkIsOnCurveFalse("x, y+P", x, yy)
// Check if the overflow is dropped.
xx.Add(x, new(big.Int).Lsh(big.NewInt(1), 535))
checkIsOnCurveFalse("x+2⁵³⁵, y", xx, y)
yy.Add(y, new(big.Int).Lsh(big.NewInt(1), 535))
checkIsOnCurveFalse("x, y+2⁵³⁵", x, yy)
// Check if P is treated like zero (if possible).
// y^2 = x^3 - 3x + B
// y = mod_sqrt(x^3 - 3x + B)
// y = mod_sqrt(B) if x = 0
// If there is no modsqrt, there is no point with x = 0, can't test x = P.
if yy := new(big.Int).ModSqrt(curve.Params().B, p); yy != nil {
if !curve.IsOnCurve(big.NewInt(0), yy) {
t.Fatal("(0, mod_sqrt(B)) is not on the curve?")
}
checkIsOnCurveFalse("P, y", p, yy)
}
}
func TestMarshalCompressed(t *testing.T) {
t.Run("P-256/03", func(t *testing.T) {
data, _ := hex.DecodeString("031e3987d9f9ea9d7dd7155a56a86b2009e1e0ab332f962d10d8beb6406ab1ad79")

View File

@ -7,30 +7,13 @@
package main
import (
"bytes"
"crypto/elliptic"
"encoding/binary"
"fmt"
"go/format"
"log"
"os"
)
func main() {
buf := new(bytes.Buffer)
fmt.Fprint(buf, `
// Copyright 2021 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.
// Generated by gen_p256_table.go. DO NOT EDIT.
//go:build amd64 || arm64
package elliptic
`[1:])
// Generate precomputed p256 tables.
var pre [43][32 * 8]uint64
basePoint := []uint64{
@ -70,41 +53,21 @@ package elliptic
}
}
fmt.Fprint(buf, "const p256Precomputed = \"\" +\n\n")
var bin []byte
// Dump the precomputed tables, flattened, little-endian.
// These tables are used directly by assembly on little-endian platforms.
// Putting the data in a const string lets it be stored readonly.
// go:embedding the data into a string lets it be stored readonly.
for i := range &pre {
for j, v := range &pre[i] {
fmt.Fprintf(buf, "\"")
for _, v := range &pre[i] {
var u8 [8]byte
binary.LittleEndian.PutUint64(u8[:], v)
for _, b := range &u8 {
fmt.Fprintf(buf, "\\x%02x", b)
}
fmt.Fprintf(buf, "\"")
if i < len(pre)-1 || j < len(pre[i])-1 {
fmt.Fprint(buf, "+")
}
if j%8 == 7 {
fmt.Fprint(buf, "\n")
}
bin = append(bin, u8[:]...)
}
fmt.Fprint(buf, "\n")
}
src := buf.Bytes()
fmtsrc, fmterr := format.Source(src)
// If formatting failed, keep the original source for debugging.
if fmterr == nil {
src = fmtsrc
}
err := os.WriteFile("p256_asm_table.go", src, 0644)
err := os.WriteFile("p256_asm_table.bin", bin, 0644)
if err != nil {
log.Fatal(err)
}
if fmterr != nil {
log.Fatal(fmterr)
}
}

View File

@ -61,6 +61,9 @@ func p224PointFromAffine(x, y *big.Int) (p *nistec.P224Point, ok bool) {
if x.Sign() == 0 && y.Sign() == 0 {
return nistec.NewP224Point(), true
}
if x.Sign() < 0 || y.Sign() < 0 {
return nil, false
}
if x.BitLen() > 224 || y.BitLen() > 224 {
return nil, false
}

View File

@ -6,11 +6,11 @@
package elliptic
// This file contains a constant-time, 32-bit implementation of P256.
// P-256 is implemented by various different backends, including a generic
// 32-bit constant-time one in this file, which is used when assembly
// implementations are not available, or not appropriate for the hardware.
import (
"math/big"
)
import "math/big"
type p256Curve struct {
*CurveParams

View File

@ -15,11 +15,15 @@
package elliptic
import (
_ "embed"
"math/big"
)
//go:generate go run -tags=tablegen gen_p256_table.go
//go:embed p256_asm_table.bin
var p256Precomputed string
type (
p256Curve struct {
*CurveParams

File diff suppressed because it is too large Load Diff

View File

@ -66,6 +66,9 @@ func p384PointFromAffine(x, y *big.Int) (p *nistec.P384Point, ok bool) {
if x.Sign() == 0 && y.Sign() == 0 {
return nistec.NewP384Point(), true
}
if x.Sign() < 0 || y.Sign() < 0 {
return nil, false
}
if x.BitLen() > 384 || y.BitLen() > 384 {
return nil, false
}

View File

@ -71,6 +71,9 @@ func p521PointFromAffine(x, y *big.Int) (p *nistec.P521Point, ok bool) {
if x.Sign() == 0 && y.Sign() == 0 {
return nistec.NewP521Point(), true
}
if x.Sign() < 0 || y.Sign() < 0 {
return nil, false
}
if x.BitLen() > 521 || y.BitLen() > 521 {
return nil, false
}

View File

@ -51,9 +51,9 @@ func isPrintable(b byte) bool {
}
// parseASN1String parses the ASN.1 string types T61String, PrintableString,
// UTF8String, BMPString, and IA5String. This is mostly copied from the
// respective encoding/asn1.parse... methods, rather than just increasing
// the API surface of that package.
// UTF8String, BMPString, IA5String, and NumericString. This is mostly copied
// from the respective encoding/asn1.parse... methods, rather than just
// increasing the API surface of that package.
func parseASN1String(tag cryptobyte_asn1.Tag, value []byte) (string, error) {
switch tag {
case cryptobyte_asn1.T61String:
@ -93,6 +93,13 @@ func parseASN1String(tag cryptobyte_asn1.Tag, value []byte) (string, error) {
return "", errors.New("invalid IA5String")
}
return s, nil
case cryptobyte_asn1.Tag(asn1.TagNumericString):
for _, b := range value {
if !('0' <= b && b <= '9' || b == ' ') {
return "", errors.New("invalid NumericString")
}
}
return string(value), nil
}
return "", fmt.Errorf("unsupported string type: %v", tag)
}

View File

@ -0,0 +1,102 @@
// Copyright 2021 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 x509
import (
"encoding/asn1"
"testing"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
)
func TestParseASN1String(t *testing.T) {
tests := []struct {
name string
tag cryptobyte_asn1.Tag
value []byte
expected string
expectedErr string
}{
{
name: "T61String",
tag: cryptobyte_asn1.T61String,
value: []byte{80, 81, 82},
expected: string("PQR"),
},
{
name: "PrintableString",
tag: cryptobyte_asn1.PrintableString,
value: []byte{80, 81, 82},
expected: string("PQR"),
},
{
name: "PrintableString (invalid)",
tag: cryptobyte_asn1.PrintableString,
value: []byte{1, 2, 3},
expectedErr: "invalid PrintableString",
},
{
name: "UTF8String",
tag: cryptobyte_asn1.UTF8String,
value: []byte{80, 81, 82},
expected: string("PQR"),
},
{
name: "UTF8String (invalid)",
tag: cryptobyte_asn1.UTF8String,
value: []byte{255},
expectedErr: "invalid UTF-8 string",
},
{
name: "BMPString",
tag: cryptobyte_asn1.Tag(asn1.TagBMPString),
value: []byte{80, 81},
expected: string("偑"),
},
{
name: "BMPString (invalid length)",
tag: cryptobyte_asn1.Tag(asn1.TagBMPString),
value: []byte{255},
expectedErr: "invalid BMPString",
},
{
name: "IA5String",
tag: cryptobyte_asn1.IA5String,
value: []byte{80, 81},
expected: string("PQ"),
},
{
name: "IA5String (invalid)",
tag: cryptobyte_asn1.IA5String,
value: []byte{255},
expectedErr: "invalid IA5String",
},
{
name: "NumericString",
tag: cryptobyte_asn1.Tag(asn1.TagNumericString),
value: []byte{49, 50},
expected: string("12"),
},
{
name: "NumericString (invalid)",
tag: cryptobyte_asn1.Tag(asn1.TagNumericString),
value: []byte{80},
expectedErr: "invalid NumericString",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
out, err := parseASN1String(tc.tag, tc.value)
if err != nil && err.Error() != tc.expectedErr {
t.Fatalf("parseASN1String returned unexpected error: got %q, want %q", err, tc.expectedErr)
} else if err == nil && tc.expectedErr != "" {
t.Fatalf("parseASN1String didn't fail, expected: %s", tc.expectedErr)
}
if out != tc.expected {
t.Fatalf("parseASN1String returned unexpected value: got %q, want %q", out, tc.expected)
}
})
}
}

View File

@ -676,6 +676,9 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm
if c.waiter != nil {
c.waiter(ctx)
if err := ctx.Err(); err != nil {
return nil, err
}
}
if stmt.wait > 0 {

View File

@ -418,26 +418,31 @@ func TestQueryContextWait(t *testing.T) {
defer closeDB(t, db)
prepares0 := numPrepares(t, db)
// TODO(kardianos): convert this from using a timeout to using an explicit
// cancel when the query signals that it is "executing" the query.
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// This will trigger the *fakeConn.Prepare method which will take time
// performing the query. The ctxDriverPrepare func will check the context
// after this and close the rows and return an error.
_, err := db.QueryContext(ctx, "WAIT|1s|SELECT|people|age,name|")
if err != context.DeadlineExceeded {
c, err := db.Conn(ctx)
if err != nil {
t.Fatal(err)
}
c.dc.ci.(*fakeConn).waiter = func(c context.Context) {
cancel()
<-ctx.Done()
}
_, err = c.QueryContext(ctx, "SELECT|people|age,name|")
c.Close()
if err != context.Canceled {
t.Fatalf("expected QueryContext to error with context deadline exceeded but returned %v", err)
}
// Verify closed rows connection after error condition.
waitForFree(t, db, 1)
if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
// TODO(kardianos): if the context timeouts before the db.QueryContext
// executes this check may fail. After adjusting how the context
// is canceled above revert this back to a Fatal error.
t.Logf("executed %d Prepare statements; want 1", prepares)
t.Fatalf("executed %d Prepare statements; want 1", prepares)
}
}
@ -455,14 +460,14 @@ func TestTxContextWait(t *testing.T) {
}
tx.keepConnOnRollback = false
go func() {
time.Sleep(15 * time.Millisecond)
tx.dc.ci.(*fakeConn).waiter = func(c context.Context) {
cancel()
}()
<-ctx.Done()
}
// This will trigger the *fakeConn.Prepare method which will take time
// performing the query. The ctxDriverPrepare func will check the context
// after this and close the rows and return an error.
_, err = tx.QueryContext(ctx, "WAIT|1s|SELECT|people|age,name|")
_, err = tx.QueryContext(ctx, "SELECT|people|age,name|")
if err != context.Canceled {
t.Fatalf("expected QueryContext to error with context canceled but returned %v", err)
}

View File

@ -75,8 +75,8 @@ func Read(r io.ReaderAt) (*BuildInfo, error) {
if err != nil {
return nil, err
}
bi := &BuildInfo{}
if err := bi.UnmarshalText([]byte(mod)); err != nil {
bi, err := debug.ParseBuildInfo(mod)
if err != nil {
return nil, err
}
bi.GoVersion = vers

View File

@ -212,12 +212,10 @@ func TestReadFile(t *testing.T) {
} else {
if tc.wantErr != "" {
t.Fatalf("unexpected success; want error containing %q", tc.wantErr)
} else if got, err := info.MarshalText(); err != nil {
t.Fatalf("unexpected error marshaling BuildInfo: %v", err)
} else if got := cleanOutputForComparison(string(got)); got != tc.want {
if got != tc.want {
t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want)
}
}
got := info.String()
if clean := cleanOutputForComparison(string(got)); got != tc.want && clean != tc.want {
t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want)
}
}
})

View File

@ -88,6 +88,7 @@ var depsRules = `
< internal/itoa
< internal/unsafeheader
< runtime/internal/sys
< runtime/internal/syscall
< runtime/internal/atomic
< runtime/internal/math
< runtime
@ -418,7 +419,7 @@ var depsRules = `
CGO, fmt, net !< CRYPTO;
# CRYPTO-MATH is core bignum-based crypto - no cgo, net; fmt now ok.
CRYPTO, FMT, math/big
CRYPTO, FMT, math/big, embed
< crypto/rand
< crypto/internal/randutil
< crypto/ed25519

View File

@ -927,6 +927,7 @@ var predeclaredTypes = map[string]bool{
"any": true,
"bool": true,
"byte": true,
"comparable": true,
"complex64": true,
"complex128": true,
"error": true,

View File

@ -46,6 +46,9 @@ VARIABLES
FUNCTIONS
// Associated with comparable type if AllDecls is set.
func ComparableFactory() comparable
//
func F(x int) int

View File

@ -38,6 +38,12 @@ TYPES
//
func (x *T) M()
// Should only appear if AllDecls is set.
type comparable struct{} // overrides a predeclared type comparable
// Associated with comparable type if AllDecls is set.
func ComparableFactory() comparable
//
type notExported int

View File

@ -46,6 +46,9 @@ VARIABLES
FUNCTIONS
// Associated with comparable type if AllDecls is set.
func ComparableFactory() comparable
//
func F(x int) int

View File

@ -27,9 +27,15 @@ func UintFactory() uint {}
// Associated with uint type if AllDecls is set.
func uintFactory() uint {}
// Associated with comparable type if AllDecls is set.
func ComparableFactory() comparable {}
// Should only appear if AllDecls is set.
type uint struct{} // overrides a predeclared type uint
// Should only appear if AllDecls is set.
type comparable struct{} // overrides a predeclared type comparable
// ----------------------------------------------------------------------------
// Exported declarations associated with non-exported types must always be shown.

View File

@ -543,6 +543,13 @@ func (p *parser) parseArrayType(lbrack token.Pos, len ast.Expr) *ast.ArrayType {
}
p.exprLev--
}
if p.tok == token.COMMA {
// Trailing commas are accepted in type parameter
// lists but not in array type declarations.
// Accept for better error handling but complain.
p.error(p.pos, "unexpected comma; expecting ]")
p.next()
}
p.expect(token.RBRACK)
elt := p.parseType()
return &ast.ArrayType{Lbrack: lbrack, Len: len, Elt: elt}
@ -797,7 +804,7 @@ func (p *parser) parseParamDecl(name *ast.Ident, typeSetsOK bool) (f field) {
return
}
func (p *parser) parseParameterList(name0 *ast.Ident, closing token.Token) (params []*ast.Field) {
func (p *parser) parseParameterList(name0 *ast.Ident, typ0 ast.Expr, closing token.Token) (params []*ast.Field) {
if p.trace {
defer un(trace(p, "ParameterList"))
}
@ -816,8 +823,17 @@ func (p *parser) parseParameterList(name0 *ast.Ident, closing token.Token) (para
var named int // number of parameters that have an explicit name and type
for name0 != nil || p.tok != closing && p.tok != token.EOF {
par := p.parseParamDecl(name0, typeSetsOK)
var par field
if typ0 != nil {
if typeSetsOK {
typ0 = p.embeddedElem(typ0)
}
par = field{name0, typ0}
} else {
par = p.parseParamDecl(name0, typeSetsOK)
}
name0 = nil // 1st name was consumed if present
typ0 = nil // 1st typ was consumed if present
if par.name != nil || par.typ != nil {
list = append(list, par)
if par.name != nil && par.typ != nil {
@ -926,7 +942,7 @@ func (p *parser) parseParameters(acceptTParams bool) (tparams, params *ast.Field
opening := p.pos
p.next()
// [T any](params) syntax
list := p.parseParameterList(nil, token.RBRACK)
list := p.parseParameterList(nil, nil, token.RBRACK)
rbrack := p.expect(token.RBRACK)
tparams = &ast.FieldList{Opening: opening, List: list, Closing: rbrack}
// Type parameter lists must not be empty.
@ -940,7 +956,7 @@ func (p *parser) parseParameters(acceptTParams bool) (tparams, params *ast.Field
var fields []*ast.Field
if p.tok != token.RPAREN {
fields = p.parseParameterList(nil, token.RPAREN)
fields = p.parseParameterList(nil, nil, token.RPAREN)
}
rparen := p.expect(token.RPAREN)
@ -977,7 +993,7 @@ func (p *parser) parseFuncType() *ast.FuncType {
pos := p.expect(token.FUNC)
tparams, params := p.parseParameters(true)
if tparams != nil {
p.error(tparams.Pos(), "function type cannot have type parameters")
p.error(tparams.Pos(), "function type must have no type parameters")
}
results := p.parseResult()
@ -1004,18 +1020,21 @@ func (p *parser) parseMethodSpec() *ast.Field {
p.exprLev--
if name0, _ := x.(*ast.Ident); name0 != nil && p.tok != token.COMMA && p.tok != token.RBRACK {
// generic method m[T any]
list := p.parseParameterList(name0, token.RBRACK)
rbrack := p.expect(token.RBRACK)
tparams := &ast.FieldList{Opening: lbrack, List: list, Closing: rbrack}
//
// Interface methods do not have type parameters. We parse them for a
// better error message and improved error recovery.
_ = p.parseParameterList(name0, nil, token.RBRACK)
_ = p.expect(token.RBRACK)
p.error(lbrack, "interface method must have no type parameters")
// TODO(rfindley) refactor to share code with parseFuncType.
_, params := p.parseParameters(false)
results := p.parseResult()
idents = []*ast.Ident{ident}
typ = &ast.FuncType{
Func: token.NoPos,
TypeParams: tparams,
Params: params,
Results: results,
Func: token.NoPos,
Params: params,
Results: results,
}
} else {
// embedded instantiated type
@ -1781,7 +1800,12 @@ func (p *parser) tokPrec() (token.Token, int) {
return tok, tok.Precedence()
}
func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int) ast.Expr {
// parseBinaryExpr parses a (possibly) binary expression.
// If x is non-nil, it is used as the left operand.
// If check is true, operands are checked to be valid expressions.
//
// TODO(rfindley): parseBinaryExpr has become overloaded. Consider refactoring.
func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int, check bool) ast.Expr {
if p.trace {
defer un(trace(p, "BinaryExpr"))
}
@ -1795,11 +1819,32 @@ func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int) ast.Expr {
return x
}
pos := p.expect(op)
y := p.parseBinaryExpr(nil, oprec+1)
x = &ast.BinaryExpr{X: p.checkExpr(x), OpPos: pos, Op: op, Y: p.checkExpr(y)}
y := p.parseBinaryExpr(nil, oprec+1, check)
if check {
x = p.checkExpr(x)
y = p.checkExpr(y)
}
x = &ast.BinaryExpr{X: x, OpPos: pos, Op: op, Y: y}
}
}
// checkBinaryExpr checks binary expressions that were not already checked by
// parseBinaryExpr, because the latter was called with check=false.
func (p *parser) checkBinaryExpr(x ast.Expr) {
bx, ok := x.(*ast.BinaryExpr)
if !ok {
return
}
bx.X = p.checkExpr(bx.X)
bx.Y = p.checkExpr(bx.Y)
// parseBinaryExpr checks x and y for each binary expr in a tree, so we
// traverse the tree of binary exprs starting from x.
p.checkBinaryExpr(bx.X)
p.checkBinaryExpr(bx.Y)
}
// The result may be a type or even a raw type ([...]int). Callers must
// check the result (using checkExpr or checkExprOrType), depending on
// context.
@ -1808,7 +1853,7 @@ func (p *parser) parseExpr() ast.Expr {
defer un(trace(p, "Expression"))
}
return p.parseBinaryExpr(nil, token.LowestPrec+1)
return p.parseBinaryExpr(nil, token.LowestPrec+1, true)
}
func (p *parser) parseRhs() ast.Expr {
@ -2531,12 +2576,12 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, _ token.Pos, keyword toke
return spec
}
func (p *parser) parseGenericType(spec *ast.TypeSpec, openPos token.Pos, name0 *ast.Ident) {
func (p *parser) parseGenericType(spec *ast.TypeSpec, openPos token.Pos, name0 *ast.Ident, typ0 ast.Expr) {
if p.trace {
defer un(trace(p, "parseGenericType"))
}
list := p.parseParameterList(name0, token.RBRACK)
list := p.parseParameterList(name0, typ0, token.RBRACK)
closePos := p.expect(token.RBRACK)
spec.TypeParams = &ast.FieldList{Opening: openPos, List: list, Closing: closePos}
// Let the type checker decide whether to accept type parameters on aliases:
@ -2561,31 +2606,85 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token
lbrack := p.pos
p.next()
if p.tok == token.IDENT {
// array type or generic type: [name0...
name0 := p.parseIdent()
// We may have an array type or a type parameter list.
// In either case we expect an expression x (which may
// just be a name, or a more complex expression) which
// we can analyze further.
//
// A type parameter list may have a type bound starting
// with a "[" as in: P []E. In that case, simply parsing
// an expression would lead to an error: P[] is invalid.
// But since index or slice expressions are never constant
// and thus invalid array length expressions, if we see a
// "[" following a name it must be the start of an array
// or slice constraint. Only if we don't see a "[" do we
// need to parse a full expression.
// Index or slice expressions are never constant and thus invalid
// array length expressions. Thus, if we see a "[" following name
// we can safely assume that "[" name starts a type parameter list.
var x ast.Expr // x != nil means x is the array length expression
var x ast.Expr = p.parseIdent()
if p.tok != token.LBRACK {
// We may still have either an array type or generic type -- check if
// name0 is the entire expr.
// To parse the expression starting with name, expand
// the call sequence we would get by passing in name
// to parser.expr, and pass in name to parsePrimaryExpr.
p.exprLev++
lhs := p.parsePrimaryExpr(name0)
x = p.parseBinaryExpr(lhs, token.LowestPrec+1)
lhs := p.parsePrimaryExpr(x)
x = p.parseBinaryExpr(lhs, token.LowestPrec+1, false)
p.exprLev--
if x == name0 && p.tok != token.RBRACK {
x = nil
}
// analyze the cases
var pname *ast.Ident // pname != nil means pname is the type parameter name
var ptype ast.Expr // ptype != nil means ptype is the type parameter type; pname != nil in this case
switch t := x.(type) {
case *ast.Ident:
// Unless we see a "]", we are at the start of a type parameter list.
if p.tok != token.RBRACK {
// d.Name "[" name ...
pname = t
// no ptype
}
case *ast.BinaryExpr:
// If we have an expression of the form name*T, and T is a (possibly
// parenthesized) type literal or the next token is a comma, we are
// at the start of a type parameter list.
if name, _ := t.X.(*ast.Ident); name != nil {
if t.Op == token.MUL && (isTypeLit(t.Y) || p.tok == token.COMMA) {
// d.Name "[" name "*" t.Y
// d.Name "[" name "*" t.Y ","
// convert t into unary *t.Y
pname = name
ptype = &ast.StarExpr{Star: t.OpPos, X: t.Y}
}
}
if pname == nil {
// A normal binary expression. Since we passed check=false, we must
// now check its operands.
p.checkBinaryExpr(t)
}
case *ast.CallExpr:
// If we have an expression of the form name(T), and T is a (possibly
// parenthesized) type literal or the next token is a comma, we are
// at the start of a type parameter list.
if name, _ := t.Fun.(*ast.Ident); name != nil {
if len(t.Args) == 1 && !t.Ellipsis.IsValid() && (isTypeLit(t.Args[0]) || p.tok == token.COMMA) {
// d.Name "[" name "(" t.ArgList[0] ")"
// d.Name "[" name "(" t.ArgList[0] ")" ","
pname = name
ptype = t.Args[0]
}
}
}
if x == nil {
// generic type [T any];
p.parseGenericType(spec, lbrack, name0)
if pname != nil {
// d.Name "[" pname ...
// d.Name "[" pname ptype ...
// d.Name "[" pname ptype "," ...
p.parseGenericType(spec, lbrack, pname, ptype)
} else {
// array type
// TODO(rfindley) should resolve all identifiers in x.
// d.Name "[" x ...
spec.Type = p.parseArrayType(lbrack, x)
}
} else {
@ -2608,6 +2707,21 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token
return spec
}
// isTypeLit reports whether x is a (possibly parenthesized) type literal.
func isTypeLit(x ast.Expr) bool {
switch x := x.(type) {
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
return true
case *ast.StarExpr:
// *T may be a pointer dereferenciation.
// Only consider *T as type literal if T is a type literal.
return isTypeLit(x.X)
case *ast.ParenExpr:
return isTypeLit(x.X)
}
return false
}
func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl {
if p.trace {
defer un(trace(p, "GenDecl("+keyword.String()+")"))
@ -2655,6 +2769,12 @@ func (p *parser) parseFuncDecl() *ast.FuncDecl {
ident := p.parseIdent()
tparams, params := p.parseParameters(true)
if recv != nil && tparams != nil {
// Method declarations do not have type parameters. We parse them for a
// better error message and improved error recovery.
p.error(tparams.Opening, "method must have no type parameters")
tparams = nil
}
results := p.parseResult()
var body *ast.BlockStmt

View File

@ -8,6 +8,7 @@ import (
"fmt"
"go/ast"
"go/token"
"strings"
)
const debugResolve = false
@ -24,6 +25,7 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
declErr: declErr,
topScope: pkgScope,
pkgScope: pkgScope,
depth: 1,
}
for _, decl := range file.Decls {
@ -45,7 +47,7 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
i++
} else if debugResolve {
pos := ident.Obj.Decl.(interface{ Pos() token.Pos }).Pos()
r.dump("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos)
r.trace("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos)
}
}
file.Scope = r.pkgScope
@ -60,6 +62,7 @@ type resolver struct {
pkgScope *ast.Scope // pkgScope.Outer == nil
topScope *ast.Scope // top-most scope; may be pkgScope
unresolved []*ast.Ident // unresolved identifiers
depth int // scope depth
// Label scopes
// (maintained by open/close LabelScope)
@ -67,8 +70,8 @@ type resolver struct {
targetStack [][]*ast.Ident // stack of unresolved labels
}
func (r *resolver) dump(format string, args ...any) {
fmt.Println(">>> " + r.sprintf(format, args...))
func (r *resolver) trace(format string, args ...any) {
fmt.Println(strings.Repeat(". ", r.depth) + r.sprintf(format, args...))
}
func (r *resolver) sprintf(format string, args ...any) string {
@ -83,14 +86,16 @@ func (r *resolver) sprintf(format string, args ...any) string {
func (r *resolver) openScope(pos token.Pos) {
if debugResolve {
r.dump("opening scope @%v", pos)
r.trace("opening scope @%v", pos)
r.depth++
}
r.topScope = ast.NewScope(r.topScope)
}
func (r *resolver) closeScope() {
if debugResolve {
r.dump("closing scope")
r.depth--
r.trace("closing scope")
}
r.topScope = r.topScope.Outer
}
@ -117,21 +122,27 @@ func (r *resolver) closeLabelScope() {
func (r *resolver) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
for _, ident := range idents {
assert(ident.Obj == nil, "identifier already declared or resolved")
if ident.Obj != nil {
panic(fmt.Sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name))
}
obj := ast.NewObj(kind, ident.Name)
// remember the corresponding declaration for redeclaration
// errors and global variable resolution/typechecking phase
obj.Decl = decl
obj.Data = data
ident.Obj = obj
// Identifiers (for receiver type parameters) are written to the scope, but
// never set as the resolved object. See issue #50956.
if _, ok := decl.(*ast.Ident); !ok {
ident.Obj = obj
}
if ident.Name != "_" {
if debugResolve {
r.dump("declaring %s@%v", ident.Name, ident.Pos())
r.trace("declaring %s@%v", ident.Name, ident.Pos())
}
if alt := scope.Insert(obj); alt != nil && r.declErr != nil {
prevDecl := ""
if pos := alt.Pos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", r.handle.Position(pos))
prevDecl = r.sprintf("\n\tprevious declaration at %v", pos)
}
r.declErr(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl))
}
@ -153,7 +164,7 @@ func (r *resolver) shortVarDecl(decl *ast.AssignStmt) {
ident.Obj = obj
if ident.Name != "_" {
if debugResolve {
r.dump("declaring %s@%v", ident.Name, ident.Pos())
r.trace("declaring %s@%v", ident.Name, ident.Pos())
}
if alt := r.topScope.Insert(obj); alt != nil {
ident.Obj = alt // redeclaration
@ -180,7 +191,7 @@ var unresolved = new(ast.Object)
//
func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) {
if ident.Obj != nil {
panic(fmt.Sprintf("%s: identifier %s already declared or resolved", r.handle.Position(ident.Pos()), ident.Name))
panic(r.sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name))
}
// '_' should never refer to existing declarations, because it has special
// handling in the spec.
@ -189,8 +200,15 @@ func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) {
}
for s := r.topScope; s != nil; s = s.Outer {
if obj := s.Lookup(ident.Name); obj != nil {
if debugResolve {
r.trace("resolved %v:%s to %v", ident.Pos(), ident.Name, obj)
}
assert(obj.Name != "", "obj with no name")
ident.Obj = obj
// Identifiers (for receiver type parameters) are written to the scope,
// but never set as the resolved object. See issue #50956.
if _, ok := obj.Decl.(*ast.Ident); !ok {
ident.Obj = obj
}
return
}
}
@ -227,7 +245,7 @@ func (r *resolver) walkStmts(list []ast.Stmt) {
func (r *resolver) Visit(node ast.Node) ast.Visitor {
if debugResolve && node != nil {
r.dump("node %T@%v", node, node.Pos())
r.trace("node %T@%v", node, node.Pos())
}
switch n := node.(type) {
@ -461,8 +479,7 @@ func (r *resolver) Visit(node ast.Node) ast.Visitor {
r.openScope(n.Pos())
defer r.closeScope()
// Resolve the receiver first, without declaring.
r.resolveList(n.Recv)
r.walkRecv(n.Recv)
// Type parameters are walked normally: they can reference each other, and
// can be referenced by normal parameters.
@ -519,6 +536,52 @@ func (r *resolver) declareList(list *ast.FieldList, kind ast.ObjKind) {
}
}
func (r *resolver) walkRecv(recv *ast.FieldList) {
// If our receiver has receiver type parameters, we must declare them before
// trying to resolve the rest of the receiver, and avoid re-resolving the
// type parameter identifiers.
if recv == nil || len(recv.List) == 0 {
return // nothing to do
}
typ := recv.List[0].Type
if ptr, ok := typ.(*ast.StarExpr); ok {
typ = ptr.X
}
var declareExprs []ast.Expr // exprs to declare
var resolveExprs []ast.Expr // exprs to resolve
switch typ := typ.(type) {
case *ast.IndexExpr:
declareExprs = []ast.Expr{typ.Index}
resolveExprs = append(resolveExprs, typ.X)
case *ast.IndexListExpr:
declareExprs = typ.Indices
resolveExprs = append(resolveExprs, typ.X)
default:
resolveExprs = append(resolveExprs, typ)
}
for _, expr := range declareExprs {
if id, _ := expr.(*ast.Ident); id != nil {
r.declare(expr, nil, r.topScope, ast.Typ, id)
} else {
// The receiver type parameter expression is invalid, but try to resolve
// it anyway for consistency.
resolveExprs = append(resolveExprs, expr)
}
}
for _, expr := range resolveExprs {
if expr != nil {
ast.Walk(r, expr)
}
}
// The receiver is invalid, but try to resolve it anyway for consistency.
for _, f := range recv.List[1:] {
if f.Type != nil {
ast.Walk(r, f.Type)
}
}
}
func (r *resolver) walkFieldList(list *ast.FieldList, kind ast.ObjKind) {
if list == nil {
return

View File

@ -74,7 +74,7 @@ var validWithTParamsOnly = []string{
`package p; type T[P any /* ERROR "expected ']', found any" */ ] struct { P }`,
`package p; type T[P comparable /* ERROR "expected ']', found comparable" */ ] struct { P }`,
`package p; type T[P comparable /* ERROR "expected ']', found comparable" */ [P]] struct { P }`,
`package p; type T[P1, /* ERROR "expected ']', found ','" */ P2 any] struct { P1; f []P2 }`,
`package p; type T[P1, /* ERROR "unexpected comma" */ P2 any] struct { P1; f []P2 }`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ T any]()()`,
`package p; func _(T (P))`,
`package p; func f[ /* ERROR "expected '\(', found '\['" */ A, B any](); func _() { _ = f[int, int] }`,
@ -83,8 +83,8 @@ var validWithTParamsOnly = []string{
`package p; func _(p.T[ /* ERROR "missing ',' in parameter list" */ Q])`,
`package p; type _[A interface /* ERROR "expected ']', found 'interface'" */ {},] struct{}`,
`package p; type _[A interface /* ERROR "expected ']', found 'interface'" */ {}] struct{}`,
`package p; type _[A, /* ERROR "expected ']', found ','" */ B any,] struct{}`,
`package p; type _[A, /* ERROR "expected ']', found ','" */ B any] struct{}`,
`package p; type _[A, /* ERROR "unexpected comma" */ B any,] struct{}`,
`package p; type _[A, /* ERROR "unexpected comma" */ B any] struct{}`,
`package p; type _[A any /* ERROR "expected ']', found any" */,] struct{}`,
`package p; type _[A any /* ERROR "expected ']', found any" */ ]struct{}`,
`package p; type _[A any /* ERROR "expected ']', found any" */ ] struct{ A }`,
@ -94,11 +94,9 @@ var validWithTParamsOnly = []string{
`package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B any](a A) B`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B C](a A) B`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B C[A, B]](a A) B`,
`package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B any](a A) B`,
`package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B C](a A) B`,
`package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B C[A, B]](a A) B`,
`package p; type _[A, /* ERROR "expected ']', found ','" */ B any] interface { _(a A) B }`,
`package p; type _[A, /* ERROR "expected ']', found ','" */ B C[A, B]] interface { _(a A) B }`,
`package p; type _[A, /* ERROR "unexpected comma" */ B any] interface { _(a A) B }`,
`package p; type _[A, /* ERROR "unexpected comma" */ B C[A, B]] interface { _(a A) B }`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ T1, T2 interface{}](x T1) T2`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ T1 interface{ m() }, T2, T3 interface{}](x T1, y T3) T2`,
`package p; var _ = [ /* ERROR "expected expression" */ ]T[int]{}`,
@ -110,10 +108,10 @@ var validWithTParamsOnly = []string{
`package p; var _ T[ /* ERROR "expected ';', found '\['" */ chan int]`,
// TODO(rfindley) this error message could be improved.
`package p; func (_ /* ERROR "mixed named and unnamed parameters" */ R[P]) _[T any](x T)`,
`package p; func (_ /* ERROR "mixed named and unnamed parameters" */ R[ P, Q]) _[T1, T2 any](x T)`,
`package p; func (_ /* ERROR "mixed named and unnamed parameters" */ R[P]) _(x T)`,
`package p; func (_ /* ERROR "mixed named and unnamed parameters" */ R[ P, Q]) _(x T)`,
`package p; func (R[P] /* ERROR "missing element type" */ ) _[T any]()`,
`package p; func (R[P] /* ERROR "missing element type" */ ) _()`,
`package p; func _(T[P] /* ERROR "missing element type" */ )`,
`package p; func _(T[P1, /* ERROR "expected ']', found ','" */ P2, P3 ])`,
`package p; func _(T[P] /* ERROR "missing element type" */ ) T[P]`,
@ -122,7 +120,7 @@ var validWithTParamsOnly = []string{
`package p; type _ interface{int| /* ERROR "expected ';'" */ float32; bool; m(); string;}`,
`package p; type I1[T any /* ERROR "expected ']', found any" */ ] interface{}; type I2 interface{ I1[int] }`,
`package p; type I1[T any /* ERROR "expected ']', found any" */ ] interface{}; type I2[T any] interface{ I1[T] }`,
`package p; type _ interface { f[ /* ERROR "expected ';', found '\['" */ T any]() }`,
`package p; type _ interface { N[ /* ERROR "expected ';', found '\['" */ T] }`,
`package p; type T[P any /* ERROR "expected ']'" */ ] = T0`,
}
@ -195,7 +193,7 @@ var invalids = []string{
`package p; func f() { go func() { func() { f(x func /* ERROR "missing ','" */ (){}) } } }`,
`package p; func _() (type /* ERROR "found 'type'" */ T)(T)`,
`package p; func (type /* ERROR "found 'type'" */ T)(T) _()`,
`package p; type _[A+B, /* ERROR "expected ']'" */ ] int`,
`package p; type _[A+B, /* ERROR "unexpected comma" */ ] int`,
// TODO(rfindley): this error should be positioned on the ':'
`package p; var a = a[[]int:[ /* ERROR "expected expression" */ ]int];`,
@ -233,22 +231,34 @@ var invalidNoTParamErrs = []string{
`package p; type T[P any /* ERROR "expected ']', found any" */ ] = T0`,
`package p; var _ func[ /* ERROR "expected '\(', found '\['" */ T any](T)`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ ]()`,
`package p; type _[A, /* ERROR "expected ']', found ','" */] struct{ A }`,
`package p; type _[A, /* ERROR "unexpected comma" */] struct{ A }`,
`package p; func _[ /* ERROR "expected '\(', found '\['" */ type P, *Q interface{}]()`,
`package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B any](a A) B`,
`package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B C](a A) B`,
`package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B C[A, B]](a A) B`,
`package p; func(*T[ /* ERROR "missing ',' in parameter list" */ e, e]) _()`,
}
// invalidTParamErrs holds invalid source code examples annotated with the
// error messages produced when ParseTypeParams is set.
var invalidTParamErrs = []string{
`package p; type _[_ any] int; var _ = T[] /* ERROR "expected operand" */ {}`,
`package p; var _ func[ /* ERROR "cannot have type parameters" */ T any](T)`,
`package p; var _ func[ /* ERROR "must have no type parameters" */ T any](T)`,
`package p; func _[]/* ERROR "empty type parameter list" */()`,
// TODO(rfindley) a better location would be after the ']'
`package p; type _[A/* ERROR "all type parameters must be named" */,] struct{ A }`,
`package p; type _[A /* ERROR "all type parameters must be named" */ ,] struct{ A }`,
// TODO(rfindley) this error is confusing.
`package p; func _[type /* ERROR "all type parameters must be named" */P, *Q interface{}]()`,
`package p; func _[type /* ERROR "all type parameters must be named" */ P, *Q interface{}]()`,
`package p; func (T) _[ /* ERROR "must have no type parameters" */ A, B any](a A) B`,
`package p; func (T) _[ /* ERROR "must have no type parameters" */ A, B C](a A) B`,
`package p; func (T) _[ /* ERROR "must have no type parameters" */ A, B C[A, B]](a A) B`,
`package p; func(*T[e, e /* ERROR "e redeclared" */ ]) _()`,
}
func TestInvalid(t *testing.T) {

View File

@ -25,10 +25,15 @@ func Add /* =@AddDecl */[T /* =@T */ Addable /* @Addable */](l /* =@l */, r /* =
type Receiver /* =@Receiver */[P /* =@P */ any] struct {}
type RP /* =@RP1 */ struct{}
// TODO(rFindley): make a decision on how/whether to resolve identifiers that
// refer to receiver type parameters, as is the case for the 'P' result
// parameter below.
func (r /* =@recv */ Receiver /* @Receiver */ [P]) m() P {}
//
// For now, we ensure that types are not incorrectly resolved when receiver
// type parameters are in scope.
func (r /* =@recv */ Receiver /* @Receiver */ [RP]) m(RP) RP {}
func f /* =@f */[T1 /* =@T1 */ interface{~[]T2 /* @T2 */}, T2 /* =@T2 */ any](
x /* =@x */ T1 /* @T1 */, T1 /* =@T1_duplicate */ y, // Note that this is a bug:
@ -41,3 +46,6 @@ func f /* =@f */[T1 /* =@T1 */ interface{~[]T2 /* @T2 */}, T2 /* =@T2 */ any](
T1 /* @T1 */ := 0
var t1var /* =@t1var */ T1 /* @T1 */
}
// From issue #39634
func(*ph1[e, e])h(d)

View File

@ -9,7 +9,7 @@ package p
type List[E any /* ERROR "expected ']', found any" */ ] []E
type Pair[L, /* ERROR "expected ']', found ','" */ R any] struct {
type Pair[L, /* ERROR "unexpected comma" */ R any] struct {
Left L
Right R
}

View File

@ -367,20 +367,48 @@ func (p *printer) parameters(fields *ast.FieldList, isTypeParam bool) {
p.expr(stripParensAlways(par.Type))
prevLine = parLineEnd
}
// if the closing ")" is on a separate line from the last parameter,
// print an additional "," and line break
if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing {
p.print(token.COMMA)
p.linebreak(closing, 0, ignore, true)
} else if isTypeParam && fields.NumFields() == 1 {
// Otherwise, if we are in a type parameter list that could be confused
// with the constant array length expression [P*C], print a comma so that
// parsing is unambiguous.
//
// Note that while ParenExprs can also be ambiguous (issue #49482), the
// printed type is never parenthesized (stripParensAlways is used above).
if t, _ := fields.List[0].Type.(*ast.StarExpr); t != nil && !isTypeLit(t.X) {
p.print(token.COMMA)
}
}
// unindent if we indented
if ws == ignore {
p.print(unindent)
}
}
p.print(fields.Closing, closeTok)
}
// isTypeLit reports whether x is a (possibly parenthesized) type literal.
func isTypeLit(x ast.Expr) bool {
switch x := x.(type) {
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
return true
case *ast.StarExpr:
// *T may be a pointer dereferenciation.
// Only consider *T as type literal if T is a type literal.
return isTypeLit(x.X)
case *ast.ParenExpr:
return isTypeLit(x.X)
}
return false
}
func (p *printer) signature(sig *ast.FuncType) {
if sig.TypeParams != nil {
p.parameters(sig.TypeParams, true)

View File

@ -38,3 +38,29 @@ func _() {
// type constraint literals with elided interfaces
func _[P ~int, Q int | string]() {}
func _[P struct{ f int }, Q *P]() {}
// various potentially ambiguous type parameter lists (issue #49482)
type _[P *T,] struct{}
type _[P *T, _ any] struct{}
type _[P *T,] struct{}
type _[P *T, _ any] struct{}
type _[P T] struct{}
type _[P T, _ any] struct{}
type _[P *struct{}] struct{}
type _[P *struct{}] struct{}
type _[P []int] struct{}
// array type declarations
type _ [P(T)]struct{}
type _ [P((T))]struct{}
type _ [P * *T]struct{}
type _ [P * T]struct{}
type _ [P(*T)]struct{}
type _ [P(**T)]struct{}
type _ [P * T]struct{}
type _ [P*T - T]struct{}
type _[
P *T,
] struct{}

View File

@ -35,3 +35,29 @@ func _() {
// type constraint literals with elided interfaces
func _[P ~int, Q int | string]() {}
func _[P struct{f int}, Q *P]() {}
// various potentially ambiguous type parameter lists (issue #49482)
type _[P *T,] struct{}
type _[P *T, _ any] struct{}
type _[P (*T),] struct{}
type _[P (*T), _ any] struct{}
type _[P (T),] struct{}
type _[P (T), _ any] struct{}
type _[P *struct{}] struct{}
type _[P (*struct{})] struct{}
type _[P ([]int)] struct{}
// array type declarations
type _ [P(T)]struct{}
type _ [P((T))]struct{}
type _ [P * *T]struct{}
type _ [P * T]struct{}
type _ [P(*T)]struct{}
type _ [P(**T)]struct{}
type _ [P * T]struct{}
type _ [P * T - T]struct{}
type _[
P *T,
] struct{}

View File

@ -419,9 +419,15 @@ func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, i
}
// AssertableTo reports whether a value of type V can be asserted to have type T.
// The behavior of AssertableTo is undefined if V is a generalized interface; i.e.,
// an interface that may only be used as a type constraint in Go code.
func AssertableTo(V *Interface, T Type) bool {
m, _ := (*Checker)(nil).assertableTo(V, T)
return m == nil
// Checker.newAssertableTo suppresses errors for invalid types, so we need special
// handling here.
if T.Underlying() == Typ[Invalid] {
return false
}
return (*Checker)(nil).newAssertableTo(V, T) == nil
}
// AssignableTo reports whether a value of type V is assignable to a variable of type T.

View File

@ -2308,27 +2308,27 @@ type Bad Bad // invalid type
conf := Config{Error: func(error) {}}
pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
scope := pkg.Scope()
lookup := func(tname string) Type { return pkg.Scope().Lookup(tname).Type() }
var (
EmptyIface = scope.Lookup("EmptyIface").Type().Underlying().(*Interface)
I = scope.Lookup("I").Type().(*Named)
EmptyIface = lookup("EmptyIface").Underlying().(*Interface)
I = lookup("I").(*Named)
II = I.Underlying().(*Interface)
C = scope.Lookup("C").Type().(*Named)
C = lookup("C").(*Named)
CI = C.Underlying().(*Interface)
Integer = scope.Lookup("Integer").Type().Underlying().(*Interface)
EmptyTypeSet = scope.Lookup("EmptyTypeSet").Type().Underlying().(*Interface)
N1 = scope.Lookup("N1").Type()
Integer = lookup("Integer").Underlying().(*Interface)
EmptyTypeSet = lookup("EmptyTypeSet").Underlying().(*Interface)
N1 = lookup("N1")
N1p = NewPointer(N1)
N2 = scope.Lookup("N2").Type()
N2 = lookup("N2")
N2p = NewPointer(N2)
N3 = scope.Lookup("N3").Type()
N4 = scope.Lookup("N4").Type()
Bad = scope.Lookup("Bad").Type()
N3 = lookup("N3")
N4 = lookup("N4")
Bad = lookup("Bad")
)
tests := []struct {
t Type
i *Interface
V Type
T *Interface
want bool
}{
{I, II, true},
@ -2359,8 +2359,78 @@ type Bad Bad // invalid type
}
for _, test := range tests {
if got := Implements(test.t, test.i); got != test.want {
t.Errorf("Implements(%s, %s) = %t, want %t", test.t, test.i, got, test.want)
if got := Implements(test.V, test.T); got != test.want {
t.Errorf("Implements(%s, %s) = %t, want %t", test.V, test.T, got, test.want)
}
// The type assertion x.(T) is valid if T is an interface or if T implements the type of x.
// The assertion is never valid if T is a bad type.
V := test.T
T := test.V
want := false
if _, ok := T.Underlying().(*Interface); (ok || Implements(T, V)) && T != Bad {
want = true
}
if got := AssertableTo(V, T); got != want {
t.Errorf("AssertableTo(%s, %s) = %t, want %t", V, T, got, want)
}
}
}
func TestMissingMethodAlternative(t *testing.T) {
const src = `
package p
type T interface {
m()
}
type V0 struct{}
func (V0) m() {}
type V1 struct{}
type V2 struct{}
func (V2) m() int
type V3 struct{}
func (*V3) m()
type V4 struct{}
func (V4) M()
`
pkg, err := pkgFor("p.go", src, nil)
if err != nil {
t.Fatal(err)
}
T := pkg.Scope().Lookup("T").Type().Underlying().(*Interface)
lookup := func(name string) (*Func, bool) {
return MissingMethod(pkg.Scope().Lookup(name).Type(), T, true)
}
// V0 has method m with correct signature. Should not report wrongType.
method, wrongType := lookup("V0")
if method != nil || wrongType {
t.Fatalf("V0: got method = %v, wrongType = %v", method, wrongType)
}
checkMissingMethod := func(tname string, reportWrongType bool) {
method, wrongType := lookup(tname)
if method == nil || method.Name() != "m" || wrongType != reportWrongType {
t.Fatalf("%s: got method = %v, wrongType = %v", tname, method, wrongType)
}
}
// V1 has no method m. Should not report wrongType.
checkMissingMethod("V1", false)
// V2 has method m with wrong signature type (ignoring receiver). Should report wrongType.
checkMissingMethod("V2", true)
// V3 has no method m but it exists on *V3. Should report wrongType.
checkMissingMethod("V3", true)
// V4 has no method m but has M. Should not report wrongType.
checkMissingMethod("V4", false)
}

View File

@ -83,10 +83,24 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// of S and the respective parameter passing rules apply."
S := x.typ
var T Type
if s, _ := structuralType(S).(*Slice); s != nil {
if s, _ := coreType(S).(*Slice); s != nil {
T = s.elem
} else {
check.invalidArg(x, _InvalidAppend, "%s is not a slice", x)
var cause string
switch {
case x.isNil():
cause = "have untyped nil"
case isTypeParam(S):
if u := coreType(S); u != nil {
cause = check.sprintf("%s has core type %s", x, u)
} else {
cause = check.sprintf("%s has no core type", x)
}
default:
cause = check.sprintf("have %s", x)
}
// don't use Checker.invalidArg here as it would repeat "argument" in the error message
check.errorf(x, _InvalidAppend, "first argument to append must be a slice; %s", cause)
return
}
@ -102,7 +116,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
if x.mode == invalid {
return
}
if t := structuralString(x.typ); t != nil && isString(t) {
if t := coreString(x.typ); t != nil && isString(t) {
if check.Types != nil {
sig := makeSig(S, S, x.typ)
sig.variadic = true
@ -143,9 +157,8 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// cap(x)
// len(x)
mode := invalid
var typ Type
var val constant.Value
switch typ = arrayPtrDeref(under(x.typ)); t := typ.(type) {
switch t := arrayPtrDeref(under(x.typ)).(type) {
case *Basic:
if isString(t) && id == _Len {
if x.mode == constant_ {
@ -202,7 +215,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
}
if mode == invalid && typ != Typ[Invalid] {
if mode == invalid && under(x.typ) != Typ[Invalid] {
code := _InvalidCap
if id == _Len {
code = _InvalidLen
@ -211,12 +224,14 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
return
}
// record the signature before changing x.typ
if check.Types != nil && mode != constant_ {
check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ))
}
x.mode = mode
x.typ = Typ[Int]
x.val = val
if check.Types != nil && mode != constant_ {
check.recordBuiltinType(call.Fun, makeSig(x.typ, typ))
}
case _Close:
// close(c)
@ -314,7 +329,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
return nil
}
resTyp := check.applyTypeFunc(f, x.typ)
resTyp := check.applyTypeFunc(f, x, id)
if resTyp == nil {
check.invalidArg(x, _InvalidComplex, "arguments have type %s, expected floating-point", x.typ)
return
@ -335,14 +350,14 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case _Copy:
// copy(x, y []T) int
dst, _ := structuralType(x.typ).(*Slice)
dst, _ := coreType(x.typ).(*Slice)
var y operand
arg(&y, 1)
if y.mode == invalid {
return
}
src0 := structuralString(y.typ)
src0 := coreString(y.typ)
if src0 != nil && isString(src0) {
src0 = NewSlice(universeByte)
}
@ -442,7 +457,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
return nil
}
resTyp := check.applyTypeFunc(f, x.typ)
resTyp := check.applyTypeFunc(f, x, id)
if resTyp == nil {
code := _InvalidImag
if id == _Real {
@ -480,13 +495,13 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
}
var min int // minimum number of arguments
switch structuralType(T).(type) {
switch coreType(T).(type) {
case *Slice:
min = 2
case *Map, *Chan:
min = 1
case nil:
check.errorf(arg0, _InvalidMake, "cannot make %s: no structural type", arg0)
check.errorf(arg0, _InvalidMake, "cannot make %s: no core type", arg0)
return
default:
check.invalidArg(arg0, _InvalidMake, "cannot make %s; type must be slice, map, or channel", arg0)
@ -809,8 +824,8 @@ func hasVarSize(t Type) bool {
// of x. If any of these applications of f return nil,
// applyTypeFunc returns nil.
// If x is not a type parameter, the result is f(x).
func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
if tp, _ := x.(*TypeParam); tp != nil {
func (check *Checker) applyTypeFunc(f func(Type) Type, x *operand, id builtinId) Type {
if tp, _ := x.typ.(*TypeParam); tp != nil {
// Test if t satisfies the requirements for the argument
// type and collect possible result types at the same time.
var terms []*Term
@ -827,17 +842,34 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
return nil
}
// We can type-check this fine but we're introducing a synthetic
// type parameter for the result. It's not clear what the API
// implications are here. Report an error for 1.18 (see #50912),
// but continue type-checking.
var code errorCode
switch id {
case _Real:
code = _InvalidReal
case _Imag:
code = _InvalidImag
case _Complex:
code = _InvalidComplex
default:
unreachable()
}
check.softErrorf(x, code, "%s not supported as argument to %s for go1.18 (see issue #50937)", x, predeclaredFuncs[id].name)
// Construct a suitable new type parameter for the result type.
// The type parameter is placed in the current package so export/import
// works as expected.
tpar := NewTypeName(token.NoPos, check.pkg, "<type parameter>", nil)
tpar := NewTypeName(token.NoPos, check.pkg, tp.obj.name, nil)
ptyp := check.newTypeParam(tpar, NewInterfaceType(nil, []Type{NewUnion(terms)})) // assigns type to tpar as a side-effect
ptyp.index = tp.index
return ptyp
}
return f(x)
return f(x.typ)
}
// makeSig makes a signature for the given argument and result types.

View File

@ -29,6 +29,8 @@ var builtinCalls = []struct {
{"cap", `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant
{"cap", `var s []int64; _ = cap(s)`, `func([]int64) int`},
{"cap", `var c chan<-bool; _ = cap(c)`, `func(chan<- bool) int`},
{"cap", `type S []byte; var s S; _ = cap(s)`, `func(p.S) int`},
{"cap", `var s P; _ = cap(s)`, `func(P) int`},
{"len", `_ = len("foo")`, `invalid type`}, // constant
{"len", `var s string; _ = len(s)`, `func(string) int`},
@ -37,6 +39,8 @@ var builtinCalls = []struct {
{"len", `var s []int64; _ = len(s)`, `func([]int64) int`},
{"len", `var c chan<-bool; _ = len(c)`, `func(chan<- bool) int`},
{"len", `var m map[string]float32; _ = len(m)`, `func(map[string]float32) int`},
{"len", `type S []byte; var s S; _ = len(s)`, `func(p.S) int`},
{"len", `var s P; _ = len(s)`, `func(P) int`},
{"close", `var c chan int; close(c)`, `func(chan int)`},
{"close", `var c chan<- chan string; close(c)`, `func(chan<- chan string)`},
@ -159,7 +163,7 @@ func TestBuiltinSignatures(t *testing.T) {
func testBuiltinSignature(t *testing.T, name, src0, want string) {
t.Skip("skipping for gccgo--no default importer")
src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _[P any]() { %s }`, src0)
src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _[P ~[]byte]() { %s }`, src0)
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
t.Errorf("%s: %s", src0, err)

View File

@ -171,7 +171,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
cgocall := x.mode == cgofunc
// a type parameter may be "called" if all types have the same signature
sig, _ := structuralType(x.typ).(*Signature)
sig, _ := coreType(x.typ).(*Signature)
if sig == nil {
check.invalidOp(x, _InvalidCall, "cannot call non-function %s", x)
x.mode = invalid

Some files were not shown because too many files have changed in this diff Show More