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