| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package inline | |
| import ( | |
| "fmt" | |
| "go/ast" | |
| "go/types" | |
| "slices" | |
| "strings" | |
| _ "embed" | |
| "golang.org/x/tools/go/analysis" | |
| "golang.org/x/tools/go/analysis/passes/inspect" | |
| "golang.org/x/tools/go/analysis/passes/internal/gofixdirective" | |
| "golang.org/x/tools/go/ast/edge" | |
| "golang.org/x/tools/go/ast/inspector" | |
| "golang.org/x/tools/go/types/typeutil" | |
| "golang.org/x/tools/internal/analysis/analyzerutil" | |
| typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex" | |
| "golang.org/x/tools/internal/astutil" | |
| "golang.org/x/tools/internal/moreiters" | |
| "golang.org/x/tools/internal/packagepath" | |
| "golang.org/x/tools/internal/refactor" | |
| "golang.org/x/tools/internal/refactor/inline" | |
| "golang.org/x/tools/internal/typesinternal" | |
| "golang.org/x/tools/internal/typesinternal/typeindex" | |
| ) | |
| //go:embed doc.go | |
| var doc string | |
| var Analyzer = &analysis.Analyzer{ | |
| Name: "inline", | |
| Doc: analyzerutil.MustExtractDoc(doc, "inline"), | |
| URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline", | |
| Run: run, | |
| FactTypes: []analysis.Fact{ | |
| (*goFixInlineFuncFact)(nil), | |
| (*goFixInlineConstFact)(nil), | |
| (*goFixInlineAliasFact)(nil), | |
| }, | |
| Requires: []*analysis.Analyzer{ | |
| inspect.Analyzer, | |
| typeindexanalyzer.Analyzer, | |
| }, | |
| } | |
| var ( | |
| allowBindingDecl bool | |
| lazyEdits bool | |
| ) | |
| func init() { | |
| Analyzer.Flags.BoolVar(&allowBindingDecl, "allow_binding_decl", false, | |
| "permit inlinings that require a 'var params = args' declaration") | |
| Analyzer.Flags.BoolVar(&lazyEdits, "lazy_edits", false, | |
| "compute edits lazily (only meaningful to gopls driver)") | |
| } | |
| // analyzer holds the state for this analysis. | |
| type analyzer struct { | |
| pass *analysis.Pass | |
| root inspector.Cursor | |
| index *typeindex.Index | |
| // memoization of repeated calls for same file. | |
| fileContent map[string][]byte | |
| // memoization of fact imports (nil => no fact) | |
| inlinableFuncs map[*types.Func]*inline.Callee | |
| inlinableConsts map[*types.Const]*goFixInlineConstFact | |
| inlinableAliases map[*types.TypeName]*goFixInlineAliasFact | |
| } | |
| func run(pass *analysis.Pass) (any, error) { | |
| a := &analyzer{ | |
| pass: pass, | |
| root: pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root(), | |
| index: pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index), | |
| fileContent: make(map[string][]byte), | |
| inlinableFuncs: make(map[*types.Func]*inline.Callee), | |
| inlinableConsts: make(map[*types.Const]*goFixInlineConstFact), | |
| inlinableAliases: make(map[*types.TypeName]*goFixInlineAliasFact), | |
| } | |
| gofixdirective.Find(pass, a.root, a) | |
| a.inline() | |
| return nil, nil | |
| } | |
| // HandleFunc exports a fact for functions marked with go:fix. | |
| func (a *analyzer) HandleFunc(decl *ast.FuncDecl) { | |
| content, err := a.readFile(decl) | |
| if err != nil { | |
| a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err) | |
| return | |
| } | |
| callee, err := inline.AnalyzeCallee(discard, a.pass.Fset, a.pass.Pkg, a.pass.TypesInfo, decl, content) | |
| if err != nil { | |
| a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err) | |
| return | |
| } | |
| fn := a.pass.TypesInfo.Defs[decl.Name].(*types.Func) | |
| a.pass.ExportObjectFact(fn, &goFixInlineFuncFact{callee}) | |
| a.inlinableFuncs[fn] = callee | |
| } | |
| // HandleAlias exports a fact for aliases marked with go:fix. | |
| func (a *analyzer) HandleAlias(spec *ast.TypeSpec) { | |
| // Remember that this is an inlinable alias. | |
| typ := &goFixInlineAliasFact{} | |
| lhs := a.pass.TypesInfo.Defs[spec.Name].(*types.TypeName) | |
| a.inlinableAliases[lhs] = typ | |
| // Create a fact only if the LHS is exported and defined at top level. | |
| // We create a fact even if the RHS is non-exported, | |
| // so we can warn about uses in other packages. | |
| if lhs.Exported() && typesinternal.IsPackageLevel(lhs) { | |
| a.pass.ExportObjectFact(lhs, typ) | |
| } | |
| } | |
| // HandleConst exports a fact for constants marked with go:fix. | |
| func (a *analyzer) HandleConst(nameIdent, rhsIdent *ast.Ident) { | |
| lhs := a.pass.TypesInfo.Defs[nameIdent].(*types.Const) | |
| rhs := a.pass.TypesInfo.Uses[rhsIdent].(*types.Const) // must be so in a well-typed program | |
| con := &goFixInlineConstFact{ | |
| RHSName: rhs.Name(), | |
| RHSPkgName: rhs.Pkg().Name(), | |
| RHSPkgPath: rhs.Pkg().Path(), | |
| } | |
| if rhs.Pkg() == a.pass.Pkg { | |
| con.rhsObj = rhs | |
| } | |
| a.inlinableConsts[lhs] = con | |
| // Create a fact only if the LHS is exported and defined at top level. | |
| // We create a fact even if the RHS is non-exported, | |
| // so we can warn about uses in other packages. | |
| if lhs.Exported() && typesinternal.IsPackageLevel(lhs) { | |
| a.pass.ExportObjectFact(lhs, con) | |
| } | |
| } | |
| // inline inlines each static call to an inlinable function | |
| // and each reference to an inlinable constant or type alias. | |
| func (a *analyzer) inline() { | |
| for cur := range a.root.Preorder((*ast.CallExpr)(nil), (*ast.Ident)(nil)) { | |
| switch n := cur.Node().(type) { | |
| case *ast.CallExpr: | |
| a.inlineCall(n, cur) | |
| case *ast.Ident: | |
| switch obj := a.pass.TypesInfo.Uses[n].(type) { | |
| case *types.TypeName: | |
| a.inlineAlias(obj, cur) | |
| case *types.Const: | |
| a.inlineConst(obj, cur) | |
| } | |
| } | |
| } | |
| } | |
| // If call is a call to an inlinable func, suggest inlining its use at cur. | |
| func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) { | |
| if fn := typeutil.StaticCallee(a.pass.TypesInfo, call); fn != nil { | |
| // Inlinable? | |
| callee, ok := a.inlinableFuncs[fn] | |
| if !ok { | |
| var fact goFixInlineFuncFact | |
| if a.pass.ImportObjectFact(fn, &fact) { | |
| callee = fact.Callee | |
| a.inlinableFuncs[fn] = callee | |
| } | |
| } | |
| if callee == nil { | |
| return // nope | |
| } | |
| if a.withinTestOf(cur, fn) { | |
| return // don't inline a function from within its own test | |
| } | |
| // Compute the edits. | |
| // | |
| // Ordinarily the analyzer reports a fix containing | |
| // edits. However, the algorithm is somewhat expensive | |
| // (unnecessarily so: see go.dev/issue/75773) so | |
| // to reduce costs in gopls, we omit the edits, | |
| // meaning that gopls must compute them on demand | |
| // (based on the Diagnostic.Category) when they are | |
| // requested via a code action. | |
| // | |
| // This does mean that the following categories of | |
| // caller-dependent obstacles to inlining will be | |
| // reported when the gopls user requests the fix, | |
| // rather than by quietly suppressing the diagnostic: | |
| // - shadowing problems | |
| // - callee imports inaccessible "internal" packages | |
| // - callee refers to nonexported symbols | |
| // - callee uses too-new Go features | |
| // - inlining call from a cgo file | |
| var edits []analysis.TextEdit | |
| if !lazyEdits { | |
| // Inline the call. | |
| caller := &inline.Caller{ | |
| Fset: a.pass.Fset, | |
| Types: a.pass.Pkg, | |
| Info: a.pass.TypesInfo, | |
| Call: cur, | |
| CountUses: func(pkgname *types.PkgName) int { | |
| return moreiters.Len(a.index.Uses(pkgname)) | |
| }, | |
| } | |
| res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard}) | |
| if err != nil { | |
| a.pass.Reportf(call.Lparen, "%v", err) | |
| return | |
| } | |
| if res.Literalized { | |
| // Users are not fond of inlinings that literalize | |
| // f(x) to func() { ... }(), so avoid them. | |
| // | |
| // (Unfortunately the inliner is very timid, | |
| // and often literalizes when it cannot prove that | |
| // reducing the call is safe; the user of this tool | |
| // has no indication of what the problem is.) | |
| return | |
| } | |
| if res.BindingDecl && !allowBindingDecl { | |
| // When applying fix en masse, users are similarly | |
| // unenthusiastic about inlinings that cannot | |
| // entirely eliminate the parameters and | |
| // insert a 'var params = args' declaration. | |
| // The flag allows them to decline such fixes. | |
| return | |
| } | |
| edits = res.Edits | |
| } | |
| a.pass.Report(analysis.Diagnostic{ | |
| Pos: call.Pos(), | |
| End: call.End(), | |
| Message: fmt.Sprintf("Call of %v should be inlined", callee), | |
| Category: "inline_call", // keep consistent with gopls/internal/golang.fixInlineCall | |
| SuggestedFixes: []analysis.SuggestedFix{{ | |
| Message: fmt.Sprintf("Inline call of %v", callee), | |
| TextEdits: edits, // within gopls, this is nil => compute fix's edits lazily | |
| }}, | |
| }) | |
| } | |
| } | |
| // withinTestOf reports whether curUse is within a dedicated test | |
| // function for the inlinable target symbol. | |
| // A call within its dedicated test should not be inlined. | |
| func (a *analyzer) withinTestOf(curUse inspector.Cursor, target types.Object) bool { | |
| // x_test.go -> x | |
| useFileBase, isTest := strings.CutSuffix(a.pass.Fset.File(curUse.Node().Pos()).Name(), "_test.go") | |
| if !isTest { | |
| return false // not a test file | |
| } | |
| // Suppress fixes for uses in x_test.go of target symbol defined in x.go (#79272). | |
| if useFileBase == strings.TrimSuffix(a.pass.Fset.File(target.Pos()).Name(), ".go") { | |
| return true | |
| } | |
| curFuncDecl, ok := moreiters.First(curUse.Enclosing((*ast.FuncDecl)(nil))) | |
| if !ok { | |
| return false // not in a function | |
| } | |
| funcDecl := curFuncDecl.Node().(*ast.FuncDecl) | |
| if funcDecl.Recv != nil { | |
| return false // not a test func | |
| } | |
| if strings.TrimSuffix(a.pass.Pkg.Path(), "_test") != target.Pkg().Path() { | |
| return false // different package | |
| } | |
| // Computed expected SYMBOL portion of "ExampleSYMBOL_comment" | |
| // for the target symbol. (Strictly, this convention applies | |
| // only to Example functions.) | |
| // TODO(adonovan): use a proper Test function parser. | |
| symbol := target.Name() | |
| if fn, ok := target.(*types.Func); ok { | |
| if _, named := typesinternal.RecvBase(fn); named != nil { | |
| symbol = named.Obj().Name() + "_" + symbol | |
| } | |
| } | |
| fname := funcDecl.Name.Name | |
| for _, pre := range []string{"Test", "Example", "Bench", "Fuzz"} { | |
| if fname == pre+symbol || strings.HasPrefix(fname, pre+symbol+"_") { | |
| return true // use of X within TestX | |
| } | |
| } | |
| return false | |
| } | |
| // If tn is the TypeName of an inlinable alias, suggest inlining its use at cur. | |
| func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) { | |
| inalias, ok := a.inlinableAliases[tn] | |
| if !ok { | |
| var fact goFixInlineAliasFact | |
| if a.pass.ImportObjectFact(tn, &fact) { | |
| inalias = &fact | |
| a.inlinableAliases[tn] = inalias | |
| } | |
| } | |
| if inalias == nil { | |
| return // nope | |
| } | |
| if a.withinTestOf(curId, tn) { | |
| return // don't inline a type alias from within its own test | |
| } | |
| alias := tn.Type().(*types.Alias) | |
| // Remember the names of the alias's type params. When we check for shadowing | |
| // later, we'll ignore these because they won't appear in the replacement text. | |
| typeParamNames := map[*types.TypeName]bool{} | |
| for tp := range alias.TypeParams().TypeParams() { | |
| typeParamNames[tp.Obj()] = true | |
| } | |
| rhs := alias.Rhs() | |
| curPath := a.pass.Pkg.Path() | |
| curFile := astutil.EnclosingFile(curId) | |
| id := curId.Node().(*ast.Ident) | |
| // Find the complete identifier, which may take any of these forms: | |
| // Id | |
| // Id[T] | |
| // Id[K, V] | |
| // pkg.Id | |
| // pkg.Id[T] | |
| // pkg.Id[K, V] | |
| var expr ast.Expr = id | |
| if curId.ParentEdgeKind() == edge.SelectorExpr_Sel { | |
| curId = curId.Parent() | |
| expr = curId.Node().(ast.Expr) | |
| } | |
| // If expr is part of an IndexExpr or IndexListExpr, we'll need that node. | |
| // Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated. | |
| switch curId.ParentEdgeKind() { | |
| case edge.IndexExpr_X: | |
| curId = curId.Parent() | |
| expr = curId.Node().(*ast.IndexExpr) | |
| case edge.IndexListExpr_X: | |
| curId = curId.Parent() | |
| expr = curId.Node().(*ast.IndexListExpr) | |
| } | |
| // Reject inlining of a type alias used to declare an embedded | |
| // struct field if doing so would change the field's name. | |
| if v, ok := a.pass.TypesInfo.Defs[id].(*types.Var); ok && v.Embedded() { | |
| identicalName := false | |
| // TODO(adonovan): should we allow a pointer (type A = *pkg.A)? | |
| if rhs, ok := alias.Rhs().(*types.Named); ok { | |
| identicalName = alias.Obj().Name() == rhs.Obj().Name() | |
| } | |
| if !identicalName { | |
| // Type is embedded, inlining the alias will cause | |
| // the field name to be changed, which might break | |
| // programs in terms of backwards compatibility. | |
| return | |
| } | |
| } | |
| t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier | |
| if targs := t.TypeArgs(); targs.Len() > 0 { | |
| // Instantiate the alias with the type args from this use. | |
| // For example, given type A = M[K, V], compute the type of the use | |
| // A[int, Foo] as M[int, Foo]. | |
| // Don't validate instantiation: it can't panic unless we have a bug, | |
| // in which case seeing the stack trace via telemetry would be helpful. | |
| instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false) | |
| rhs = instAlias.(*types.Alias).Rhs() | |
| } | |
| // We have an identifier A here (n), possibly qualified by a package | |
| // identifier (sel.n), and an inlinable "type A = rhs" elsewhere. | |
| // | |
| // We can replace A with rhs if no name in rhs is shadowed at n's position, | |
| // and every package in rhs is importable by the current package. | |
| var ( | |
| importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix | |
| edits []analysis.TextEdit | |
| ) | |
| for _, tn := range typenames(rhs) { | |
| // Ignore the type parameters of the alias: they won't appear in the result. | |
| if typeParamNames[tn] { | |
| continue | |
| } | |
| var pkgPath, pkgName string | |
| if pkg := tn.Pkg(); pkg != nil { | |
| pkgPath = pkg.Path() | |
| pkgName = pkg.Name() | |
| } | |
| if pkgPath == "" || pkgPath == curPath { | |
| // The name is in the current package or the universe scope, so no import | |
| // is required. Check that it is not shadowed (that is, that the type | |
| // it refers to in rhs is the same one it refers to at n). | |
| scope := a.pass.TypesInfo.Scopes[curFile].Innermost(id.Pos()) // n's scope | |
| _, obj := scope.LookupParent(tn.Name(), id.Pos()) // what qn.name means in n's scope | |
| if obj != tn { | |
| return | |
| } | |
| } else if !packagepath.CanImport(a.pass.Pkg.Path(), pkgPath) { | |
| // If this package can't see the package of this part of rhs, we can't inline. | |
| return | |
| } else if _, ok := importPrefixes[pkgPath]; !ok { | |
| // Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns | |
| // with the prefix it assigns | |
| // with the package path for use by the TypeString qualifier below. | |
| prefix, eds := refactor.AddImport( | |
| a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos()) | |
| importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".") | |
| edits = append(edits, eds...) | |
| } | |
| } | |
| // To get the replacement text, render the alias RHS using the package prefixes | |
| // we assigned above. | |
| newText := types.TypeString(rhs, func(p *types.Package) string { | |
| if p == a.pass.Pkg { | |
| return "" | |
| } | |
| if prefix, ok := importPrefixes[p.Path()]; ok { | |
| return prefix | |
| } | |
| panic(fmt.Sprintf("in %q, package path %q has no import prefix", rhs, p.Path())) | |
| }) | |
| a.reportInline("type alias", "Type alias", expr, edits, newText) | |
| } | |
| // typenames returns the TypeNames for types within t (including t itself) that have | |
| // them: basic types, named types and alias types. | |
| // The same name may appear more than once. | |
| func typenames(t types.Type) []*types.TypeName { | |
| var tns []*types.TypeName | |
| var visit func(types.Type) | |
| visit = func(t types.Type) { | |
| if hasName, ok := t.(interface{ Obj() *types.TypeName }); ok { | |
| tns = append(tns, hasName.Obj()) | |
| } | |
| switch t := t.(type) { | |
| case *types.Basic: | |
| tns = append(tns, types.Universe.Lookup(t.Name()).(*types.TypeName)) | |
| case *types.Named: | |
| for t := range t.TypeArgs().Types() { | |
| visit(t) | |
| } | |
| case *types.Alias: | |
| for t := range t.TypeArgs().Types() { | |
| visit(t) | |
| } | |
| case *types.TypeParam: | |
| tns = append(tns, t.Obj()) | |
| case *types.Pointer: | |
| visit(t.Elem()) | |
| case *types.Slice: | |
| visit(t.Elem()) | |
| case *types.Array: | |
| visit(t.Elem()) | |
| case *types.Chan: | |
| visit(t.Elem()) | |
| case *types.Map: | |
| visit(t.Key()) | |
| visit(t.Elem()) | |
| case *types.Struct: | |
| for field := range t.Fields() { | |
| visit(field.Type()) | |
| } | |
| case *types.Signature: | |
| // Ignore the receiver: although it may be present, it has no meaning | |
| // in a type expression. | |
| // Ditto for receiver type params. | |
| // Also, function type params cannot appear in a type expression. | |
| if t.TypeParams() != nil { | |
| panic("Signature.TypeParams in type expression") | |
| } | |
| visit(t.Params()) | |
| visit(t.Results()) | |
| case *types.Interface: | |
| for etyp := range t.EmbeddedTypes() { | |
| visit(etyp) | |
| } | |
| for method := range t.ExplicitMethods() { | |
| visit(method.Type()) | |
| } | |
| case *types.Tuple: | |
| for v := range t.Variables() { | |
| visit(v.Type()) | |
| } | |
| case *types.Union: | |
| panic("Union in type expression") | |
| default: | |
| panic(fmt.Sprintf("unknown type %T", t)) | |
| } | |
| } | |
| visit(t) | |
| return tns | |
| } | |
| // If con is an inlinable constant, suggest inlining its use at cur. | |
| func (a *analyzer) inlineConst(con *types.Const, cur inspector.Cursor) { | |
| incon, ok := a.inlinableConsts[con] | |
| if !ok { | |
| var fact goFixInlineConstFact | |
| if a.pass.ImportObjectFact(con, &fact) { | |
| incon = &fact | |
| a.inlinableConsts[con] = incon | |
| } | |
| } | |
| if incon == nil { | |
| return // nope | |
| } | |
| if a.withinTestOf(cur, con) { | |
| return // don't inline a type alias from within its own test | |
| } | |
| // If n is qualified by a package identifier, we'll need the full selector expression. | |
| curFile := astutil.EnclosingFile(cur) | |
| n := cur.Node().(*ast.Ident) | |
| // We have an identifier A here (n), possibly qualified by a package identifier (sel.X, | |
| // where sel is the parent of n), // and an inlinable "const A = B" elsewhere (incon). | |
| // Consider replacing A with B. | |
| // Check that the expression we are inlining (B) means the same thing | |
| // (refers to the same object) in n's scope as it does in A's scope. | |
| // If the RHS is not in the current package, AddImport will handle | |
| // shadowing, so we only need to worry about when both expressions | |
| // are in the current package. | |
| if a.pass.Pkg.Path() == incon.RHSPkgPath { | |
| // incon.rhsObj is the object referred to by B in the definition of A. | |
| scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope | |
| _, obj := scope.LookupParent(incon.RHSName, n.Pos()) // what "B" means in n's scope | |
| if obj == nil { | |
| // Should be impossible: if code at n can refer to the LHS, | |
| // it can refer to the RHS. | |
| panic(fmt.Sprintf("no object for inlinable const %s RHS %s", n.Name, incon.RHSName)) | |
| } | |
| if obj != incon.rhsObj { | |
| // "B" means something different here than at the inlinable const's scope. | |
| return | |
| } | |
| } else if !packagepath.CanImport(a.pass.Pkg.Path(), incon.RHSPkgPath) { | |
| // If this package can't see the RHS's package, we can't inline. | |
| return | |
| } | |
| var ( | |
| importPrefix string | |
| edits []analysis.TextEdit | |
| ) | |
| if incon.RHSPkgPath != a.pass.Pkg.Path() { | |
| importPrefix, edits = refactor.AddImport( | |
| a.pass.TypesInfo, curFile, incon.RHSPkgName, incon.RHSPkgPath, incon.RHSName, n.Pos()) | |
| } | |
| // If n is qualified by a package identifier, we'll need the full selector expression. | |
| var expr ast.Expr = n | |
| if cur.ParentEdgeKind() == edge.SelectorExpr_Sel { | |
| expr = cur.Parent().Node().(ast.Expr) | |
| } | |
| a.reportInline("constant", "Constant", expr, edits, importPrefix+incon.RHSName) | |
| } | |
| // reportInline reports a diagnostic for fixing an inlinable name. | |
| func (a *analyzer) reportInline(kind, capKind string, ident ast.Expr, edits []analysis.TextEdit, newText string) { | |
| edits = append(edits, analysis.TextEdit{ | |
| Pos: ident.Pos(), | |
| End: ident.End(), | |
| NewText: []byte(newText), | |
| }) | |
| name := astutil.Format(a.pass.Fset, ident) | |
| a.pass.Report(analysis.Diagnostic{ | |
| Pos: ident.Pos(), | |
| End: ident.End(), | |
| Message: fmt.Sprintf("%s %s should be inlined", capKind, name), | |
| SuggestedFixes: []analysis.SuggestedFix{{ | |
| Message: fmt.Sprintf("Inline %s %s", kind, name), | |
| TextEdits: edits, | |
| }}, | |
| }) | |
| } | |
| func (a *analyzer) readFile(node ast.Node) ([]byte, error) { | |
| filename := a.pass.Fset.File(node.Pos()).Name() | |
| content, ok := a.fileContent[filename] | |
| if !ok { | |
| var err error | |
| content, err = a.pass.ReadFile(filename) | |
| if err != nil { | |
| return nil, err | |
| } | |
| a.fileContent[filename] = content | |
| } | |
| return content, nil | |
| } | |
| // A goFixInlineFuncFact is exported for each function marked "//go:fix inline". | |
| // It holds information about the callee to support inlining. | |
| type goFixInlineFuncFact struct{ Callee *inline.Callee } | |
| func (f *goFixInlineFuncFact) String() string { return "goFixInline " + f.Callee.String() } | |
| func (*goFixInlineFuncFact) AFact() {} | |
| // A goFixInlineConstFact is exported for each constant marked "//go:fix inline". | |
| // It holds information about an inlinable constant. Gob-serializable. | |
| type goFixInlineConstFact struct { | |
| // Information about "const LHSName = RHSName". | |
| RHSName string | |
| RHSPkgPath string | |
| RHSPkgName string | |
| rhsObj types.Object // for current package | |
| } | |
| func (c *goFixInlineConstFact) String() string { | |
| return fmt.Sprintf("goFixInline const %q.%s", c.RHSPkgPath, c.RHSName) | |
| } | |
| func (*goFixInlineConstFact) AFact() {} | |
| // A goFixInlineAliasFact is exported for each type alias marked "//go:fix inline". | |
| // It holds no information; its mere existence demonstrates that an alias is inlinable. | |
| type goFixInlineAliasFact struct{} | |
| func (c *goFixInlineAliasFact) String() string { return "goFixInline alias" } | |
| func (*goFixInlineAliasFact) AFact() {} | |
| func discard(string, ...any) {} |