Later examples will be variations on this one, and we'll often omit boilerplate details such as parsing. To check out and build the examples, run go get golang.
go/example/HEAD/./gotypestree:0933941cc4a2983d86b4201335b700a0aef1482d[pathhistory][tgz]MakefileREADME.mddefsuses/doc/go-types.mdhello/hugeparam/implements/lookup/nilfunc/pkginfo/skeleton/typeandvalue/weave.gogotypes/README.mdgo/types:TheGoTypeCheckerThisdocumentismaintainedbyAlanDonovanadonovan@google.com.October2015GothamGotalkongo/typesContentsIntroductionAnExampleObjectsIdentifierResolutionScopesInitializationOrderTypesBasictypesSimpleCompositeTypesStructTypesTupleTypesFunctionandMethodTypesNamedTypesInterfaceTypesTypeAndValueSelectionsIdsMethodSetsConstantsSizeandAlignmentImportsFormattingsupportGettingfromAtoBIntroductionThego/typespackageisatype-checkerforGoprograms,designedbyRobertGriesemer.ItbecamepartofGo‘sstandardlibraryinGo1.5.MeasuredbylinesofcodeandbyAPIsurfacearea,itisoneofthemostcomplexpackagesinGo’sstandardlibrary,andusingitrequiresafirmgraspofthestructureofGoprograms.Thistutorialwillhelpyoufindyourbearings.Itcomeswithseveralexampleprogramsthatyoucanobtainwithgogetandplaywith.WeassumeyouareaproficientGoprogrammerwhowantstobuildtoolstoanalyzeormanipulateGoprogramsandthatyouhavesomeknowledgeofhowatypicalcompilerworks.ThetypecheckercomplementsseveralexistingstandardpackagesforanalyzingGoprograms.We'velistedthembelow.→go/types
go/constant
go/parser
go/ast
go/scanner
go/token
Startingatthebottom,thego/tokenpackagedefinesthelexicaltokensofGo.Thego/scannerpackagetokenizesaninputstreamandrecordsfilepositioninformationforuseindiagnosticsorforfilesurgeryinarefactoringtool.Thego/astpackagedefinesthedatatypesoftheabstractsyntaxtree(AST).Thego/parserpackageprovidesarobustrecursive-descentparserthatconstructstheAST.Andgo/constantprovidesrepresentationsandarithmeticoperationsforthevaluesofcompile-timeconstantexpressions,aswe'llseeinConstants.Thegolang.org/x/tools/go/loaderpackagefromthex/toolsrepositoryisaclientofthetypecheckerthatloads,parses,andtype-checksacompleteGoprogramfromsourcecode.Weuseitinsomeofourexamplesandyoumayfinditusefultoo.TheGotypecheckerdoesthreemainthings.First,foreverynameintheprogram,itdetermineswhichdeclarationthenamerefersto;thisisknownasidentifierresolution.Second,foreveryexpressionintheprogram,itdetermineswhattypethatexpressionhas,orreportsanerroriftheexpressionhasnotype,orhasaninappropriatetypeforitscontext;thisisknownastypededuction.Third,foreveryconstantexpressionintheprogram,itdeterminesthevalueofthatconstant;thisisknownasconstantevaluation.Superficially,itappearsthatthesethreeprocessescouldbedonesequentially,intheorderabove,butperhapssurprisingly,theymustbedonetogether.Forexample,thevalueofaconstantmaydependonthetypeofanexpressionduetooperatorslikeunsafe.Sizeof.Conversely,thetypeofanexpressionmaydependonthevalueofaconstant,sincearraytypescontainconstants.Asaresult,typedeductionandconstantevaluationmustbedonetogether.Asanotherexample,wecannotresolvetheidentifierkinthecompositeliteralT{k:0}untilweknowwhetherTisastructtype.Ifitis,thenkmustbefoundamongT'sfields.Ifnot,thenkisanordinaryreferencetoaconstantorvariableinthelexicalenvironment.Consequently,identifierresolutionandtypedeductionarealsoinseparableinthegeneralcase.Nonetheless,thethreeprocessesofidentifierresolution,typededuction,andconstantevaluationcanbeseparatedforthepurposeofexplanation.AnExampleThecodebelowshowsthemostbasicuseofthetypecheckertocheckthehello,worldprogram,suppliedasastring.Laterexampleswillbevariationsonthisone,andwe'lloftenomitboilerplatedetailssuchasparsing.Tocheckoutandbuildtheexamples,rungogetgolang.org/x/example/gotypes/....//gogetgolang.org/x/example/gotypes/pkginfo
packagemain
import(
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
consthello=`packagemain
import"fmt"
funcmain(){
fmt.Println("Hello,world")
}`
funcmain(){
fset:=token.NewFileSet()
//Parsetheinputstring,[]byte,orio.Reader,
//recordingpositioninformationinfset.
//ParseFilereturnsan*ast.File,asyntaxtree.
f,err:=parser.ParseFile(fset,"hello.go",hello,0)
iferr!=nil{
log.Fatal(err)//parseerror
}
//AConfigcontrolsvariousoptionsofthetypechecker.
//Thedefaultsworkfineexceptforonesetting:
//wemustspecifyhowtodealwithimports.
conf:=types.Config{Importer:importer.Default()}
//Type-checkthepackagecontainingonlyfilef.
//Checkreturnsa*types.Package.
pkg,err:=conf.Check("cmd/hello",fset,[]*ast.File{f},nil)
iferr!=nil{
log.Fatal(err)//typeerror
}
fmt.Printf("Package%q\n",pkg.Path())
fmt.Printf("Name:%s\n",pkg.Name())
fmt.Printf("Imports:%s\n",pkg.Imports())
fmt.Printf("Scope:%s\n",pkg.Scope())
}
First,theprogramcreatesatoken.FileSet.Toavoidtheneedtostorefilenamesandlineandcolumnnumbersineverynodeofthesyntaxtree,thego/tokenpackageprovidesFileSet,adatastructurethatstoresthisinformationcompactlyforasequenceoffiles.AFileSetrecordseachfilenameonlyonce,andrecordsonlythebyteoffsetsofeachnewline,allowingapositionwithinanyfiletobeidentifiedusingasmallintegercalledatoken.Pos.ManytoolscreateasingleFileSetatstartup.Anypartoftheprogramthatneedstoconvertatoken.Posintoanintelligiblelocation---aspartofanerrormessage,forinstance---musthaveaccesstotheFileSet.Second,theprogramparsestheinputstring.Morerealisticpackagescontainseveralsourcefiles,sotheparsingstepmustberepeatedforeachone,orbetter,doneinparallel.Third,itcreatesaConfigthatspecifiestype-checkingoptions.Sincethehello,worldprogramusesimports,wemustindicatehowtolocatetheimportedpackages.Hereweuseimporter.Default(),whichloadscompiler-generatedexportdata,butwe'llexplorealternativesinImports.Fourth,theprogramcallsCheck.ThiscreatesaPackagewhosepathis"cmd/hello",andtype-checkseachofthespecifiedfiles---justoneinthisexample.Thefinal(nil)argumentisapointertoanoptionalInfostructthatreturnsadditionaldeductionsfromthetypechecker;moreonthatlater.CheckreturnsaPackageevenwhenitalsoreturnsanerror.Thetypecheckerisrobusttoill-formedinput,andgoestogreatlengthstoreportaccuratepartialinformationeveninthevicinityofsyntaxortypeerrors.Packagehasthisdefinition:typePackagestruct{...}
func(*Package)Path()string
func(*Package)Name()string
func(*Package)Scope()*Scope
func(*Package)Imports()[]*Package
Finally,theprogramprintstheattributesofthepackage,shownbelow.(Thehexadecimalnumbermayvaryfromoneruntothenext.)$gobuildgolang.org/x/example/gotypes/pkginfo
$./pkginfo
Package"cmd/hello"
Name:main
Imports:[packagefmt("fmt")]
Scope:package"cmd/hello"scope0x820533590{
.funccmd/hello.main()
}
Apackage'sPath,suchas"encoding/json",isthestringbywhichimportdeclarationsidentifyit.Itisuniquewithina$GOPATHworkspace,andforpublishedpackagesitmustbegloballyunique.Apackage'sNameistheidentifierinthepackagedeclarationofeachsourcefilewithinthepackage,suchasjson.Thetypecheckerreportsanerrorifnotallthepackagedeclarationsinthepackageagree.Thepackagenamedetermineshowthepackageisknownwhenitisimportedintoafile(unlessarenamingimportisused),butisotherwisenotvisibletoaprogram.Scopereturnsthepackage'slexicalblock,whichprovidesaccesstoallthenamedentitiesorobjectsdeclaredatpackagelevel.Importsreturnsthesetofpackagesdirectlyimportedbythisone,andmaybeusefulforcomputingdependencies(InitializationOrder).ObjectsThetaskofidentifierresolutionistomapeveryidentifierinthesyntaxtree,thatis,everyast.Ident,toanobject.Forourpurposes,anobjectisanamedentitycreatedbyadeclaration,suchasavar,type,orfuncdeclaration.(Thisisdifferentfromtheeverydaymeaningofobjectinobject-orientedprogramming.)ObjectsarerepresentedbytheObjectinterface:typeObjectinterface{
Name()string//package-localobjectname
Exported()bool//reportswhetherthenamestartswithacapitalletter
Type()Type//objecttype
Pos()token.Pos//positionofobjectidentifierindeclaration
Parent()*Scope//scopeinwhichthisobjectisdeclared
Pkg()*Package//nilforobjectsintheUniversescopeandlabels
Id()string//objectid(seeIdssectionbelow)
}
Thefirstfourmethodsarestraightforward;we‘llexplaintheotherthreelater.Namereturnstheobject’sname---anidentifier.ExportedisaconveniencemethodthatreportswhetherthefirstletterofNameisacapital,indicatingthattheobjectmaybevisiblefromoutsidethepackage.It‘sashorthandforast.IsExported(obj.Name()).Typereturnstheobject’stype;we'llcomebacktothatinTypes.Posreturnsthesourcepositionoftheobject'sdeclaringidentifier.Tomakesenseofatoken.Pos,weneedtocallthe(*token.FileSet).Positionmethod,whichreturnsastructwithindividualfieldsforthefilename,linenumber,column,andbyteoffset,thoughusuallywejustcallitsStringmethod:fmt.Println(fset.Position(obj.Pos()))//"hello.go:10:6"
Notallobjectscarrypositioninformation.Sincethefileformatforcompilerexportdata(Imports)doesnotrecordpositioninformation,callingPosonanobjectimportedfromsuchafilereturnszero,alsoknownastoken.NoPos.ThereareeightkindsofobjectsintheGotypechecker.Mostfamiliararethekindsthatcanbedeclaredatpackagelevel:constants,variables,functions,andtypes.Lessfamiliararestatementlabels,importedpackagenames(suchasjsoninafilecontaininganimport"encoding/json"declaration),built-infunctions(suchasappendandlen),andthepre-declarednil.TheeighttypesshownbelowaretheonlyconcretetypesthatsatisfytheObjectinterface.Inotherwords,Objectisadiscriminatedunionof8possibletypes,andwecommonlyuseatypeswitchtodistinguishthem.Object=*Func//function,concretemethod,orabstractmethod
|*Var//variable,parameter,result,orstructfield
|*Const//constant
|*TypeName//typename
|*Label//statementlabel
|*PkgName//packagename,e.g.jsonafterimport"encoding/json"
|*Builtin//predeclaredfunctionsuchasappendorlen
|*Nil//predeclarednil
Objectsarecanonical.Thatis,twoObjectsxandydenotethesameentityifandonlyifx==y.Objectidentityissignificant,andobjectsareroutinelycomparedbytheaddressesoftheunderlyingpointers.Althoughapackage-levelobjectisuniquelyidentifiedbyitsnameandenclosingpackage,forotherobjectsthereisnosimplewaytoobtainastringthatuniquelyidentifiesit.TheParentmethodreturnstheScope(lexicalblock)inwhichtheobjectwasdeclared;we'llcomebacktothisinScopes.Fieldsandmethodsarenotfoundinthelexicalenvironment,sotheirobjectshavenoParent.ThePkgmethodreturnsthePackagetowhichthisobjectbelongs,evenforobjectsnotdeclaredatpackagelevel.Onlypredeclaredobjectshavenopackage.TheIdmethodwillbeexplainedinIds.Notallmethodsmakesenseforeachkindofobject.Forinstance,thelastfourkindsabovehavenomeaningfulTypemethod.AndsomekindsofobjectshavemethodsinadditiontothoserequiredbytheObjectinterface:func(*Func)Scope()*Scope
func(*Var)Anonymous()bool
func(*Var)IsField()bool
func(*Const)Val()constant.Value
func(*TypeName)IsAlias()bool
func(*PkgName)Imported()*Package
(*Func).Scopereturnsthelexicalblockcontainingthefunction'sparameters,results,andotherlocaldeclarations.(*Var).IsFielddistinguishesstructfieldsfromordinaryvariables,and(*Var).Anonymousdiscriminatesnamedfieldsliketheoneinstruct{TT}fromanonymousfieldsliketheoneinstruct{T}.(*Const).Valreturnsthevalueofanamedconstant.(*TypeName).IsAlias,introducedinGo1.9,reportswhetherthetypenameissimplyanaliasforatype(asintypeI=int),asopposedtoadefinitionofaNamedtype,asintypeCelsiusfloat64.(*PkgName).Importedreturnsthepackage(forinstance,encoding/json)denotedbyagivenimportnamesuchasjson.Eachtimeapackageisimported,anewPkgNameobjectiscreated,usuallywiththesamenameasthePackageitdenotes,butnotalways,asinthecaseofarenamingimport.PkgNamesareobjects,butPackagesarenot.We'lllookmorecloselyatthisinImports.Allrelationshipsbetweenthesyntaxtrees(ast.Nodes)andtypecheckerdatastructuressuchasObjectsandTypesarestoredinmappingsoutsidethesyntaxtreeitself.Beawarethatthego/astpackagealsodefinesatypecalledObjectthatresembles---andpredates---thetypechecker'sObject,andthatast.ObjectsarehelddirectlybyidentifiersintheAST.Theyarecreatedbytheparser,whichhasanecessarilylimitedviewofthepackage,sotheinformationtheyrepresentisatbestpartialandinsomecaseswrong,asintheT{k:0}examplementionedabove.Ifyouareusingthetypechecker,thereisnoreasontousetheolderast.Objectmechanism.IdentifierResolutionIdentifierresolutioncomputestherelationshipbetweenidentifiersandobjects.ItsresultsarerecordedintheInfostructoptionallypassedtoCheck.Thefieldsrelatedtoidentifierresolutionareshownbelow.typeInfostruct{
Defsmap[*ast.Ident]Object
Usesmap[*ast.Ident]Object
Implicitsmap[ast.Node]Object
Selectionsmap[*ast.SelectorExpr]*Selection
Scopesmap[ast.Node]*Scope
...
}
Sincenotallfactscomputedbythetypecheckerareneededbyeveryclient,theAPIletsclientscontrolwhichcomponentsoftheresultshouldberecordedandwhichdiscarded:onlyfieldsthatholdanon-nilmapwillbepopulatedduringthecalltoCheck.Thetwofieldsoftypemap[*ast.Ident]Objectarethemostimportant:DefsrecordsdeclaringidentifiersandUsesrecordsreferringidentifiers.Intheexamplebelow,thecommentsindicatewhichidentifiersareofwhichkind.varxint//defofx,useofint
fmt.Println(x)//usesoffmt,Println,andx
typeTstruct{U}//defofT,useofU(type),defofU(field)
Thefinallineaboveillustrateswhywedon'tcombineDefsandUsesintoonemap.Intheanonymousfielddeclarationstruct{U},theidentifierUisbothauseofthetypeU(aTypeName)andadefinitionoftheanonymousfield(aVar).Thefunctionbelowprintsthelocationofeachreferringanddefiningidentifierintheinputprogram,andtheobjectitrefersto.//gogetgolang.org/x/example/gotypes/defsuses
funcPrintDefsUses(fset*token.FileSet,files...*ast.File)error{
conf:=types.Config{Importer:importer.Default()}
info:=&types.Info{
Defs:make(map[*ast.Ident]types.Object),
Uses:make(map[*ast.Ident]types.Object),
}
_,err:=conf.Check("hello",fset,files,info)
iferr!=nil{
returnerr//typeerror
}
forid,obj:=rangeinfo.Defs{
fmt.Printf("%s:%qdefines%v\n",
fset.Position(id.Pos()),id.Name,obj)
}
forid,obj:=rangeinfo.Uses{
fmt.Printf("%s:%quses%v\n",
fset.Position(id.Pos()),id.Name,obj)
}
returnnil
}
Let'susethehello,worldprogramagainastheinput://gogetgolang.org/x/example/gotypes/hello
packagemain
import"fmt"
funcmain(){
fmt.Println("Hello,世界")
}
Thisiswhatitprints:$gobuildgolang.org/x/example/gotypes/defsuses
$./defsuses
hello.go:1:9:"main"defines
hello.go:5:6:"main"definesfunchello.main()
hello.go:6:9:"fmt"usespackagefmt
hello.go:6:13:"Println"usesfuncfmt.Println(a...interface{})(nint,errerror)
NoticethattheDefsmappingmaycontainnilentriesinafewcases.ThefirstlineofoutputreportsthatthepackageidentifiermainispresentintheDefsmapping,buthasnoassociatedobject.TheImplicitsmappinghandlestwocasesofthesyntaxinwhichanObjectisdeclaredwithoutanast.Ident,namelytypeswitchesandimportdeclarations.Inthetypeswitchbelow,whichdeclaresalocalvariabley,thetypeofyisdifferentineachcaseoftheswitch:switchy:=x.(type){
caseint:
fmt.Printf("%d",y)
casestring:
fmt.Printf("%q",y)
default:
fmt.Print(y)
}
Torepresentthis,foreachsingle-typecase,thetypecheckercreatesaseparateVarobjectforywiththeappropriatetype,andImplicitsmapseachast.CaseClausetotheVarforthatcase.Thedefaultcase,thenilcase,andcaseswithmorethanonetypeallusetheregularVarobjectthatisassociatedwiththeidentifiery,whichisfoundintheDefsmapping.Theimportdeclarationbelowdefinesthenamejsonwithoutanast.Ident:import"encoding/json"
Implicitsmapsthisast.ImportSpectothePkgNameobjectnamedjsonthatitimplicitlydeclares.TheSelectionsmapping,oftypemap[*ast.SelectorExpr]*Selection,recordsthemeaningofeachexpressionoftheformexpr.f,whereexprisanexpressionortypeandfisthenameofafieldormethod.Theseexpressions,calledselections,arerepresentedbyast.SelectorExprnodesintheAST.We'lltalkmoreabouttheSelectiontypeinSelections.Notallast.SelectorExprnodesrepresentselections.Expressionslikefmt.Println,inwhichapackagenameprecedesthedot,arequalifiedidentifiers.TheydonotappearintheSelectionsmapping,buttheirconstituentidentifiers(suchasfmtandPrintln)bothappearinUses.Referringidentifiersthatarenotpartofanast.SelectorExprarelexicalreferences.Thatis,theyareresolvedtoanobjectbysearchingfortheinnermostenclosinglexicaldeclarationofthatname.We'llseehowthatsearchworksinthenextsection.ScopesTheScopetypeisamappingfromnamestoobjects.typeScopestruct{...}
func(s*Scope)Names()[]string
func(s*Scope)Lookup(namestring)Object
Namesreturnsthesetofnamesinthemapping,insortedorder.(Itisnotasimpleaccessorthough,socallitsparingly.)TheLookupmethodreturnstheobjectforagivenname,sowecanprintalltheentriesorbindingsinascopelikethis:for_,name:=rangescope.Names(){
fmt.Println(scope.Lookup(name))
}
Thescopeofadeclarationofanameistheregionofprogramsourceinwhichareferencetothenameresolvestothatdeclaration.Thatis,scopeisapropertyofadeclaration.However,inthego/typesAPI,theScopetyperepresentsalexicalblock,whichisonecomponentofthelexicalenvironment.Considerthehello,worldprogramagain:packagemain
import"fmt"
funcmain(){
constmessage="hello,world"
fmt.Println(message)
}
Therearefourlexicalblocksinthisprogram.Theoutermostoneistheuniverseblock,whichmapsthepre-declarednameslikeint,true,andappendtotheirobjects---aTypeName,aConst,andaBuiltin,respectively.TheuniverseblockisrepresentedbytheglobalvariableUniverse,oftype*Scope,althoughit‘slogicallyaconstantsoyoushouldn’tmodifyit.Nextisthepackageblock,whichmaps"main"tothemainfunction.Followingthatisthefileblock,whichmaps"fmt"tothePkgNameobjectforthisimportofthefmtpackage.Andfinally,theinnermostblockisthatoffunctionmain,alocalblock,whichcontainsthedeclarationofmessage,aConst.Themainfunctionistrivial,butmanyfunctionscontainseveralblockssinceeachif,for,switch,case,orselectstatementcreatesatleastoneadditionalblock.Localblocksnesttoarbitrarydepths.Thestructureofthelexicalenvironmentthusformsatree,withtheuniverseblockattheroot,thepackageblocksbeneathit,thefileblocksbeneaththem,andthenanynumberoflocalblocksbeneaththefiles.WecanaccessandnavigatethistreestructurewiththefollowingmethodsofScope:func(s*Scope)Parent()*Scope
func(s*Scope)NumChildren()int
func(s*Scope)Child(iint)*Scope
Parentletsuswalkupthetree,andChildletsuswalkdownit.NotethatalthoughtheParentofeverypackageScopeisUniverse,Universehasnochildren.ThisasymmetryisaconsequenceofusingaglobalvariabletoholdUniverse.Toobtaintheuniverseblock,weusetheUniverseglobalvariable.ToobtainthelexicalblockofaPackage,wecallitsScopemethod.Toobtainthescopeofafile(*ast.File),oranysmallerpieceofsyntaxsuchasan*ast.IfStmt,weconsulttheScopesmappingintheInfostruct,whichmapseachblock-creatingsyntaxnodetoitsblock.Thelexicalblockofanamedfunctionormethodcanalsobeobtainedbycallingits(*Func).Scopemethod.Tolookupanameinthelexicalenvironment,wemustsearchthetreeoflexicalblocks,startingataparticularScopeandwalkinguptotherootuntiladeclarationofthenameisfound.Forconvenience,theLookupParentmethoddoesthis,returningnotjusttheobject,iffound,butalsotheScopeinwhichitwasdeclared,whichmaybeanancestoroftheinitialone:func(s*Scope)LookupParent(namestring,postoken.Pos)(*Scope,Object)
Theposparameterdeterminesthepositioninthesourcecodeatwhichthenameshouldberesolved.Theeffectivelexicalenvironmentisdifferentateachpointintheblockbecauseitdependsonwhichlocaldeclarationsappearbeforeorafterthatpoint.(We'llseeanillustrationinamoment.)Scopehasseveralothermethodsrelatingtosourcepositions:func(s*Scope)Pos()token.Pos
func(s*Scope)End()token.Pos
func(s*Scope)Contains(postoken.Pos)bool
func(s*Scope)Innermost(postoken.Pos)*Scope
PosandEndreporttheScope'sstartandendpositionwhich,forexplicitblocks,coincidewithitscurlybraces.Containsisaconveniencemethodthatreportswhetherapositionliesinthisinterval.Innermostreturnstheinnermostscopecontainingthespecifiedposition,whichmaybeachildorotherdescendentoftheinitialscope.Thesefeaturesareusefulfortoolsthatwishtoresolvenamesorevaluateconstantexpressionsasiftheyhadappearedataparticularpointwithintheprogram.Thenextexampleprogramfindsallthecommentsintheinput,treatingthecontentsofeachoneasaname.Itlooksupeachnameintheenvironmentatthepositionofthecomment,andprintswhatitfinds.ObservethattheParseCommentsflagdirectstheparsertopreservecommentsintheinput.//gogetgolang.org/x/example/gotypes/lookup
funcmain(){
fset:=token.NewFileSet()
f,err:=parser.ParseFile(fset,"hello.go",hello,parser.ParseComments)
iferr!=nil{
log.Fatal(err)//parseerror
}
conf:=types.Config{Importer:importer.Default()}
pkg,err:=conf.Check("cmd/hello",fset,[]*ast.File{f},nil)
iferr!=nil{
log.Fatal(err)//typeerror
}
//Eachcommentcontainsaname.
//Lookupthatnameintheinnermostscopeenclosingthecomment.
for_,comment:=rangef.Comments{
pos:=comment.Pos()
name:=strings.TrimSpace(comment.Text())
fmt.Printf("At%s,\t%q=",fset.Position(pos),name)
inner:=pkg.Scope().Innermost(pos)
if_,obj:=inner.LookupParent(name,pos);obj!=nil{
fmt.Println(obj)
}else{
fmt.Println("notfound")
}
}
}
Theexpressionpkg.Scope().Innermost(pos)findstheinnermostScopethatenclosesthecomment,andLookupParent(name,pos)doesanamelookupataspecificpositioninthatlexicalblock.Atypicalinputisshownbelow.Thefirstcommentcausesalookupof"append"inthefileblock.Thesecondcommentlooksup"fmt"inthemainfunction'sblock,andsoon.consthello=`
packagemain
import"fmt"
//append
funcmain(){
//fmt
fmt.Println("Hello,world")
//main
main,x:=1,2
//main
print(main,x)
//x
}
//x
`
Here'stheoutput:$gobuildgolang.org/x/example/gotypes/lookup
$./lookup
Athello.go:6:1,"append"=builtinappend
Athello.go:8:9,"fmt"=packagefmt
Athello.go:10:9,"main"=funccmd/hello.main()
Athello.go:12:9,"main"=varmainint
Athello.go:14:9,"x"=varxint
Athello.go:16:1,"x"=notfound
Noticehowthetwolookupsofmainreturndifferentresults,eventhoughtheyoccurinthesameblock,becauseoneprecedesthedeclarationofthelocalvariablenamedmainandtheotherfollowsit.Alsonoticethattherearetwolookupsofthenamexbutonlythefirstone,inthefunctionblock,succeeds.Downloadtheprogramandmodifyboththeinputprogramandthesetofcommentstogetabetterfeelforhownameresolutionworks.Thetablebelowsummarizeswhichkindsofobjectsmaybedeclaredateachlevelofthetreeoflexicalblocks.UniverseFilePackageLocal
Builtin✔
Nil✔
Const✔✔✔
TypeName✔✔✔
Func✔
Var✔✔
PkgName✔
Label✔
InitializationOrderInthecourseofidentifierresolution,thetypecheckerconstructsagraphofreferencesamongdeclarationsofpackage-levelvariablesandfunctions.Thetypecheckerreportsanerroriftheinitializerexpressionforavariablereferstothatvariable,whetherdirectlyorindirectly.Thereferencegraphdeterminestheinitializationorderofthepackage-levelvariables,asrequiredbytheGospec,usingabreadth-firstalgorithm.First,variablesinthegraphwithnosuccessorsareremoved,sortedintotheorderinwhichtheyappearinthesourcecode,thenaddedtoalist.Thiscreatesmorevariablesthathavenosuccessors.Theprocessrepeatsuntiltheyhaveallbeenremoved.TheresultisavailableintheInitOrderfieldoftheInfostruct,whosetypeis[]Initializer.typeInfostruct{
...
InitOrder[]Initializer
...
}
typeInitializerstruct{
Lhs[]*Var//varLhs=Rhs
Rhsast.Expr
}
Eachelementofthelistrepresentsasingleinitializerexpressionthatmustbeexecuted,andthevariablestowhichitisassigned.Thevariablesmaynumberzero,one,ormore,asintheseexamples:var_io.Writer=new(bytes.Buffer)
varrx=regexp.MustCompile("^b(an)*a$")
varcwd,cwdErr=os.Getwd()
Thisprocessgovernstheinitializationorderofvariableswithinapackage.Acrosspackages,dependenciesmustbeinitializedfirst,althoughtheorderamongthemisnotspecified.Thatis,anytopologicalorderoftheimportgraphwilldo.The(*Package).Importsmethodreturnsthesetofdirectdependenciesofapackage.TypesThemainjobofthetypecheckeris,ofcourse,todeducethetypeofeachexpressionandtoreporttypeerrors.LikeObject,Typeisaninterfacetypeusedasadiscriminatedunionofseveralconcretetypesbut,unlikeObject,Typehasveryfewmethodsbecausetypeshavelittleincommonwitheachother.Hereistheinterface:typeTypeinterface{
Underlying()Type
}
Andherearetheelevenconcretetypesthatsatisfyit:Type=*Basic
|*Pointer
|*Array
|*Slice
|*Map
|*Chan
|*Struct
|*Tuple
|*Signature
|*Named
|*Interface
WiththeexceptionofNamedtypes,instancesofTypearenotcanonical.Thatis,itisusuallyamistaketocomparetypesusingt1==t2sincethisequivalenceisnotthesameasthetypeidentityrelationdefinedbytheGospec.Usethisfunctioninstead:funcIdentical(t1,t2Type)bool
Forthesamereason,youshouldnotuseaTypeasakeyinamap.Thegolang.org/x/tools/go/types/typeutilpackageprovidesamapkeyedbytypesthatusesthecorrectequivalencerelation.TheGospecdefinesthreerelationsovertypes.Assignabilitygovernswhichpairsoftypesmayappearontheleft-andright-handsideofanassignment,includingimplicitassignmentssuchasfunctioncalls,mapandchanneloperations,andsoon.Comparabilitydetermineswhichtypesmayappearinacomparisonx==yoraswitchcaseormaybeusedasamapkey.ConvertibilitygovernswhichpairsoftypesareallowedinaconversionoperationT(v).Youcanquerytheserelationswiththefollowingpredicatefunctions:funcAssignableTo(V,TType)bool
funcComparable(TType)bool
funcConvertibleTo(V,TType)bool
Let'stakealookateachkindoftype.BasictypesBasicrepresentsalltypesthatarenotcomposedfromsimplertypes.Thisisessentiallythesetofunderlyingtypesthataconstantexpressionispermittedtohave--strings,booleans,andnumbers---butitalsoincludesunsafe.Pointeranduntypednil.typeBasicstruct{...}
func(*Basic)Kind()BasicKind
func(*Basic)Name()string
func(*Basic)Info()BasicInfo
TheKindmethodreturnsan“enum”valuethatindicateswhichbasictypethisis.ThekindsBool,String,Int16,andsoon,representthecorrespondingpredeclaredboolean,string,ornumerictypes.Therearetwosynonyms:ByteisequivalenttoUint8andRuneisequivalenttoInt32.ThekindUnsafePointerrepresentsunsafe.Pointer.ThekindsUntypedBool,UntypedIntandsoonrepresentthesixkindsof“untyped”constanttypes:boolean,integer,rune,float,complex,andstring.ThekindUntypedNilrepresentsthetypeofthepredeclarednilvalue.AndthekindInvalidindicatestheinvalidtype,whichisusedforexpressionscontainingerrors,orforobjectswithouttypes,likeLabel,Builtin,orPkgName.TheNamemethodreturnsthenameofthetype,suchas"float64",andtheInfomethodreturnsabitfieldthatencodesinformationaboutthetype,suchaswhetheritissignedorunsigned,integerorfloatingpoint,orrealorcomplex.Typisatableofcanonicalbasictypes,indexedbykind,soTyp[String]returnsthe*Basicthatrepresentsstring,forinstance.LikeUniverse,Typislogicallyaconstant,sodon'tmodifyit.Afewminorsubtleties:AccordingtotheGospec,pre-declaredtypessuchasintarenamedtypesforthepurposesofassignability,eventhoughthetypecheckerdoesnotrepresentthemusingNamed.Andunsafe.Pointerisapointertypeforthepurposeofdeterminingwhetherthereceivertypeofamethodislegal,eventhoughthetypecheckerdoesnotrepresentitusingPointer.The“untyped”typesareusuallyonlyascribedtoconstantexpressions,butthereisoneexception.Acomparisonx==yhastype“untypedbool”,sotheresultofthisexpressionmaybeassignedtoavariableoftypebooloranyothernamedbooleantype.SimpleCompositeTypesThetypesPointer,Array,Slice,Map,andChanareprettyself-explanatory.AllhaveanElemmethodthatreturnstheelementtypeTforapointer*T,anarray[n]T,aslice[]T,amapmap[K]T,orachannelchanT.Thisshouldfeelfamiliarifyou'veusedthereflect.ValueAPI.Inaddition,the*Map,*Chan,and*Arraytypeshaveaccessormethodsthatreturntheirkeytype,direction,andlength,respectively:func(*Map)Key()Type
func(*Chan)Dir()ChanDir//=Send|Recv|SendRecv
func(*Array)Len()int64
StructTypesAstructtypehasanorderedlistoffieldsandacorrespondingorderedlistoffieldtags.typeStructstruct{...}
func(*Struct)NumFields()int
func(*Struct)Field(iint)*Var
func(*Struct)Tag(iint)string
EachfieldisaVarobjectwhoseIsFieldmethodreturnstrue.FieldobjectshavenoParentscope,becausetheyareresolvedthroughselections,notthroughthelexicalenvironment.Thankstoembedding,theexpressionnew(S).fmaybeashorthandforalongerexpressionsuchasnew(S).d.e.f,butintherepresentationofStructtypes,thesefieldselectionoperationsareexplicit.Thatis,thesetoffieldsofstructtypeSdoesnotincludef.Ananonymousfieldisrepresentedlikearegularfield,butitsAnonymousmethodreturnstrue.Onesubtletyisrelevanttotoolsthatgeneratedocumentation.Whenanalyzingadeclarationsuchasthis,typeTstruct{xint}
itmaybetemptingtoconsidertheVarobjectforfieldxasifithadthename"T.x",butbeware:fieldobjectsdonothavecanonicalnamesandthereisnowaytoobtainthename"T"fromtheVarforx.That'sbecauseseveraltypesmayhavethesameunderlyingstructtype,asinthiscode:typeTstruct{xint}
typeUT
Here,theVarforfieldxbelongsequallytoTandtoU,andshortofinspectingsourcepositionsorwalkingtheAST---neitherofwhichispossibleforobjectsloadedfromcompilerexportdata---itisnotpossibletoascertainthatxwasdeclaredaspartofT.Thetypecheckerbuildstheexactsamedatastructuresgiventhisinput:typeTU
typeUstruct{xint}
Asimilarissueappliestothemethodsofnamedinterfacetypes.TupleTypesLikeastruct,atupletypehasanorderedlistoffields,andfieldsmaybenamed.typeTuplestruct{...}
func(*Tuple)Len()int
func(*Tuple)At(iint)*Var
AlthoughtuplesarenotthetypeofanyvariableinGo,theyarethetypeofsomeexpressions,suchastheright-handsidesoftheseassignments:v,ok=m[key]
v,ok=int64(*bytesFlag){
fmt.Printf("%s:%q%s:%s=%dbytes\n",
fset.Position(v.Pos()),
v.Name(),descr,v.Type(),sz)
}
}
}
checkSig:=func(sig*types.Signature){
checkTuple("parameter",sig.Params())
checkTuple("result",sig.Results())
}
for_,file:=rangefiles{
ast.Inspect(file,func(nast.Node)bool{
switchn:=n.(type){
case*ast.FuncDecl:
checkSig(info.Defs[n.Name].Type().(*types.Signature))
case*ast.FuncLit:
checkSig(info.Types[n.Type].Type.(*types.Signature))
}
returntrue
})
}
}
Asbefore,InspectappliesafunctiontoeverynodeintheAST.Thefunctioncaresabouttwokindsofnodes:declarationsofnamedfunctionsandmethods(*ast.FuncDecl)andfunctionliterals(*ast.FuncLit).Observethetwocases'differentlogictoobtainthetypeofeachfunction.Here'satypicalinvocationonthestandardencoding/xmlpackage.Itreportsanumberofplaceswherethe7-wordStartElementtypeiscopied.%./hugeparamencoding/xml
/go/src/encoding/xml/marshal.go:167:50:"start"parameter:encoding/xml.StartElement=56bytes
/go/src/encoding/xml/marshal.go:734:97:""result:encoding/xml.StartElement=56bytes
/go/src/encoding/xml/marshal.go:761:51:"start"parameter:encoding/xml.StartElement=56bytes
/go/src/encoding/xml/marshal.go:781:68:"start"parameter:encoding/xml.StartElement=56bytes
/go/src/encoding/xml/xml.go:72:30:""result:encoding/xml.StartElement=56bytes
ImportsThetypechecker'sCheckfunctionprocessesasliceofparsedfiles([]*ast.File)thatmakeuponepackage.Whenthetypecheckerencountersanimportdeclaration,itneedsthetypeinformationfortheobjectsintheimportedpackage.ItgetsitbycallingtheImportmethodoftheImporterinterfaceshownbelow,aninstanceofwhichmustbeprovidedbytheConfig.ThisseparationofconcernsrelievesthetypecheckerfromhavingtoknowanyofthedetailsofGoworkspaceorganization,GOPATH,compilerfileformats,andsoon.typeImporterinterface{
Import(pathstring)(*Package,error)
}
MostofourexamplesusedthesimplestImporterimplementation,importer.Default(),providedbythego/importerpackage.Thisimporterlooksin$GOROOTand$GOPATHfor.afileswrittenbythecompiler(gcorgccgo)thatwasusedtobuildtheprogram.Inadditiontoobjectcode,thesefilescontainexportdata,thatis,adescriptionofalltheobjectsdeclaredbythepackage,andalsoofanyobjectsfromotherpackagesthatwerereferredtoindirectly.Becauseexportdataincludesinformationaboutdependencies,thetypecheckerneedloadatmostonefileperimport,insteadofonepertransitivedependency.Compilerexportdataiscompactandefficienttolocate,load,andparse,butithasseveralshortcomings.First,itdoesnotcontainpositioninformationforimportedobjects,reducingthequalityofcertaindiagnosticmessages.Second,itdoesnotcontaincompletesyntaxtreesnorsemanticinformationaboutthecontentsoffunctionbodies,soitisnotsuitableforinterproceduralanalyses.Third,compilerobjectdatamaybestale.Nothingdetectsorensuresthattheobjectfilesaremorerecentthanthesourcefilesfromwhichtheywerederived.Generally,objectdataforstandardpackagesislikelytobeup-to-date,butforuserpackages,itdependsonhowrecentlytheuserranagoinstallorgobuild-icommand.Thegolang.org/tools/x/go/loaderpackageprovidesanalternativeImporterthataddressessomeoftheseproblems.Itloadsacompleteprogramfromsource,performingcgopreprocessingifnecessary,followedbyparsingandtype-checking.ItloadsindependentpackagesinparalleltohideI/Olatency,anddetectsandreportsimportcycles.Foreachpackage,itprovidesthetypes.Packagecontainingthepackage‘slexicalenvironment,thelistofast.Filesyntaxtreesforeachfileinthepackage,thetypes.Infocontainingtypeinformationforeachsyntaxnode,andalistoftypeerrorsassociatedwiththatpackage.(Pleasebeawarethatthego/loaderpackage’sAPIislikelytochangebeforeitfinallystabilizes.)Thedocprogrambelowdemonstratesasimpleuseoftheloader.Itisarudimentaryimplementationofgodocthatprintsthetype,methods,anddocumentationofthepackage-levelobjectspecifiedonthecommandline.Here'sanexample:$./docnet/httpFile
typenet/http.Fileinterface{Readdir(countint)([]os.FileInfo,error);Seek(offsetint64,whenceint)(int64,error);Stat()(os.FileInfo,error);io.Closer;io.Reader}
/go/src/io/io.go:92:2:method(net/http.File)Close()error
/go/src/io/io.go:71:2:method(net/http.File)Read(p[]byte)(nint,errerror)
/go/src/net/http/fs.go:65:2:method(net/http.File)Readdir(countint)([]os.FileInfo,error)
/go/src/net/http/fs.go:66:2:method(net/http.File)Seek(offsetint64,whenceint)(int64,error)
/go/src/net/http/fs.go:67:2:method(net/http.File)Stat()(os.FileInfo,error)
AFileisreturnedbyaFileSystem'sOpenmethodandcanbe
servedbytheFileServerimplementation.
Themethodsshouldbehavethesameasthoseonan*os.File.
Observethatitprintsthecorrectlocationofeachmethoddeclaration,eventhough,duetoembedding,someofhttp.File‘smethodsweredeclaredinanotherpackage.Here’sthefirstpartoftheprogram,showinghowtoloadanentireprogramstartingfromthesinglepackage,pkgpath://gogetgolang.org/x/example/gotypes/doc
pkgpath,name:=os.Args[1],os.Args[2]
//TheloaderloadsacompleteGoprogramfromsourcecode.
conf:=loader.Config{ParserMode:parser.ParseComments}
conf.Import(pkgpath)
lprog,err:=conf.Load()
iferr!=nil{
log.Fatal(err)//loaderror
}
//Findthepackageandpackage-levelobject.
pkg:=lprog.Package(pkgpath).Pkg
obj:=pkg.Scope().Lookup(name)
ifobj==nil{
log.Fatalf("%s.%snotfound",pkg.Path(),name)
}
Noticethatweinstructedtheparsertoretaincommentsduringparsing.Therestoftheprogramprintstheoutput://gogetgolang.org/x/example/gotypes/doc
//Printtheobjectanditsmethods(incl.locationofdefinition).
fmt.Println(obj)
for_,sel:=rangetypeutil.IntuitiveMethodSet(obj.Type(),nil){
fmt.Printf("%s:%s\n",lprog.Fset.Position(sel.Obj().Pos()),sel)
}
//FindthepathfromtherootoftheASTtotheobject'sposition.
//Walkuptotheenclosingast.Declforthedoccomment.
_,path,_:=lprog.PathEnclosingInterval(obj.Pos(),obj.Pos())
for_,n:=rangepath{
switchn:=n.(type){
case*ast.GenDecl:
fmt.Println("\n",n.Doc.Text())
return
case*ast.FuncDecl:
fmt.Println("\n",n.Doc.Text())
return
}
}
WeusedIntuitiveMethodSettocomputethemethodset,insteadofNewMethodSet.Theresultofthisconveniencefunction,whichisintendedforuseinuserinterfaces,includesmethodsof*TaswellasthoseofT,sincethatmatchesmostusers'intuitionaboutthemethodsetofatype.(Ourexample,http.File,didn'tillustratethedifference,buttryrunningitonatypewithbothvalueandpointermethods.)AlsonoticePathEnclosingInterval,whichfindsthesetofASTnodesthatencloseaparticularpoint,inthiscase,theobject'sdeclaringidentifier.Bywalkingupwithpath,wefindtheenclosingdeclaration,towhichthedocumentationisattached.FormattingsupportAlltypesthatsatisfyTypeorObjectdefineaStringmethodthatformatsthetypeorobjectinareadablenotation.SelectionalsoprovidesaStringmethod.Allpackage-levelobjectswithinthesedatastructuresareprintedwiththecompletepackagepath,asintheseexamples:[]encoding/json.Marshaler//a*Slicetype
encoding/json.Marshal//a*Funcobject
(*encoding/json.Encoder).Encode//a*Funcobject(method)
func(enc*encoding/json.Encoder)Encode(vinterface{})error//amethod*Signature
funcNewEncoder(wio.Writer)*encoding/json.Encoder//afunction*Signature
Thisnotationisunambiguous,butitisnotlegalGosyntax.Also,packagepathsmaybelong,andthesamepackagepathmayappearmanytimesinasinglestring,forinstance,whenformattingafunctionofseveralparameters.Becausethesestringsoftenformpartofatool'suserinterface---aswiththediagnosticmessagesofhugeparamorthecodegeneratedbyskeleton---manyclientswantmorecontrolovertheformattingofpackagenames.Thego/typespackageprovidesthesealternativestotheStringmethods:funcObjectString(objObject,qfQualifier)string
funcTypeString(typType,qfQualifier)string
funcSelectionString(s*Selection,qfQualifier)string
typeQualifierfunc(*Package)string
TheTypeString,ObjectString,andSelectionStringfunctionsareliketheStringmethodsoftherespectivetypes,buttheyacceptanadditionalargument,aQualifier.AQualifierisaclient-providedfunctionthatdetermineshowapackagenameisrenderedasastring.Ifitisnil,thedefaultbehavioristoprintthepackage'spath,justliketheStringmethodsdo.Ifacallerpasses(*Package).Nameasthequalifier,thatis,afunctionthatacceptsapackageandreturnsitsName,thenobjectsarequalifiedonlybythepackagename.Theaboveexampleswouldlooklikethis:[]json.Marshaler
json.Marshal
(*json.Encoder).Encode
func(enc*json.Encoder)Encode(vinterface{})error
funcNewEncoder(wio.Writer)*json.Encoder
Oftenwhenatoolprintssomeoutput,itisimplicitlyinthecontextofaparticularpackage,perhapsonespecifiedbythecommandlineorHTTPrequest.Inthatcase,itismorenaturaltoomitthepackagequalificationaltogetherforobjectsbelongingtothatpackage,buttoqualifyallotherobjectsbytheirpackage‘spath.That’swhattheRelativeTo(pkg)qualifierdoes:funcRelativeTo(pkg*Package)Qualifier
Theexamplesbelowshowhowjson.NewEncoderwouldbeprintedusingthreequalifiers,eachrelativetoadifferentpackage://RelativeTo"encoding/json":
funcNewEncoder(wio.Writer)*Encoder
//RelativeTo"io":
funcNewEncoder(wWriter)*encoding/json.Encoder
//RelativeToanyotherpackage:
funcNewEncoder(wio.Writer)*encoding/json.Encoder
Anotherqualifierthatmayberelevanttorefactoringtools(butisnotcurrentlyprovidedbythetypechecker)isonethatrenderseachpackagenameusingthelocallyappropriatenamewithinagivensourcefile.Itsbehaviorwoulddependonthesetofimportdeclarations,includingrenamingimports,withinthatsourcefile.GettingfromAtoBThetypecheckeranditsrelatedpackagesrepresentmanyaspectsofaGoprograminmanydifferentways,andanalysistoolsmustoftenmapbetweenthem.Forinstance,anamedentitymaybeidentifiedbyitsObject;byitsdeclaringidentifier(ast.Ident)orbyanyreferringidentifier;byitsdeclaringast.Node;bytheposition(token.Pos)ofanythosenodes;orbythefilenameandline/columnnumber(orbyteoffset)ofthosetoken.Posvalues.Inthissection,we'lllistsolutionstoanumberofcommonproblemsoftheform“IhaveanA;IneedthecorrespondingB”.Tomapfromatoken.Postoanast.Node,callthehelperfunctionastutil.PathEnclosingInterval.Itreturnstheenclosingast.Node,andallitsancestorsuptotherootofthefile.Youmustknowwhichfile*ast.Filethetoken.Posbelongsto.Alternatively,youcansearchanentireprogramloadedbytheloaderpackage,using(*loader.Program).PathEnclosingInterval.TomapfromanObjecttoitsdeclaringsyntax,callPostogetitsposition,thenusePathEnclosingIntervalasbefore.Thisapproachissuitableforaone-offquery.Forrepeateduse,itmaybemoreefficienttovisitthesyntaxtreeandconstructthemappingbetweendeclarationsandobjects.Tomapfromanast.IdenttotheObjectitrefersto(ordeclares),consulttheUsesorDefsmapforthepackage,asshowninIdentifierResolution.TomapfromanObjecttoitsdocumentation,findtheobject‘sdeclaration,andlookattheattachedDocfield.Youmusthavesettheparser’sParseCommentsflag.SeethedocexampleinImports.