gotypes - example - Git at Google

文章推薦指數: 80 %
投票人數:10人

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.



請為這篇文章評分?