github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/loader/loader.go (about)

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