// Package discover is plumb's first phase: it scans loaded packages for// //plumb:<name> directives and turns each tagged declaration into a Provider,// the surface-independent description the solve phase wires together. It reads// syntax and type information but makes no wiring decisions.
package discoverimport ()// directiveNames extracts the set names from a comment group attached to a// declaration. It returns the recognized plumb set names in order, plus any// located error for a recognized-but-invalid name (e.g. //plumb:123).//// Recognition uses go/ast.ParseDirective, the same rule gofmt applies: a comment// gofmt would demote by inserting a space after "//" (and block comments) is not// a directive and is silently ignored. A //plumb: directive whose name is not a// Go identifier, or that carries arguments, is rejected.func ( *ast.CommentGroup, *token.FileSet) ([]string, *diag.Error) {returnappendDirectiveNames(nil, , )}// appendDirectiveNames appends the set names from doc to prior. prior holds the// sets the declaration already joins (an enclosing group's directives, or an// earlier comment in the same doc), so a directive naming one of them (whether// the repeat is within doc or across the group/spec boundary) is a duplicate and// is rejected identically. The returned slice reuses prior's backing array; the// caller must not retain prior separately.func ( []string, *ast.CommentGroup, *token.FileSet) ([]string, *diag.Error) {if == nil {return , nil } := for , := range .List { , := ast.ParseDirective(.Slash, .Text)if ! || .Tool != "plumb" {continue// not a //plumb: directive (gofmt-demoted, block comment, or another tool) }// Check the name before the trailing-argument case: //plumb:123 extra has // both faults, and the invalid name is the more fundamental one to report. // ParseDirective already requires the name to start with [a-z0-9], so it // never yields the blank identifier; token.IsIdentifier is the whole check.if !token.IsIdentifier(.Name) {returnnil, diag.Errorf(diag.PosIn(, .Pos()), diag.ErrInvalidSetName,"//%s; the set name must be a valid Go identifier", strings.TrimPrefix(.Text, "//")) }if .Args != "" {returnnil, diag.Errorf(diag.PosIn(, .Pos()), diag.ErrInvalidSetName,"//%s; a plumb directive names only a set, with no trailing arguments", strings.TrimPrefix(.Text, "//")) }ifslices.Contains(, .Name) {// A declaration joins a set at most once. Rather than silently deduping // (or producing a confusing "X collides with X" ambiguity), reject the // repeated directive with a clear, dedicated diagnostic.returnnil, diag.Errorf(diag.PosIn(, .Pos()), diag.ErrDuplicateDirective,"//%s; the declaration already joins set %q: remove the duplicate directive", strings.TrimPrefix(.Text, "//"), .Name) } = append(, .Name) }return , nil}// allBlank reports whether every declared name is the blank identifier, so the// spec declares nothing that could be wired.func ( []*ast.Ident) bool {for , := range {if .Name != "_" {returnfalse } }returnlen() > 0}// Analyze scans every package for directives and builds the provider list. It// returns a located error for any malformed provider.//// Packages are scanned in import-path order and files in name order, and scanFile// returns the source-earliest fault within a file, so when the input has more than// one fault the diagnostic returned is a fixed choice: the source-earliest fault// of the first file (in that order) to have one. This choice does not depend on the order// the loader presented packages and files (which it does not promise); every phase// anchors ordering to source position. The sorts run over copies, leaving the// loaded packages untouched. Provider order is irrelevant downstream (solve// re-sorts each set by position), so only diagnostic selection is affected.//// The file being overwritten (the prior generated output, identified by the// base name of -output within the destination package) is skipped, so plumb// never feeds its own previous output back as input. (plumb does not emit// directives, so this only matters for a hand-edited or stray directive in that// file.)func ( []*Package, , string) ([]*Provider, *diag.Error) {var []*Providerfor , := rangeslices.SortedFunc(slices.Values(), func(, *Package) int {returncmp.Compare(.PkgPath, .PkgPath) }) {for , := rangeslices.SortedFunc(slices.Values(.Syntax), func(, *ast.File) int {returncmp.Compare(FileBase(, ), FileBase(, )) }) {if != "" && .PkgPath == && FileBase(, ) == {continue// the file plumb is about to overwrite } , := scanFile(, )if != nil {returnnil, } = append(, ...) } }return , nil}// FileBase returns the base name of the file containing the given syntax tree.func ( *Package, *ast.File) string { := .Fset.File(.Pos())if == nil {return"" }returnfilepath.Base(.Name())}// scanFile walks one file's top-level declarations (locals are never providers)// and the type members nested in them, returning the source-earliest fault it// finds. A stray directive is caught only by the whole-file sweep, which must run// after the declaration scan, so the two can surface out of source order: a stray// in an early function body precedes a duplicate directive on a later declaration,// yet the declaration scan produces its fault first. Comparing the two by position// makes the reported diagnostic the one a reader would fix first. Within each phase// the earliest already comes first (declarations are visited in source order, so// the first declaration fault is the earliest of them, and the sweep walks comments// in position order), so only the cross-phase pair needs comparing.func ( *Package, *ast.File) ([]*Provider, *diag.Error) {var []*Providervar *diag.Errorfor , := range .Decls {var []*Providervar *diag.Errorswitch d := .(type) {case *ast.FuncDecl: , = scanFunc(, )case *ast.GenDecl: , = scanGenDecl(, )default:continue }if != nil { = break } = append(, ...) }if := diag.Earlier(, reportStrayDirectives(, )); != nil {returnnil, }return , nil}// reportStrayDirectives flags any //plumb: directive that sits in a position the// targeted scan above never reads, so it would otherwise be silently ignored: a// directive in a function body, on an import, or floating free between// declarations. The scan only consults the doc comments of declarations and// members it recognizes, so a directive anywhere else is a mistake worth a// located error rather than a no-op.//// The sweep walks file.Comments (every comment group in the file) because// comments inside bodies and free-floating ones live only there and are never// attached to a node's Doc; an AST node walk would miss exactly the positions we// want to catch. collectProviderDocs marks the groups the scan does read, and// anything else carrying a plumb directive is reported.func ( *Package, *ast.File) *diag.Error { := collectProviderDocs()for , := range .Comments {if [] {continue }for , := range .List { , := ast.ParseDirective(.Slash, .Text)if ! || .Tool != "plumb" {continue }returndiag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrMisplacedDirective,"//%s; a directive attaches to a package-level function, method, variable, constant, conversion, struct field, struct type, or interface method",strings.TrimPrefix(.Text, "//")) } }returnnil}// collectProviderDocs returns the set of comment groups the targeted scan reads// for directives: the doc of each top-level func, of each non-import GenDecl and// its value/type specs, and of each struct field and interface method. A plumb// directive in any other group is in an unsupported position.func ( *ast.File) map[*ast.CommentGroup]bool { := make(map[*ast.CommentGroup]bool) := func( *ast.CommentGroup) {if != nil { [] = true } }for , := range .Decls {switch d := .(type) {case *ast.FuncDecl: (.Doc)case *ast.GenDecl:if .Tok == token.IMPORT {continue// import specs are never providers; directives on them are stray } (.Doc)for , := range .Specs {switch s := .(type) {case *ast.ValueSpec: (.Doc)case *ast.TypeSpec: (.Doc)switch t := .Type.(type) {case *ast.StructType:for , := range .Fields.List { (.Doc) }case *ast.InterfaceType:for , := range .Methods.List { (.Doc) } } } } } }return}// specDirectiveNames returns the sets a spec joins: the union of the directives on// the enclosing GenDecl (which apply to every spec in the group) and the spec's// own directives. A group directive that precedes the keyword attaches to// GenDecl.Doc for both a single declaration and a parenthesized group; a directive// inside the group attaches to the spec's own doc. Unioning them (rather than// letting the spec's doc shadow the group's) means a spec that joins an extra set// does not silently drop out of the group's set. A set named at both levels is a// duplicate (the group already joins the spec to it), so it is rejected like any// other repeated directive rather than silently deduped.func ( *ast.GenDecl, *ast.CommentGroup, *token.FileSet) ([]string, *diag.Error) { , := directiveNames(.Doc, )if != nil {returnnil, }returnappendDirectiveNames(, , )}func ( *Package, *ast.FuncDecl) ([]*Provider, *diag.Error) { , := directiveNames(.Doc, .Fset)if != nil || len() == 0 {returnnil, } , := .Info.Defs[.Name].(*types.Func)if == nil {// A declared name with no object did not type-check: a redeclaration // loses its Defs entry. The loader already reports the real cause; a // recognized directive must still fail loudly, never silently vanish.returnnil, diag.Errorf(diag.PosIn(.Fset, .Name.Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire it", .Name.Name) } := .Type().(*types.Signature) := diag.PosIn(.Fset, .Name.Pos())// go/types records an object for a blank name too, so a directive on func _() // or a blank method reaches here. A blank identifier cannot be referenced (and // a blank method is absent from its type's method set, so it would later panic), // so reject it rather than emit an unusable "_" call.if .Name() == "_" { := "function"if .Recv() != nil { = "method" }returnnil, diag.Errorf(, diag.ErrBlankProvider, "give the %s a name; a blank identifier declares nothing to wire", ) }// init is the same trap with a different spelling: Go forbids referring to it // from anywhere, so an emitted init() call can never compile, and a package // may declare several. A method named init is an ordinary selector and stays // allowed. (A set named init is unrelated and supported.)if .Name() == "init" && .Recv() == nil {returnnil, diag.Errorf(, diag.ErrInitProvider, "init cannot be referenced, so it cannot provide; rename the function") }var []*Providerfor , := range { := &Provider{SetName: ,Pos: ,Name: .Name(),Pkg: .Types,Fn: , }if := .Recv(); != nil {// A method. Resolve the receiver's named type and its type params. A // concrete and an interface receiver share one kind; solve resolves the // receiver input type from the owner at instantiation time. := receiverNamed(.Type())if == nil {// The receiver type did not type-check: a typo'd or undefined // receiver in tolerated-invalid input. The loader already reports the // real cause, so surface a located error rather than crashing.returnnil, diag.Errorf(, diag.ErrInvalidType, "method %s has an unresolvable receiver type", .Name()) } .Kind = KindMethod .Owner = .Tparams = .TypeParams()// Diagnostics and the report name a method as Owner.Method, matching // the interface-method path: two same-named methods on different // receivers stay distinguishable without leaning on positions. .Name = .Obj().Name() + "." + .Name() } else { .Kind = KindFunc .Tparams = .TypeParams() } = append(, ) }return , nil}// receiverNamed unwraps a method receiver type to its origin named type, or nil// if the receiver is not a defined (named) type.func ( types.Type) *types.Named {if , := .(*types.Pointer); { = .Elem() } , := types.Unalias().(*types.Named)if ! {returnnil }return .Origin()}func ( *Package, *ast.GenDecl) ([]*Provider, *diag.Error) {// An empty group such as var (), const (), or type () has no spec a directive could // attach to, and collectProviderDocs marks the group doc as read, so the // stray-directive sweep never sees it. Judge the directive here: an invalid // name surfaces as such, and a well-formed one is rejected as misplaced // rather than silently ignored. Import groups stay with the sweep, which // already flags them.iflen(.Specs) == 0 && .Tok != token.IMPORT { , := directiveNames(.Doc, .Fset)if != nil {returnnil, }iflen() > 0 {for , := range .Doc.List {if , := ast.ParseDirective(.Slash, .Text); && .Tool == "plumb" {returnnil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrMisplacedDirective,"//%s; the declaration group is empty and declares nothing to wire: add a declaration or remove the directive",strings.TrimPrefix(.Text, "//")) } } } }var []*Providerfor , := range .Specs {switch s := .(type) {case *ast.ValueSpec: , := scanValueSpec(, , )if != nil {returnnil, } = append(, ...)case *ast.TypeSpec: , := scanTypeSpec(, , )if != nil {returnnil, } = append(, ...) } }return , nil}func ( *Package, *ast.GenDecl, *ast.ValueSpec) ([]*Provider, *diag.Error) { , := specDirectiveNames(, .Doc, .Fset)if != nil || len() == 0 {returnnil, }switch .Tok {casetoken.VAR:returnscanVar(, , )casetoken.CONST:returnscanConst(, , ) }returnnil, nil}func ( *Package, *ast.ValueSpec, []string) ([]*Provider, *diag.Error) {iflen(.Names) == 0 {returnnil, nil }// Conversion provider: a single blank variable with an explicit target type.iflen(.Names) == 1 && .Names[0].Name == "_" {returnscanConversion(, , .Names[0], ) }// Past the conversion case, an all-blank spec (var _, _ = ...) declares nothing // to wire; the directive is inert, so reject it rather than silently dropping it.ifallBlank(.Names) {returnnil, diag.Errorf(diag.PosIn(.Fset, .Names[0].Pos()), diag.ErrBlankProvider, "give the variable a name; a blank identifier declares nothing to wire") }// The directive applies to the spec; every named variable it declares becomes // a value provider. (Two of the same type then collide as an ambiguity, which // is correct.)var []*Providerfor , := range .Names {if .Name == "_" {continue } , := .Info.Defs[].(*types.Var)if == nil {// Redeclared names lose their Defs entry; fail loudly, as scanFunc does.returnnil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire it", .Name) } := diag.PosIn(.Fset, .Pos())for , := range { = append(, &Provider{SetName: ,Kind: KindSymbol,Pos: ,Name: .Name(),Pkg: .Types,Sym: , }) } }return , nil}func ( *Package, *ast.ValueSpec, *ast.Ident, []string) ([]*Provider, *diag.Error) { := diag.PosIn(.Fset, .Pos())if .Type == nil {returnnil, diag.Errorf(, diag.ErrInvalidConversion, "must declare an explicit target type") } := .Info.TypeOf(.Type)if == nil {// Unreachable even for tolerated-invalid input: go/types records a // (possibly Invalid) type for every type expression, never nothing.panic(fmt.Sprintf("plumb: conversion target type at %s has no recorded type", )) }iflen(.Values) == 0 {returnnil, diag.Errorf(, diag.ErrInvalidConversion, "needs a source like (*T)(nil); write the source type explicitly") }// A conversion provider goes from the source type to the declared target type: // it consumes typeof(Expr) and produces T, emitting T(src). Any conversion the // blank-var assignment type-checks is allowed (Go makes assignability itself a // conversion rule), so the target may be an interface the source implements, a // directional channel, a pointer with an identical base, and so on. The only // rejected form is an untyped nil, which names no source type at all. := .Info.TypeOf(.Values[0])if == nil {// An undefined identifier in value position gets no recorded type at all // (unlike a type position, which records Invalid). The loader already // reports the real cause; the directive must still fail loudly.returnnil, diag.Errorf(, diag.ErrInvalidType, "conversion source did not type-check; cannot wire it") }ifgotypes.IsUntypedNil() {returnnil, diag.Errorf(, diag.ErrInvalidConversion, "bare nil source; write (*Concrete)(nil) or name the source type") }// An identity conversion (source and target the same type) consumes and // produces the same type, which would otherwise surface as a self-referential // dependency cycle; reject it with the real reason instead.iftypes.Identical(, ) {// A constant source that is not itself written as a conversion (var _ // MyInt = 5) already carries the target's type (go/types converts the // literal implicitly), so "both MyInt" would baffle someone who wrote 5. // Name the real problem instead.if := .Info.Types[.Values[0]]; .Value != nil && !isConversionExpr(, .Values[0]) {returnnil, diag.Errorf(, diag.ErrInvalidConversion, "the source must name a type, e.g. (*T)(nil); a constant takes the target's own type") }returnnil, diag.Errorf(, diag.ErrInvalidConversion, "identity conversion: source and target are both %s", gotypes.TypeName()) }// On valid input the blank-var assignment type-checks, so the source is // assignable (hence convertible) to the target. When it does not (tolerated // type-error input), a non-convertible pair would emit an uncompilable T(src); // report it here instead. Skip the check if either side did not type-check; that // invalid type is caught downstream as ErrInvalidType.if !gotypes.ContainsInvalid() && !gotypes.ContainsInvalid() && !types.ConvertibleTo(, ) {returnnil, diag.Errorf(, diag.ErrInvalidConversion, "cannot convert %s to %s", gotypes.TypeName(), gotypes.TypeName()) }var []*Providerfor , := range { = append(, &Provider{SetName: ,Kind: KindConvert,Pos: ,Name: gotypes.TypeName(),Pkg: .Types,ConvertTo: ,ConvertFrom: , }) }return , nil}func ( *Package, *ast.ValueSpec, []string) ([]*Provider, *diag.Error) {ifallBlank(.Names) {returnnil, diag.Errorf(diag.PosIn(.Fset, .Names[0].Pos()), diag.ErrBlankProvider, "give the constant a name; a blank identifier declares nothing to wire") }var []*Providerfor , := range .Names {if .Name == "_" {continue } , := .Info.Defs[].(*types.Const)if == nil {// Redeclared names lose their Defs entry; fail loudly, as scanFunc does.returnnil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire it", .Name) } := diag.PosIn(.Fset, .Pos())ifgotypes.IsUntyped(.Type()) {returnnil, diag.Errorf(, diag.ErrUntypedConstant, "constant %s must have an explicit type", .Name()) }for , := range { = append(, &Provider{SetName: ,Kind: KindSymbol,Pos: ,Name: .Name(),Pkg: .Types,Sym: , }) } }return , nil}func ( *Package, *ast.GenDecl, *ast.TypeSpec) ([]*Provider, *diag.Error) {var []*Provider// 1. A directive on the type declaration itself: a struct provider. , := specDirectiveNames(, .Doc, .Fset)if != nil {returnnil, } , := .Info.Defs[.Name].(*types.TypeName) := .Name.Name == "_"iflen() > 0 {if {returnnil, diag.Errorf(diag.PosIn(.Fset, .Name.Pos()), diag.ErrBlankProvider, "give the type a name; a blank identifier declares nothing to wire") }if == nil {// A declared name with no object did not type-check: a redeclaration // loses its Defs entry. Fail loudly rather than drop the directive.returnnil, diag.Errorf(diag.PosIn(.Fset, .Name.Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire it", .Name.Name) } , := structProviders(, , , )if != nil {returnnil, } = append(, ...) }// 2. Directives on the type's members: struct fields or interface methods. The // owner is the declared type: a *types.Named for a defined type, or a // *types.Alias when the directive is on a member of an alias to an anonymous // composite (type C = struct{...} / interface{...}). This member scan only fires // when s.Type is a struct or interface literal, so an alias to a *named* // composite (which has an ast.Ident type) never reaches here. The gotypes.* // helpers read the origin, type parameters, and declaring object off either // form uniformly, so a field or method binds to a value of the alias just as it // does to one of a defined type.if != nil { := .Type()var []*Providerswitch st := .Type.(type) {case *ast.StructType: , = scanStructFields(, , )case *ast.InterfaceType: , = scanInterfaceMethods(, , ) }if != nil {returnnil, }// A blank-named type cannot be referenced, so a member provider bound to it // would emit an unusable receiver; reject a member directive on one.iflen() > 0 && {returnnil, diag.Errorf(diag.PosIn(.Fset, .Name.Pos()), diag.ErrBlankProvider, "give the type a name; its members cannot be reached through a blank identifier") } = append(, ...) } elseif := memberDirective(.Type); != nil {// The owner's declaration did not type-check (a redeclaration loses its // Defs entry), so the member scan cannot run. A member directive // must still fail loudly, never silently vanish with its owner.returnnil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire its members", .Name.Name) }return , nil}// memberDirective returns the first //plumb: directive comment attached to a// member of a struct or interface literal, or nil.func ( ast.Expr) *ast.Comment {var []*ast.Fieldswitch t := .(type) {case *ast.StructType: = .Fields.Listcase *ast.InterfaceType: = .Methods.Listdefault:returnnil }for , := range {if .Doc == nil {continue }for , := range .Doc.List {if , := ast.ParseDirective(.Slash, .Text); && .Tool == "plumb" {return } } }returnnil}func ( *Package, *ast.TypeSpec, *types.TypeName, []string) ([]*Provider, *diag.Error) { := diag.PosIn(.Fset, .Name.Pos())// The declared type may be a defined type or an alias; either way it must be a // struct at its core. An alias to an anonymous struct (type S = struct{...}) has // no *types.Named of its own but is still a struct, so gate on the underlying // type rather than requiring a defined type. := types.Unalias(.Type()).Underlying()if , := .(*types.Struct); ! {returnnil, diag.Errorf(, diag.ErrStructProvider, "%s is %s; struct-type providers are only supported on struct types", .Name(), gotypes.KindOfType()) }// Keep the declared type as written (a *Named, or a *Alias when the directive // is on an alias) so the generated composite literal names it rather than the // underlying type; the type parameters and origin come from that declared type. := .Type()var []*Providerfor , := range { = append(, &Provider{SetName: ,Kind: KindStruct,Pos: ,Name: .Name(),Pkg: .Types,Declared: ,Tparams: gotypes.TypeParamsOf(), }) }return , nil}// scanStructFields scans the fields of a struct declaration for member// directives. owner is the declared type (a *types.Named or, for an alias to an// anonymous struct, a *types.Alias), which the field provider binds to as its// receiver.func ( *Package, *ast.StructType, types.Type) ([]*Provider, *diag.Error) {var []*Providerfor , := range .Fields.List { , := directiveNames(.Doc, .Fset)if != nil {returnnil, }iflen() == 0 {continue } := diag.PosIn(.Fset, .Pos())iflen(.Names) == 0 {returnnil, diag.Errorf(, diag.ErrEmbeddedField, "give the field a name to use it") }// An all-blank directed field group (//plumb + _ int) declares nothing to // wire; reject it rather than silently drop the directive, as scanVar does.ifallBlank(.Names) {returnnil, diag.Errorf(, diag.ErrBlankProvider, "give the field a name; a blank identifier declares nothing to wire") }// Every name in a multi-name field group (A, B int) is its own provider, // mirroring multi-name var/const handling; same-type names then collide as // an ambiguity rather than being silently narrowed to the first. A blank // among them is skipped, the same way scanVar skips one.for , := range .Names {if .Name == "_" {continue } , := .Info.Defs[].(*types.Var)if == nil {// Redeclared fields lose their Defs entry; fail loudly, as scanFunc // does, rather than silently drop the directive on the losing field.returnnil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire it", .Name) }for , := range { = append(, &Provider{SetName: ,Kind: KindField,Pos: ,Name: gotypes.TypeNameOf().Name() + "." + .Name(),Pkg: .Types,Sym: ,Owner: gotypes.GenericOrigin(),Tparams: gotypes.TypeParamsOf(), }) } } }return , nil}// scanInterfaceMethods scans the methods of an interface declaration for member// directives. owner is the declared type (a *types.Named or, for an alias to an// anonymous interface, a *types.Alias), which the method provider binds to as its// receiver.func ( *Package, *ast.InterfaceType, types.Type) ([]*Provider, *diag.Error) {var []*Providerfor , := range .Methods.List { , := directiveNames(.Doc, .Fset)if != nil {returnnil, }iflen() == 0 {continue } := diag.PosIn(.Fset, .Pos())iflen(.Names) == 0 {returnnil, diag.Errorf(, diag.ErrEmbeddedInterface, "put the directive on a method instead") }// A non-basic (constraint) interface cannot be the type of a value.if , := .Underlying().(*types.Interface); && !.IsMethodSet() {returnnil, diag.Errorf(, diag.ErrConstraintInterfaceMethod, "%s: a constraint type cannot be the type of a value, so it cannot be a receiver", gotypes.TypeNameOf().Name()) } := .Names[0] , := .Info.Defs[].(*types.Func)if == nil {// No reachable trigger while the interface type-checks, but mirror the // sibling scanners rather than silently drop a recognized directive.returnnil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrInvalidType, "declaration of %s did not type-check; cannot wire it", .Name) }for , := range { = append(, &Provider{SetName: ,Kind: KindMethod,Pos: ,Name: gotypes.TypeNameOf().Name() + "." + .Name(),Pkg: .Types,Fn: ,Owner: gotypes.GenericOrigin(),Tparams: gotypes.TypeParamsOf(), }) } }return , nil}// isConversionExpr reports whether e is written as a conversion (a call whose// operand names a type), so MyInt(5) keeps the identity-conversion wording// while a bare constant gets the constant-specific one.func ( *Package, ast.Expr) bool { , := ast.Unparen().(*ast.CallExpr)if ! {returnfalse }return .Info.Types[ast.Unparen(.Fun)].IsType()}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)