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