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