cuelang.org/go@v0.10.1/cue/load/search.go (about)

     1  // Copyright 2018 The CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package load
    16  
    17  import (
    18  	"fmt"
    19  	"io/fs"
    20  	"path"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"cuelang.org/go/cue/build"
    25  	"cuelang.org/go/cue/errors"
    26  	"cuelang.org/go/cue/token"
    27  	"cuelang.org/go/internal/mod/modimports"
    28  	"cuelang.org/go/mod/module"
    29  )
    30  
    31  // A match represents the result of matching a single package pattern.
    32  type match struct {
    33  	Pattern string // the pattern itself
    34  	Literal bool   // whether it is a literal (no wildcards)
    35  	Pkgs    []*build.Instance
    36  	Err     errors.Error
    37  }
    38  
    39  // TODO: should be matched from module file only.
    40  // The pattern is either "all" (all packages), "std" (standard packages),
    41  // "cmd" (standard commands), or a path including "...".
    42  func (l *loader) matchPackages(pattern, pkgName string) *match {
    43  	// cfg := l.cfg
    44  	m := &match{
    45  		Pattern: pattern,
    46  		Literal: false,
    47  	}
    48  	// match := func(string) bool { return true }
    49  	// treeCanMatch := func(string) bool { return true }
    50  	// if !isMetaPackage(pattern) {
    51  	// 	match = matchPattern(pattern)
    52  	// 	treeCanMatch = treeCanMatchPattern(pattern)
    53  	// }
    54  
    55  	// have := map[string]bool{
    56  	// 	"builtin": true, // ignore pseudo-package that exists only for documentation
    57  	// }
    58  
    59  	// for _, src := range cfg.srcDirs() {
    60  	// 	if pattern == "std" || pattern == "cmd" {
    61  	// 		continue
    62  	// 	}
    63  	// 	src = filepath.Clean(src) + string(filepath.Separator)
    64  	// 	root := src
    65  	// 	if pattern == "cmd" {
    66  	// 		root += "cmd" + string(filepath.Separator)
    67  	// 	}
    68  	// 	filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
    69  	// 		if err != nil || path == src {
    70  	// 			return nil
    71  	// 		}
    72  
    73  	// 		want := true
    74  	// 		// Avoid .foo, _foo, and testdata directory trees.
    75  	// 		_, elem := filepath.Split(path)
    76  	// 		if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    77  	// 			want = false
    78  	// 		}
    79  
    80  	// 		name := filepath.ToSlash(path[len(src):])
    81  	// 		if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
    82  	// 			// The name "std" is only the standard library.
    83  	// 			// If the name is cmd, it's the root of the command tree.
    84  	// 			want = false
    85  	// 		}
    86  	// 		if !treeCanMatch(name) {
    87  	// 			want = false
    88  	// 		}
    89  
    90  	// 		if !fi.IsDir() {
    91  	// 			if fi.Mode()&os.ModeSymlink != 0 && want {
    92  	// 				if target, err := os.Stat(path); err == nil && target.IsDir() {
    93  	// 					fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
    94  	// 				}
    95  	// 			}
    96  	// 			return nil
    97  	// 		}
    98  	// 		if !want {
    99  	// 			return skipDir
   100  	// 		}
   101  
   102  	// 		if have[name] {
   103  	// 			return nil
   104  	// 		}
   105  	// 		have[name] = true
   106  	// 		if !match(name) {
   107  	// 			return nil
   108  	// 		}
   109  	// 		pkg := l.importPkg(".", path)
   110  	// 		if err := pkg.Error; err != nil {
   111  	// 			if _, noGo := err.(*noCUEError); noGo {
   112  	// 				return nil
   113  	// 			}
   114  	// 		}
   115  
   116  	// 		// If we are expanding "cmd", skip main
   117  	// 		// packages under cmd/vendor. At least as of
   118  	// 		// March, 2017, there is one there for the
   119  	// 		// vendored pprof tool.
   120  	// 		if pattern == "cmd" && strings.HasPrefix(pkg.DisplayPath, "cmd/vendor") && pkg.PkgName == "main" {
   121  	// 			return nil
   122  	// 		}
   123  
   124  	// 		m.Pkgs = append(m.Pkgs, pkg)
   125  	// 		return nil
   126  	// 	})
   127  	// }
   128  	return m
   129  }
   130  
   131  // matchPackagesInFS is like allPackages but is passed a pattern
   132  // beginning ./ or ../, meaning it should scan the tree rooted
   133  // at the given directory. There are ... in the pattern too.
   134  // (See cue help inputs for pattern syntax.)
   135  func (l *loader) matchPackagesInFS(pattern, pkgName string) *match {
   136  	c := l.cfg
   137  	m := &match{
   138  		Pattern: pattern,
   139  		Literal: false,
   140  	}
   141  
   142  	// Find directory to begin the scan.
   143  	// Could be smarter but this one optimization
   144  	// is enough for now, since ... is usually at the
   145  	// end of a path.
   146  	i := strings.Index(pattern, "...")
   147  	dir, _ := path.Split(pattern[:i])
   148  
   149  	root := l.abs(dir)
   150  
   151  	// Find new module root from here or check there are no additional
   152  	// cue.mod files between here and the next module.
   153  
   154  	if !hasFilepathPrefix(root, c.ModuleRoot) {
   155  		m.Err = errors.Newf(token.NoPos,
   156  			"cue: pattern %s refers to dir %s, outside module root %s",
   157  			pattern, root, c.ModuleRoot)
   158  		return m
   159  	}
   160  
   161  	pkgDir := filepath.Join(root, modDir)
   162  
   163  	_ = c.fileSystem.walk(root, func(path string, entry fs.DirEntry, err errors.Error) errors.Error {
   164  		if err != nil || !entry.IsDir() {
   165  			return nil
   166  		}
   167  		if path == pkgDir {
   168  			return skipDir
   169  		}
   170  
   171  		top := path == root
   172  
   173  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   174  		_, elem := filepath.Split(path)
   175  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   176  		if dot || strings.HasPrefix(elem, "_") || (elem == "testdata" && !top) {
   177  			return skipDir
   178  		}
   179  
   180  		if !top {
   181  			// Ignore other modules found in subdirectories.
   182  			if _, err := c.fileSystem.stat(filepath.Join(path, modDir)); err == nil {
   183  				return skipDir
   184  			}
   185  		}
   186  
   187  		// name := prefix + filepath.ToSlash(path)
   188  		// if !match(name) {
   189  		// 	return nil
   190  		// }
   191  
   192  		// We keep the directory if we can import it, or if we can't import it
   193  		// due to invalid CUE source files. This means that directories
   194  		// containing parse errors will be built (and fail) instead of being
   195  		// silently skipped as not matching the pattern.
   196  		// Do not take root, as we want to stay relative
   197  		// to one dir only.
   198  		relPath, err2 := filepath.Rel(c.Dir, path)
   199  		if err2 != nil {
   200  			panic(err2) // Should never happen because c.Dir is absolute.
   201  		}
   202  		relPath = "./" + filepath.ToSlash(relPath)
   203  		// TODO: consider not doing these checks here.
   204  		inst := l.newRelInstance(token.NoPos, relPath, pkgName)
   205  		pkgs := l.importPkg(token.NoPos, inst)
   206  		for _, p := range pkgs {
   207  			if err := p.Err; err != nil && (p == nil || len(p.InvalidFiles) == 0) {
   208  				switch err.(type) {
   209  				case *NoFilesError:
   210  					if c.DataFiles && len(p.OrphanedFiles) > 0 {
   211  						break
   212  					}
   213  					return nil
   214  				default:
   215  					m.Err = errors.Append(m.Err, err)
   216  				}
   217  			}
   218  		}
   219  
   220  		m.Pkgs = append(m.Pkgs, pkgs...)
   221  		return nil
   222  	})
   223  	return m
   224  }
   225  
   226  // importPaths returns the matching paths to use for the given command line.
   227  // It calls ImportPathsQuiet and then WarnUnmatched.
   228  func (l *loader) importPaths(patterns []string) []*match {
   229  	matches := l.importPathsQuiet(patterns)
   230  	warnUnmatched(matches)
   231  	return matches
   232  }
   233  
   234  // importPathsQuiet is like importPaths but does not warn about patterns with no matches.
   235  func (l *loader) importPathsQuiet(patterns []string) []*match {
   236  	var out []*match
   237  	for _, a := range cleanPatterns(patterns) {
   238  		if isMetaPackage(a) {
   239  			out = append(out, l.matchPackages(a, l.cfg.Package))
   240  			continue
   241  		}
   242  
   243  		orig := a
   244  		pkgName := l.cfg.Package
   245  		switch p := strings.IndexByte(a, ':'); {
   246  		case p < 0:
   247  		case p == 0:
   248  			pkgName = a[1:]
   249  			a = "."
   250  		default:
   251  			pkgName = a[p+1:]
   252  			a = a[:p]
   253  		}
   254  		if pkgName == "*" {
   255  			pkgName = ""
   256  		}
   257  
   258  		if strings.Contains(a, "...") {
   259  			if isLocalImport(a) {
   260  				out = append(out, l.matchPackagesInFS(a, pkgName))
   261  			} else {
   262  				out = append(out, l.matchPackages(a, pkgName))
   263  			}
   264  			continue
   265  		}
   266  
   267  		var p *build.Instance
   268  		if isLocalImport(a) {
   269  			p = l.newRelInstance(token.NoPos, a, pkgName)
   270  		} else {
   271  			p = l.newInstance(token.NoPos, importPath(orig))
   272  		}
   273  
   274  		pkgs := l.importPkg(token.NoPos, p)
   275  		out = append(out, &match{Pattern: a, Literal: true, Pkgs: pkgs})
   276  	}
   277  	return out
   278  }
   279  
   280  type resolvedPackageArg struct {
   281  	// The original field may be needed once we want to replace the original
   282  	// package pattern matching code, as it is necessary to populate Instance.DisplayPath.
   283  	original          string
   284  	resolvedCanonical string
   285  }
   286  
   287  func expandPackageArgs(c *Config, pkgArgs []string, pkgQual string, tg *tagger) ([]resolvedPackageArg, error) {
   288  	expanded := make([]resolvedPackageArg, 0, len(pkgArgs))
   289  	for _, p := range pkgArgs {
   290  		var err error
   291  		expanded, err = appendExpandedPackageArg(c, expanded, p, pkgQual, tg)
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  	}
   296  	return expanded, nil
   297  }
   298  
   299  // appendExpandedPackageArg appends all the package paths matched by p to pkgPaths
   300  // and returns the result. It also cleans the paths and makes them absolute.
   301  //
   302  // pkgQual is used to determine which packages to match when wildcards are expanded.
   303  // Its semantics follow those of [Config.Package].
   304  func appendExpandedPackageArg(c *Config, pkgPaths []resolvedPackageArg, p string, pkgQual string, tg *tagger) ([]resolvedPackageArg, error) {
   305  	origp := p
   306  	if filepath.IsAbs(p) {
   307  		return nil, fmt.Errorf("cannot use absolute directory %q as package path", p)
   308  	}
   309  	// Arguments are supposed to be import paths, but
   310  	// as a courtesy to Windows developers, rewrite \ to /
   311  	// in command-line arguments. Handles .\... and so on.
   312  	p = filepath.ToSlash(p)
   313  
   314  	ip := module.ParseImportPath(p)
   315  	if ip.Qualifier == "_" {
   316  		return nil, fmt.Errorf("invalid import path qualifier _ in %q", origp)
   317  	}
   318  
   319  	isRel := strings.HasPrefix(ip.Path, "./")
   320  	// Put argument in canonical form.
   321  	ip.Path = path.Clean(ip.Path)
   322  	if isRel && ip.Path != "." {
   323  		// Preserve leading "./".
   324  		ip.Path = "./" + ip.Path
   325  	}
   326  	isLocal := isLocalImport(ip.Path)
   327  	// Note that when c.Module is empty, c.ModuleRoot is sometimes,
   328  	// but not always, the same as c.Dir. Specifically it might point
   329  	// to the directory containing a cue.mod directory even if that
   330  	// directory doesn't actually contain a module.cue file.
   331  	moduleRoot := c.ModuleRoot
   332  	if isLocal {
   333  		if c.Module != "" {
   334  			// Make local import paths into absolute paths inside
   335  			// the module root.
   336  			absPath := path.Join(c.Dir, ip.Path)
   337  			pkgPath, err := importPathFromAbsDir(c, absPath, origp)
   338  			if err != nil {
   339  				return nil, err
   340  			}
   341  			ip1 := module.ParseImportPath(string(pkgPath))
   342  			// Leave ip.Qualifier and ip.ExplicitQualifier intact.
   343  			ip.Path = ip1.Path
   344  			ip.Version = ip1.Version
   345  		} else {
   346  			// There's no module, so we can't make
   347  			// the import path absolute.
   348  			moduleRoot = c.Dir
   349  		}
   350  	}
   351  	if !strings.Contains(ip.Path, "...") {
   352  		if isLocal && !ip.ExplicitQualifier {
   353  			// A package qualifier has not been explicitly specified for a local
   354  			// import path so we need to walk the package directory to find the
   355  			// packages in it. We have a special rule for local imports because it's
   356  			// inconvenient always to have to specify a package qualifier when
   357  			// there's only one package in the current directory but the last
   358  			// component of its package path does not match its name.
   359  			return appendExpandedUnqualifiedPackagePath(pkgPaths, origp, ip, pkgQual, module.SourceLoc{
   360  				FS:  c.fileSystem.ioFS(moduleRoot),
   361  				Dir: ".",
   362  			}, c.Module, tg)
   363  		}
   364  		return append(pkgPaths, resolvedPackageArg{origp, ip.Canonical().String()}), nil
   365  	}
   366  	// Strip the module prefix, leaving only the directory relative
   367  	// to the module root.
   368  	ip, ok := cutModulePrefix(ip, c.Module)
   369  	if !ok {
   370  		return nil, fmt.Errorf("pattern not allowed in external package path %q", origp)
   371  	}
   372  	return appendExpandedWildcardPackagePath(pkgPaths, ip, pkgQual, module.SourceLoc{
   373  		FS:  c.fileSystem.ioFS(moduleRoot),
   374  		Dir: ".",
   375  	}, c.Module, tg)
   376  }
   377  
   378  // appendExpandedUnqualifiedPackagePath expands the given import path,
   379  // which is relative to the root of the module, into its resolved and
   380  // qualified package paths according to the following rules (the first rule
   381  // that applies is used)
   382  //
   383  //  1. if pkgQual is "*", it chooses all the packages present in the
   384  //     package directory.
   385  //  2. if pkgQual is "_", it looks for a package file with no package name.
   386  //  3. if there's a package named after ip.Qualifier it chooses that
   387  //  4. if there's exactly one package in the directory it will choose that.
   388  //  5. if there's more than one package in the directory, it returns a MultiplePackageError.
   389  //  6. if there are no package files in the directory, it just appends the import path as is, leaving it
   390  //     to later logic to produce an error in this case.
   391  func appendExpandedUnqualifiedPackagePath(pkgPaths []resolvedPackageArg, origp string, ip module.ImportPath, pkgQual string, mainModRoot module.SourceLoc, mainModPath string, tg *tagger) (_ []resolvedPackageArg, _err error) {
   392  	ipRel, ok := cutModulePrefix(ip, mainModPath)
   393  	if !ok {
   394  		// Should never happen.
   395  		return nil, fmt.Errorf("internal error: local import path %q in module %q has resulted in non-internal package %q", origp, mainModPath, ip)
   396  	}
   397  	dir := path.Join(mainModRoot.Dir, ipRel.Path)
   398  	info, err := fs.Stat(mainModRoot.FS, dir)
   399  	if err != nil {
   400  		// The package directory doesn't exist.
   401  		// Treat it like an empty directory and let later logic deal with it.
   402  		return append(pkgPaths, resolvedPackageArg{origp, ip.String()}), nil
   403  	}
   404  	if !info.IsDir() {
   405  		return nil, fmt.Errorf("%s is a file and not a package directory", origp)
   406  	}
   407  	iter := modimports.PackageFiles(mainModRoot.FS, dir, "*")
   408  
   409  	// 1. if pkgQual is "*", it appends all the packages present in the package directory.
   410  	if pkgQual == "*" {
   411  		wasAdded := make(map[string]bool)
   412  		iter(func(f modimports.ModuleFile, err error) bool {
   413  			if err != nil {
   414  				_err = err
   415  				return false
   416  			}
   417  			if err := shouldBuildFile(f.Syntax, tg.tagIsSet); err != nil {
   418  				// Later build logic should pick up and report the same error.
   419  				return true
   420  			}
   421  			pkgName := f.Syntax.PackageName()
   422  			if wasAdded[pkgName] {
   423  				return true
   424  			}
   425  			wasAdded[pkgName] = true
   426  			ip := ip
   427  			ip.Qualifier = pkgName
   428  			p := ip.String()
   429  			pkgPaths = append(pkgPaths, resolvedPackageArg{p, p})
   430  			return true
   431  		})
   432  		if _err != nil {
   433  			return nil, _err
   434  		}
   435  		return pkgPaths, nil
   436  	}
   437  	var files []modimports.ModuleFile
   438  	foundQualifier := false
   439  	// TODO(rog): for f, err := range iter {
   440  	iter(func(f modimports.ModuleFile, err error) bool {
   441  		if err != nil {
   442  			_err = err
   443  			return false
   444  		}
   445  		pkgName := f.Syntax.PackageName()
   446  		// 2. if pkgQual is "_", it looks for a package file with no package name.
   447  		// 3. if there's a package named after ip.Qualifier it chooses that
   448  		if (pkgName != "" && pkgName == ip.Qualifier) || (pkgQual == "_" && pkgName == "") {
   449  			foundQualifier = true
   450  			return false
   451  		}
   452  		if pkgName != "" {
   453  			files = append(files, f)
   454  		}
   455  		return true
   456  	})
   457  	if _err != nil {
   458  		return nil, _err
   459  	}
   460  	if foundQualifier {
   461  		// We found the actual package that was implied by the import path (or pkgQual == "_").
   462  		// This takes precedence over anything else.
   463  		return append(pkgPaths, resolvedPackageArg{origp, ip.String()}), nil
   464  	}
   465  	if len(files) == 0 {
   466  		// 6. if there are no package files in the directory, it just appends the import path as is,
   467  		// leaving it to later logic to produce an error in this case.
   468  		return append(pkgPaths, resolvedPackageArg{origp, ip.String()}), nil
   469  	}
   470  	pkgName := files[0].Syntax.PackageName()
   471  	for _, f := range files[1:] {
   472  		// 5. if there's more than one package in the directory, it returns a MultiplePackageError.
   473  		if pkgName1 := f.Syntax.PackageName(); pkgName1 != pkgName {
   474  			return nil, &MultiplePackageError{
   475  				Dir:      dir,
   476  				Packages: []string{pkgName, pkgName1},
   477  				Files: []string{
   478  					path.Base(files[0].FilePath),
   479  					path.Base(f.FilePath),
   480  				},
   481  			}
   482  		}
   483  	}
   484  	// 4. if there's exactly one package in the directory it will choose that.
   485  	ip.Qualifier = pkgName
   486  	return append(pkgPaths, resolvedPackageArg{origp, ip.String()}), nil
   487  }
   488  
   489  // appendExpandedWildcardPackagePath expands the given pattern into any packages that it matches
   490  // and appends the results to pkgPaths. It returns an error if the pattern matches nothing.
   491  //
   492  // Note:
   493  // * We know that pattern contains "..."
   494  // * We know that pattern is relative to the module root
   495  func appendExpandedWildcardPackagePath(pkgPaths []resolvedPackageArg, pattern module.ImportPath, pkgQual string, mainModRoot module.SourceLoc, mainModPath string, tg *tagger) (_ []resolvedPackageArg, _err error) {
   496  	modIpath := module.ParseImportPath(mainModPath)
   497  	// Find directory to begin the scan.
   498  	// Could be smarter but this one optimization is enough for now,
   499  	// since ... is usually at the end of a path.
   500  	// TODO: strip package qualifier.
   501  	i := strings.Index(pattern.Path, "...")
   502  	dir, _ := path.Split(pattern.Path[:i])
   503  	dir = path.Join(mainModRoot.Dir, dir)
   504  	var isSelected func(string) bool
   505  	switch pkgQual {
   506  	case "_":
   507  		isSelected = func(pkgName string) bool {
   508  			return pkgName == ""
   509  		}
   510  	case "*":
   511  		isSelected = func(pkgName string) bool {
   512  			return true
   513  		}
   514  	case "":
   515  		isSelected = func(pkgName string) bool {
   516  			// The package ambiguity logic will be triggered if there's more than one
   517  			// package in the same directory.
   518  			return pkgName != ""
   519  		}
   520  	default:
   521  		isSelected = func(pkgName string) bool {
   522  			return pkgName == pkgQual
   523  		}
   524  	}
   525  
   526  	var prevFile modimports.ModuleFile
   527  	var prevImportPath module.ImportPath
   528  	iter := modimports.AllModuleFiles(mainModRoot.FS, dir)
   529  	iter(func(f modimports.ModuleFile, err error) bool {
   530  		if err != nil {
   531  			return false
   532  		}
   533  		if err := shouldBuildFile(f.Syntax, tg.tagIsSet); err != nil {
   534  			// Later build logic should pick up and report the same error.
   535  			return true
   536  		}
   537  		pkgName := f.Syntax.PackageName()
   538  		if !isSelected(pkgName) {
   539  			return true
   540  		}
   541  		if pkgName == "" {
   542  			pkgName = "_"
   543  		}
   544  		ip := module.ImportPath{
   545  			Path:      path.Join(modIpath.Path, path.Dir(f.FilePath)),
   546  			Qualifier: pkgName,
   547  			Version:   modIpath.Version,
   548  		}
   549  		if modIpath.Path == "" {
   550  			// There's no module, so make sure that the path still looks like a relative import path.
   551  			if !strings.HasPrefix(ip.Path, "../") {
   552  				ip.Path = "./" + ip.Path
   553  			}
   554  		}
   555  		if ip == prevImportPath {
   556  			// TODO(rog): this isn't sufficient for full deduplication: we can get an alternation of different
   557  			// package names within the same directory. We'll need to maintain a map.
   558  			return true
   559  		}
   560  		if pkgQual == "" {
   561  			// Note: we can look at the previous item only rather than maintaining a map
   562  			// because modimports.AllModuleFiles guarantees that files in the same
   563  			// package are always adjacent.
   564  			if prevFile.FilePath != "" && prevImportPath.Path == ip.Path && ip.Qualifier != prevImportPath.Qualifier {
   565  				// A wildcard isn't currently allowed to match multiple packages
   566  				// in a single directory.
   567  				_err = &MultiplePackageError{
   568  					Dir:      path.Dir(f.FilePath),
   569  					Packages: []string{prevImportPath.Qualifier, ip.Qualifier},
   570  					Files: []string{
   571  						path.Base(prevFile.FilePath),
   572  						path.Base(f.FilePath),
   573  					},
   574  				}
   575  				return false
   576  			}
   577  		}
   578  		pkgPaths = append(pkgPaths, resolvedPackageArg{ip.String(), ip.String()})
   579  		prevFile, prevImportPath = f, ip
   580  		return true
   581  	})
   582  	return pkgPaths, _err
   583  }
   584  
   585  // cutModulePrefix strips the given module path from p and reports whether p is inside mod.
   586  // It returns a relative package path within m.
   587  //
   588  // If p does not contain a major version suffix but otherwise matches mod, it counts as a match.
   589  func cutModulePrefix(p module.ImportPath, mod string) (module.ImportPath, bool) {
   590  	if mod == "" {
   591  		return p, true
   592  	}
   593  	modPath, modVers, ok := module.SplitPathVersion(mod)
   594  	if !ok {
   595  		modPath = mod
   596  	}
   597  	if !strings.HasPrefix(p.Path, modPath) {
   598  		return module.ImportPath{}, false
   599  	}
   600  	if p.Path == modPath {
   601  		p.Path = "."
   602  		return p, true
   603  	}
   604  	if p.Path[len(modPath)] != '/' {
   605  		return module.ImportPath{}, false
   606  	}
   607  	if p.Version != "" && modVers != "" && p.Version != modVers {
   608  		return module.ImportPath{}, false
   609  	}
   610  	p.Path = "." + p.Path[len(modPath):]
   611  	p.Version = ""
   612  	return p, true
   613  }