loader.go raw

   1  package loader
   2  
   3  import (
   4  	"crypto/sha512"
   5  	"errors"
   6  	"fmt"
   7  	"go/ast"
   8  	"go/constant"
   9  	"go/parser"
  10  	"go/scanner"
  11  	"go/token"
  12  	"go/types"
  13  	"os"
  14  	"path"
  15  	"path/filepath"
  16  	"runtime"
  17  	"strings"
  18  	"unicode"
  19  
  20  	"moxie/cgo"
  21  	"moxie/compileopts"
  22  	"moxie/goenv"
  23  )
  24  
  25  var initFileVersions = func(info *types.Info) {}
  26  
  27  // Program holds all packages and some metadata about the program as a whole.
  28  type Program struct {
  29  	config      *compileopts.Config
  30  	typeChecker types.Config
  31  	goroot      string // synthetic GOROOT
  32  	workingDir  string
  33  
  34  	Packages map[string]*Package
  35  	sorted   []*Package
  36  	fset     *token.FileSet
  37  
  38  	// Information obtained during parsing.
  39  	LDFlags []string
  40  }
  41  
  42  // PackageJSON is a subset of the JSON struct returned from `moxie list`.
  43  type PackageJSON struct {
  44  	Dir        string
  45  	ImportPath string
  46  	Name       string
  47  	ForTest    string
  48  	Root       string
  49  	Module     struct {
  50  		Path      string
  51  		Main      bool
  52  		Dir       string
  53  		GoMod     string
  54  		GoVersion string
  55  	}
  56  
  57  	// Source files
  58  	GoFiles  []string // TODO: rename to MxFiles when moxie list is implemented
  59  	CgoFiles []string
  60  	CFiles   []string
  61  
  62  	// Embedded files
  63  	EmbedFiles []string
  64  
  65  	// Dependency information
  66  	Imports   []string
  67  	ImportMap map[string]string
  68  
  69  	// Error information
  70  	Error *struct {
  71  		ImportStack []string
  72  		Pos         string
  73  		Err         string
  74  	}
  75  }
  76  
  77  // Package holds a loaded package, its imports, and its parsed files.
  78  type Package struct {
  79  	PackageJSON
  80  
  81  	program      *Program
  82  	Files        []*ast.File
  83  	FileHashes   map[string][]byte
  84  	CFlags       []string // CFlags used during CGo preprocessing (only set if CGo is used)
  85  	CGoHeaders   []string // text above 'import "C"' lines
  86  	EmbedGlobals map[string][]*EmbedFile
  87  	Pkg          *types.Package
  88  	info         types.Info
  89  }
  90  
  91  type EmbedFile struct {
  92  	Name      string
  93  	Size      uint64
  94  	Hash      string // hash of the file (as a hex string)
  95  	NeedsData bool   // true if this file is embedded as a byte slice
  96  	Data      []byte // contents of this file (only if NeedsData is set)
  97  }
  98  
  99  // Load loads the given package with all dependencies (including the runtime
 100  // package). Call .Parse() afterwards to parse all Go files (including CGo
 101  // processing, if necessary).
 102  func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) (*Program, error) {
 103  	// Make int≡int32 and uint≡uint32 on all targets.
 104  	patchIntTypes()
 105  
 106  	goroot, err := GetCachedGoroot(config)
 107  	if err != nil {
 108  		return nil, err
 109  	}
 110  	var wd string
 111  	if config.Options.Directory != "" {
 112  		wd = config.Options.Directory
 113  	} else {
 114  		wd, err = os.Getwd()
 115  		if err != nil {
 116  			return nil, err
 117  		}
 118  	}
 119  	p := &Program{
 120  		config:      config,
 121  		typeChecker: typeChecker,
 122  		goroot:      goroot,
 123  		workingDir:  wd,
 124  		Packages:    make(map[string]*Package),
 125  		fset:        token.NewFileSet(),
 126  	}
 127  
 128  	// Discover packages and dependencies using the internal mxlist
 129  	// package scanner. This replaces the external `go list` command and
 130  	// natively understands .mx source files.
 131  	pkgJSONs, err := mxListPackages(config, goroot, inputPkg)
 132  	if err != nil {
 133  		return nil, fmt.Errorf("mxlist: %w", err)
 134  	}
 135  
 136  	for _, pj := range pkgJSONs {
 137  		pkg := &Package{
 138  			PackageJSON: *pj,
 139  			program:     p,
 140  			FileHashes:  make(map[string][]byte),
 141  			EmbedGlobals: make(map[string][]*EmbedFile),
 142  			info: types.Info{
 143  				Types:      make(map[ast.Expr]types.TypeAndValue),
 144  				Instances:  make(map[*ast.Ident]types.Instance),
 145  				Defs:       make(map[*ast.Ident]types.Object),
 146  				Uses:       make(map[*ast.Ident]types.Object),
 147  				Implicits:  make(map[ast.Node]types.Object),
 148  				Scopes:     make(map[ast.Node]*types.Scope),
 149  				Selections: make(map[*ast.SelectorExpr]*types.Selection),
 150  			},
 151  		}
 152  		p.sorted = append(p.sorted, pkg)
 153  		p.Packages[pkg.ImportPath] = pkg
 154  	}
 155  
 156  	return p, nil
 157  }
 158  
 159  // getOriginalPath looks whether this path is in the generated GOROOT and if so,
 160  // replaces the path with the original path (in GOROOT or MOXIEROOT). Otherwise
 161  // the input path is returned.
 162  func (p *Program) getOriginalPath(path string) string {
 163  	originalPath := path
 164  	if strings.HasPrefix(path, p.goroot+string(filepath.Separator)) {
 165  		// If this file is part of the synthetic GOROOT, try to infer the
 166  		// original path.
 167  		relpath := path[len(filepath.Join(p.goroot, "src"))+1:]
 168  		realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
 169  		if _, err := os.Stat(realgorootPath); err == nil {
 170  			originalPath = realgorootPath
 171  		}
 172  		maybeInMoxieRoot := false
 173  		for prefix := range pathsToOverride(p.config.GoMinorVersion, needsSyscallPackage(p.config.BuildTags())) {
 174  			if runtime.GOOS == "windows" {
 175  				prefix = strings.ReplaceAll(prefix, "/", "\\")
 176  			}
 177  			if !strings.HasPrefix(relpath, prefix) {
 178  				continue
 179  			}
 180  			maybeInMoxieRoot = true
 181  		}
 182  		if maybeInMoxieRoot {
 183  			moxiePath := filepath.Join(goenv.Get("MOXIEROOT"), "src", relpath)
 184  			if _, err := os.Stat(moxiePath); err == nil {
 185  				originalPath = moxiePath
 186  			}
 187  		}
 188  	}
 189  	return originalPath
 190  }
 191  
 192  // Sorted returns a list of all packages, sorted in a way that no packages come
 193  // before the packages they depend upon.
 194  func (p *Program) Sorted() []*Package {
 195  	return p.sorted
 196  }
 197  
 198  // MainPkg returns the last package in the Sorted() slice. This is the main
 199  // package of the program.
 200  func (p *Program) MainPkg() *Package {
 201  	return p.sorted[len(p.sorted)-1]
 202  }
 203  
 204  // Parse parses all packages and typechecks them.
 205  //
 206  // The returned error may be an Errors error, which contains a list of errors.
 207  //
 208  // Idempotent.
 209  func (p *Program) Parse() error {
 210  	// Parse all packages.
 211  	// TODO: do this in parallel.
 212  	for _, pkg := range p.sorted {
 213  		err := pkg.Parse()
 214  		if err != nil {
 215  			return err
 216  		}
 217  	}
 218  
 219  	// spawn is a true builtin (patched into go/types universe scope),
 220  	// no injection needed — available in all packages like make/append.
 221  
 222  	// Moxie AST rewrite: string literals → []byte() in user and moxie-pure packages.
 223  	for _, pkg := range p.sorted {
 224  		if isMoxieStringTarget(pkg.ImportPath) {
 225  			for _, file := range pkg.Files {
 226  				rewriteStringLiterals(file)
 227  			}
 228  		}
 229  	}
 230  
 231  	// Typecheck all packages.
 232  	for _, pkg := range p.sorted {
 233  		err := pkg.Check()
 234  		if err != nil {
 235  			return err
 236  		}
 237  	}
 238  
 239  	return nil
 240  }
 241  
 242  // OriginalDir returns the real directory name. It is the same as p.Dir except
 243  // that if it is part of the cached GOROOT, its real location is returned.
 244  func (p *Package) OriginalDir() string {
 245  	return strings.TrimSuffix(p.program.getOriginalPath(p.Dir+string(os.PathSeparator)), string(os.PathSeparator))
 246  }
 247  
 248  // parseFile is a wrapper around parser.ParseFile.
 249  func (p *Package) parseFile(path string, mode parser.Mode) (*ast.File, error) {
 250  	originalPath := p.program.getOriginalPath(path)
 251  	data, err := os.ReadFile(path)
 252  	if err != nil {
 253  		return nil, err
 254  	}
 255  	sum := sha512.Sum512_224(data)
 256  	p.FileHashes[originalPath] = sum[:]
 257  
 258  	// Moxie text-level rewrites before parsing. Fire on ALL packages —
 259  	// the new syntax (chan T{}, []T{:n}) only exists in converted files,
 260  	// unconverted files pass through unchanged. The chan struct{} guard
 261  	// prevents false positives.
 262  	data = rewriteChanLiterals(data, p.program.fset)
 263  	data = rewriteSliceLiterals(data, p.program.fset)
 264  
 265  	return parser.ParseFile(p.program.fset, originalPath, data, mode)
 266  }
 267  
 268  // Parse parses and typechecks this package.
 269  //
 270  // Idempotent.
 271  func (p *Package) Parse() error {
 272  	if len(p.Files) != 0 {
 273  		return nil // nothing to do (?)
 274  	}
 275  
 276  	// Load the AST.
 277  	if p.ImportPath == "unsafe" {
 278  		// Special case for the unsafe package, which is defined internally by
 279  		// the types package.
 280  		p.Pkg = types.Unsafe
 281  		return nil
 282  	}
 283  
 284  	files, err := p.parseFiles()
 285  	if err != nil {
 286  		return err
 287  	}
 288  	p.Files = files
 289  
 290  	return nil
 291  }
 292  
 293  // Check runs the package through the typechecker. The package must already be
 294  // loaded and all dependencies must have been checked already.
 295  //
 296  // Idempotent.
 297  func (p *Package) Check() error {
 298  	if p.Pkg != nil {
 299  		return nil // already typechecked
 300  	}
 301  
 302  	// Prepare some state used during type checking.
 303  	var typeErrors []error
 304  	checker := p.program.typeChecker // make a copy, because it will be modified
 305  	checker.Error = func(err error) {
 306  		typeErrors = append(typeErrors, err)
 307  	}
 308  	checker.Importer = p
 309  	if p.Module.GoVersion != "" {
 310  		// Setting the Go version for a module makes sure the type checker
 311  		// errors out on language features not supported in that particular
 312  		// version.
 313  		checker.GoVersion = "go" + p.Module.GoVersion
 314  	} else {
 315  		// Version is not known, so use the currently installed Go version.
 316  		// This is needed for `moxie run` for example.
 317  		// Normally we'd use goenv.GorootVersionString(), but for compatibility
 318  		// with Go 1.20 and below we need a version in the form of "go1.12" (no
 319  		// patch version).
 320  		major, minor, err := goenv.GetGorootVersion()
 321  		if err != nil {
 322  			return err
 323  		}
 324  		checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
 325  	}
 326  	initFileVersions(&p.info)
 327  
 328  	// Do typechecking of the package.
 329  	packageName := p.ImportPath
 330  	if p == p.program.MainPkg() {
 331  		if p.Name != "main" {
 332  			return Errors{p, []error{
 333  				scanner.Error{
 334  					Pos: p.program.fset.Position(p.Files[0].Name.Pos()),
 335  					Msg: fmt.Sprintf("expected main package to have name \"main\", not %#v", p.Name),
 336  				},
 337  			}}
 338  		}
 339  		packageName = "main"
 340  	}
 341  	typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info)
 342  
 343  
 344  	// Two-pass Moxie rewrite: pipe concat (|), string/[]byte + concat, comparisons, switches.
 345  	// Always run for Moxie target packages — the patched type checker accepts string+
 346  	// without errors, but the restriction checker still forbids it, so we must rewrite.
 347  	if isMoxieStringTarget(p.ImportPath) {
 348  		// Reject + on text in user packages before rewriting.
 349  		if p.Module.Main {
 350  			if plusErrs := checkPlusOnText(p.Files, &p.info, p.program.fset); len(plusErrs) > 0 {
 351  				return Errors{p, plusErrs}
 352  			}
 353  		}
 354  		pipeRewrites := findPipeConcat(p.Files, &p.info)
 355  		cmpExprs := findByteComparisons(p.Files, &p.info)
 356  		byteSwitches := findByteSwitches(p.Files, &p.info)
 357  		addAssignCount := rewriteAddAssign(p.Files, &p.info)
 358  		// __moxie_secalloc refs come from the text-level slice literal
 359  		// rewriter (`[]byte{:n, secure}`), which has already run before
 360  		// parse. They need the same builtin injection as the AST-level
 361  		// rewrites below.
 362  		secallocRefs := hasMoxieSecallocRefs(p.Files)
 363  
 364  		if len(pipeRewrites) > 0 || len(cmpExprs) > 0 || len(byteSwitches) > 0 || addAssignCount > 0 || secallocRefs {
 365  			applyPipeRewrites(p.Files, pipeRewrites)
 366  			applyByteComparisonRewrites(p.Files, cmpExprs)
 367  			applyByteSwitchRewrites(byteSwitches)
 368  			typeErrors = filterPipeErrors(typeErrors)
 369  			typeErrors = filterByteCompareErrors(typeErrors)
 370  
 371  			// Inject __moxie_concat/eq/lt declarations into this package.
 372  			if f := injectMoxieByteBuiltins(p.program.fset, p.Name); f != nil {
 373  				p.Files = append(p.Files, f)
 374  			}
 375  
 376  			// Reset type info and re-check.
 377  			p.info = types.Info{
 378  				Types:      make(map[ast.Expr]types.TypeAndValue),
 379  				Instances:  make(map[*ast.Ident]types.Instance),
 380  				Defs:       make(map[*ast.Ident]types.Object),
 381  				Uses:       make(map[*ast.Ident]types.Object),
 382  				Implicits:  make(map[ast.Node]types.Object),
 383  				Scopes:     make(map[ast.Node]*types.Scope),
 384  				Selections: make(map[*ast.SelectorExpr]*types.Selection),
 385  			}
 386  			initFileVersions(&p.info)
 387  			typeErrors = nil
 388  			checker2 := p.program.typeChecker
 389  			checker2.Error = func(e error) {
 390  				typeErrors = append(typeErrors, e)
 391  			}
 392  			checker2.Importer = p
 393  			if p.Module.GoVersion != "" {
 394  				checker2.GoVersion = "go" + p.Module.GoVersion
 395  			} else {
 396  				major, minor, verr := goenv.GetGorootVersion()
 397  				if verr != nil {
 398  					return verr
 399  				}
 400  				checker2.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
 401  			}
 402  			typesPkg, err = checker2.Check(packageName, p.program.fset, p.Files, &p.info)
 403  		}
 404  	}
 405  
 406  	if err != nil {
 407  		if err, ok := err.(Errors); ok {
 408  			return err
 409  		}
 410  		if len(typeErrors) != 0 {
 411  			// Got type errors, so return them.
 412  			return Errors{p, typeErrors}
 413  		}
 414  		// This can happen in some weird cases.
 415  		// The only case I know is when compiling a Go 1.23 program, with a
 416  		// Moxie version that supports Go 1.23 but is compiled using Go 1.22.
 417  		// So this should be pretty rare.
 418  		return Errors{p, []error{err}}
 419  	}
 420  	p.Pkg = typesPkg
 421  
 422  	p.extractEmbedLines(checker.Error)
 423  	if len(typeErrors) != 0 {
 424  		return Errors{p, typeErrors}
 425  	}
 426  
 427  	return nil
 428  }
 429  
 430  // parseFiles parses the loaded list of files and returns this list.
 431  func (p *Package) parseFiles() ([]*ast.File, error) {
 432  	var files []*ast.File
 433  	var fileErrs []error
 434  
 435  	// Parse all files (including CgoFiles).
 436  	parseFile := func(file string) {
 437  		if !filepath.IsAbs(file) {
 438  			file = filepath.Join(p.Dir, file)
 439  		}
 440  		f, err := p.parseFile(file, parser.ParseComments)
 441  		if err != nil {
 442  			fileErrs = append(fileErrs, err)
 443  			return
 444  		}
 445  		files = append(files, f)
 446  	}
 447  	for _, file := range p.GoFiles {
 448  		parseFile(file)
 449  	}
 450  	for _, file := range p.CgoFiles {
 451  		parseFile(file)
 452  	}
 453  
 454  	// Do CGo processing.
 455  	// This is done when there are any CgoFiles at all. In that case, len(files)
 456  	// should be non-zero. However, if len(MxFiles) == 0 and len(CgoFiles) == 1
 457  	// and there is a syntax error in a CGo file, len(files) may be 0. Don't try
 458  	// to call cgo.Process in that case as it will only cause issues.
 459  	if len(p.CgoFiles) != 0 && len(files) != 0 {
 460  		var initialCFlags []string
 461  		initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
 462  		initialCFlags = append(initialCFlags, "-I"+p.Dir)
 463  		generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS())
 464  		p.CFlags = append(initialCFlags, cflags...)
 465  		p.CGoHeaders = headerCode
 466  		for path, hash := range accessedFiles {
 467  			p.FileHashes[path] = hash
 468  		}
 469  		if errs != nil {
 470  			fileErrs = append(fileErrs, errs...)
 471  		}
 472  		files = append(files, generated...)
 473  		p.program.LDFlags = append(p.program.LDFlags, ldflags...)
 474  	}
 475  
 476  	// Only return an error after CGo processing, so that errors in parsing and
 477  	// CGo can be reported together.
 478  	if len(fileErrs) != 0 {
 479  		return nil, Errors{p, fileErrs}
 480  	}
 481  
 482  	return files, nil
 483  }
 484  
 485  // extractEmbedLines finds all //go:embed lines in the package and matches them
 486  // against EmbedFiles from `moxie list`.
 487  func (p *Package) extractEmbedLines(addError func(error)) {
 488  	for _, file := range p.Files {
 489  		// Check for an `import "embed"` line at the start of the file.
 490  		// //go:embed lines are only valid if the given file itself imports the
 491  		// embed package. It is not valid if it is only imported in a separate
 492  		// Go file.
 493  		hasEmbed := false
 494  		for _, importSpec := range file.Imports {
 495  			if importSpec.Path.Value == `"embed"` {
 496  				hasEmbed = true
 497  			}
 498  		}
 499  
 500  		for _, decl := range file.Decls {
 501  			switch decl := decl.(type) {
 502  			case *ast.GenDecl:
 503  				if decl.Tok != token.VAR {
 504  					continue
 505  				}
 506  				for _, spec := range decl.Specs {
 507  					spec := spec.(*ast.ValueSpec)
 508  					var doc *ast.CommentGroup
 509  					if decl.Lparen == token.NoPos {
 510  						// Plain 'var' declaration, like:
 511  						//   //go:embed hello.txt
 512  						//   var hello string
 513  						doc = decl.Doc
 514  					} else {
 515  						// Bigger 'var' declaration like:
 516  						//   var (
 517  						//       //go:embed hello.txt
 518  						//       hello string
 519  						//   )
 520  						doc = spec.Doc
 521  					}
 522  					if doc == nil {
 523  						continue
 524  					}
 525  
 526  					// Look for //go:embed comments.
 527  					var allPatterns []string
 528  					for _, comment := range doc.List {
 529  						if comment.Text != "//go:embed" && !strings.HasPrefix(comment.Text, "//go:embed ") {
 530  							continue
 531  						}
 532  						if !hasEmbed {
 533  							addError(types.Error{
 534  								Fset: p.program.fset,
 535  								Pos:  comment.Pos() + 2,
 536  								Msg:  "//go:embed only allowed in Go files that import \"embed\"",
 537  							})
 538  							// Continue, because otherwise we might run into
 539  							// issues below.
 540  							continue
 541  						}
 542  						patterns, err := p.parseGoEmbed(comment.Text[len("//go:embed"):], comment.Slash)
 543  						if err != nil {
 544  							addError(err)
 545  							continue
 546  						}
 547  						if len(patterns) == 0 {
 548  							addError(types.Error{
 549  								Fset: p.program.fset,
 550  								Pos:  comment.Pos() + 2,
 551  								Msg:  "usage: //go:embed pattern...",
 552  							})
 553  							continue
 554  						}
 555  						for _, pattern := range patterns {
 556  							// Check that the pattern is well-formed.
 557  							// It must be valid: the Go toolchain has already
 558  							// checked for invalid patterns. But let's check
 559  							// anyway to be sure.
 560  							if _, err := path.Match(pattern, ""); err != nil {
 561  								addError(types.Error{
 562  									Fset: p.program.fset,
 563  									Pos:  comment.Pos(),
 564  									Msg:  "invalid pattern syntax",
 565  								})
 566  								continue
 567  							}
 568  							allPatterns = append(allPatterns, pattern)
 569  						}
 570  					}
 571  
 572  					if len(allPatterns) != 0 {
 573  						// This is a //go:embed global. Do a few more checks.
 574  						if len(spec.Names) != 1 {
 575  							addError(types.Error{
 576  								Fset: p.program.fset,
 577  								Pos:  spec.Names[1].NamePos,
 578  								Msg:  "//go:embed cannot apply to multiple vars",
 579  							})
 580  						}
 581  						if spec.Values != nil {
 582  							addError(types.Error{
 583  								Fset: p.program.fset,
 584  								Pos:  spec.Values[0].Pos(),
 585  								Msg:  "//go:embed cannot apply to var with initializer",
 586  							})
 587  						}
 588  						globalName := spec.Names[0].Name
 589  						globalType := p.Pkg.Scope().Lookup(globalName).Type()
 590  						valid, byteSlice := isValidEmbedType(globalType)
 591  						if !valid {
 592  							addError(types.Error{
 593  								Fset: p.program.fset,
 594  								Pos:  spec.Type.Pos(),
 595  								Msg:  "//go:embed cannot apply to var of type " + globalType.String(),
 596  							})
 597  						}
 598  
 599  						// Match all //go:embed patterns against the embed files
 600  						// provided by `go list`.
 601  						for _, name := range p.EmbedFiles {
 602  							for _, pattern := range allPatterns {
 603  								if matchPattern(pattern, name) {
 604  									p.EmbedGlobals[globalName] = append(p.EmbedGlobals[globalName], &EmbedFile{
 605  										Name:      name,
 606  										NeedsData: byteSlice,
 607  									})
 608  									break
 609  								}
 610  							}
 611  						}
 612  					}
 613  				}
 614  			}
 615  		}
 616  	}
 617  }
 618  
 619  // matchPattern returns true if (and only if) the given pattern would match the
 620  // filename. The pattern could also match a parent directory of name, in which
 621  // case hidden files do not match.
 622  func matchPattern(pattern, name string) bool {
 623  	// Match this file.
 624  	matched, _ := path.Match(pattern, name)
 625  	if matched {
 626  		return true
 627  	}
 628  
 629  	// Match parent directories.
 630  	dir := name
 631  	for {
 632  		dir, _ = path.Split(dir)
 633  		if dir == "" {
 634  			return false
 635  		}
 636  		dir = path.Clean(dir)
 637  		if matched, _ := path.Match(pattern, dir); matched {
 638  			// Pattern matches the directory.
 639  			suffix := name[len(dir):]
 640  			if strings.Contains(suffix, "/_") || strings.Contains(suffix, "/.") {
 641  				// Pattern matches a hidden file.
 642  				// Hidden files are included when listed directly as a
 643  				// pattern, but not when they are part of a directory tree.
 644  				// Source:
 645  				// > If a pattern names a directory, all files in the
 646  				// > subtree rooted at that directory are embedded
 647  				// > (recursively), except that files with names beginning
 648  				// > with ‘.’ or ‘_’ are excluded.
 649  				return false
 650  			}
 651  			return true
 652  		}
 653  	}
 654  }
 655  
 656  // parseGoEmbed is like strings.Fields but for a //go:embed line. It parses
 657  // regular fields and quoted fields (that may contain spaces).
 658  func (p *Package) parseGoEmbed(args string, pos token.Pos) (patterns []string, err error) {
 659  	args = strings.TrimSpace(args)
 660  	initialLen := len(args)
 661  	for args != "" {
 662  		patternPos := pos + token.Pos(initialLen-len(args))
 663  		switch args[0] {
 664  		case '`', '"', '\\':
 665  			// Parse the next pattern using the Go scanner.
 666  			// This is perhaps a bit overkill, but it does correctly implement
 667  			// parsing of the various Go strings.
 668  			var sc scanner.Scanner
 669  			fset := &token.FileSet{}
 670  			file := fset.AddFile("", 0, len(args))
 671  			sc.Init(file, []byte(args), nil, 0)
 672  			_, tok, lit := sc.Scan()
 673  			if tok != token.STRING || sc.ErrorCount != 0 {
 674  				// Calculate start of token
 675  				return nil, types.Error{
 676  					Fset: p.program.fset,
 677  					Pos:  patternPos,
 678  					Msg:  "invalid quoted string in //go:embed",
 679  				}
 680  			}
 681  			pattern := constant.StringVal(constant.MakeFromLiteral(lit, tok, 0))
 682  			patterns = append(patterns, pattern)
 683  			args = strings.TrimLeftFunc(args[len(lit):], unicode.IsSpace)
 684  		default:
 685  			// The value is just a regular value.
 686  			// Split it at the first white space.
 687  			index := strings.IndexFunc(args, unicode.IsSpace)
 688  			if index < 0 {
 689  				index = len(args)
 690  			}
 691  			pattern := args[:index]
 692  			patterns = append(patterns, pattern)
 693  			args = strings.TrimLeftFunc(args[len(pattern):], unicode.IsSpace)
 694  		}
 695  		if _, err := path.Match(patterns[len(patterns)-1], ""); err != nil {
 696  			return nil, types.Error{
 697  				Fset: p.program.fset,
 698  				Pos:  patternPos,
 699  				Msg:  "invalid pattern syntax",
 700  			}
 701  		}
 702  	}
 703  	return patterns, nil
 704  }
 705  
 706  // isValidEmbedType returns whether the given Go type can be used as a
 707  // //go:embed type. This is only true for embed.FS, strings, and byte slices.
 708  // The second return value indicates that this is a byte slice, and therefore
 709  // the contents of the file needs to be passed to the compiler.
 710  func isValidEmbedType(typ types.Type) (valid, byteSlice bool) {
 711  	if typ.Underlying() == types.Typ[types.String] {
 712  		// string type
 713  		return true, false
 714  	}
 715  	if sliceType, ok := typ.Underlying().(*types.Slice); ok {
 716  		if elemType, ok := sliceType.Elem().Underlying().(*types.Basic); ok && elemType.Kind() == types.Byte {
 717  			// byte slice type
 718  			return true, true
 719  		}
 720  	}
 721  	if namedType, ok := typ.(*types.Named); ok && namedType.String() == "embed.FS" {
 722  		// embed.FS type
 723  		return true, false
 724  	}
 725  	return false, false
 726  }
 727  
 728  // Import implements types.Importer. It loads and parses packages it encounters
 729  // along the way, if needed.
 730  func (p *Package) Import(to string) (*types.Package, error) {
 731  	if to == "unsafe" {
 732  		return types.Unsafe, nil
 733  	}
 734  	if newTo, ok := p.ImportMap[to]; ok && !strings.HasSuffix(newTo, ".test]") {
 735  		to = newTo
 736  	}
 737  	if imported, ok := p.program.Packages[to]; ok {
 738  		return imported.Pkg, nil
 739  	} else {
 740  		return nil, errors.New("package not imported: " + to)
 741  	}
 742  }
 743