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