1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535 |
- from __future__ import absolute_import
- import cython
- cython.declare(PyrexTypes=object, Naming=object, ExprNodes=object, Nodes=object,
- Options=object, UtilNodes=object, LetNode=object,
- LetRefNode=object, TreeFragment=object, EncodedString=object,
- error=object, warning=object, copy=object, hashlib=object, sys=object,
- _unicode=object)
- import copy
- import hashlib
- import sys
- from . import PyrexTypes
- from . import Naming
- from . import ExprNodes
- from . import Nodes
- from . import Options
- from . import Builtin
- from . import Errors
- from .Visitor import VisitorTransform, TreeVisitor
- from .Visitor import CythonTransform, EnvTransform, ScopeTrackingTransform
- from .UtilNodes import LetNode, LetRefNode
- from .TreeFragment import TreeFragment
- from .StringEncoding import EncodedString, _unicode
- from .Errors import error, warning, CompileError, InternalError
- from .Code import UtilityCode
- class SkipDeclarations(object):
- """
- Variable and function declarations can often have a deep tree structure,
- and yet most transformations don't need to descend to this depth.
- Declaration nodes are removed after AnalyseDeclarationsTransform, so there
- is no need to use this for transformations after that point.
- """
- def visit_CTypeDefNode(self, node):
- return node
- def visit_CVarDefNode(self, node):
- return node
- def visit_CDeclaratorNode(self, node):
- return node
- def visit_CBaseTypeNode(self, node):
- return node
- def visit_CEnumDefNode(self, node):
- return node
- def visit_CStructOrUnionDefNode(self, node):
- return node
- class NormalizeTree(CythonTransform):
- """
- This transform fixes up a few things after parsing
- in order to make the parse tree more suitable for
- transforms.
- a) After parsing, blocks with only one statement will
- be represented by that statement, not by a StatListNode.
- When doing transforms this is annoying and inconsistent,
- as one cannot in general remove a statement in a consistent
- way and so on. This transform wraps any single statements
- in a StatListNode containing a single statement.
- b) The PassStatNode is a noop and serves no purpose beyond
- plugging such one-statement blocks; i.e., once parsed a
- ` "pass" can just as well be represented using an empty
- StatListNode. This means less special cases to worry about
- in subsequent transforms (one always checks to see if a
- StatListNode has no children to see if the block is empty).
- """
- def __init__(self, context):
- super(NormalizeTree, self).__init__(context)
- self.is_in_statlist = False
- self.is_in_expr = False
- def visit_ExprNode(self, node):
- stacktmp = self.is_in_expr
- self.is_in_expr = True
- self.visitchildren(node)
- self.is_in_expr = stacktmp
- return node
- def visit_StatNode(self, node, is_listcontainer=False):
- stacktmp = self.is_in_statlist
- self.is_in_statlist = is_listcontainer
- self.visitchildren(node)
- self.is_in_statlist = stacktmp
- if not self.is_in_statlist and not self.is_in_expr:
- return Nodes.StatListNode(pos=node.pos, stats=[node])
- else:
- return node
- def visit_StatListNode(self, node):
- self.is_in_statlist = True
- self.visitchildren(node)
- self.is_in_statlist = False
- return node
- def visit_ParallelAssignmentNode(self, node):
- return self.visit_StatNode(node, True)
- def visit_CEnumDefNode(self, node):
- return self.visit_StatNode(node, True)
- def visit_CStructOrUnionDefNode(self, node):
- return self.visit_StatNode(node, True)
- def visit_PassStatNode(self, node):
- """Eliminate PassStatNode"""
- if not self.is_in_statlist:
- return Nodes.StatListNode(pos=node.pos, stats=[])
- else:
- return []
- def visit_ExprStatNode(self, node):
- """Eliminate useless string literals"""
- if node.expr.is_string_literal:
- return self.visit_PassStatNode(node)
- else:
- return self.visit_StatNode(node)
- def visit_CDeclaratorNode(self, node):
- return node
- class PostParseError(CompileError): pass
- # error strings checked by unit tests, so define them
- ERR_CDEF_INCLASS = 'Cannot assign default value to fields in cdef classes, structs or unions'
- ERR_BUF_DEFAULTS = 'Invalid buffer defaults specification (see docs)'
- ERR_INVALID_SPECIALATTR_TYPE = 'Special attributes must not have a type declared'
- class PostParse(ScopeTrackingTransform):
- """
- Basic interpretation of the parse tree, as well as validity
- checking that can be done on a very basic level on the parse
- tree (while still not being a problem with the basic syntax,
- as such).
- Specifically:
- - Default values to cdef assignments are turned into single
- assignments following the declaration (everywhere but in class
- bodies, where they raise a compile error)
- - Interpret some node structures into Python runtime values.
- Some nodes take compile-time arguments (currently:
- TemplatedTypeNode[args] and __cythonbufferdefaults__ = {args}),
- which should be interpreted. This happens in a general way
- and other steps should be taken to ensure validity.
- Type arguments cannot be interpreted in this way.
- - For __cythonbufferdefaults__ the arguments are checked for
- validity.
- TemplatedTypeNode has its directives interpreted:
- Any first positional argument goes into the "dtype" attribute,
- any "ndim" keyword argument goes into the "ndim" attribute and
- so on. Also it is checked that the directive combination is valid.
- - __cythonbufferdefaults__ attributes are parsed and put into the
- type information.
- Note: Currently Parsing.py does a lot of interpretation and
- reorganization that can be refactored into this transform
- if a more pure Abstract Syntax Tree is wanted.
- """
- def __init__(self, context):
- super(PostParse, self).__init__(context)
- self.specialattribute_handlers = {
- '__cythonbufferdefaults__' : self.handle_bufferdefaults
- }
- def visit_LambdaNode(self, node):
- # unpack a lambda expression into the corresponding DefNode
- collector = YieldNodeCollector()
- collector.visitchildren(node.result_expr)
- if collector.has_yield or collector.has_await or isinstance(node.result_expr, ExprNodes.YieldExprNode):
- body = Nodes.ExprStatNode(
- node.result_expr.pos, expr=node.result_expr)
- else:
- body = Nodes.ReturnStatNode(
- node.result_expr.pos, value=node.result_expr)
- node.def_node = Nodes.DefNode(
- node.pos, name=node.name,
- args=node.args, star_arg=node.star_arg,
- starstar_arg=node.starstar_arg,
- body=body, doc=None)
- self.visitchildren(node)
- return node
- def visit_GeneratorExpressionNode(self, node):
- # unpack a generator expression into the corresponding DefNode
- collector = YieldNodeCollector()
- collector.visitchildren(node.loop)
- node.def_node = Nodes.DefNode(
- node.pos, name=node.name, doc=None,
- args=[], star_arg=None, starstar_arg=None,
- body=node.loop, is_async_def=collector.has_await)
- self.visitchildren(node)
- return node
- def visit_ComprehensionNode(self, node):
- # enforce local scope also in Py2 for async generators (seriously, that's a Py3.6 feature...)
- if not node.has_local_scope:
- collector = YieldNodeCollector()
- collector.visitchildren(node.loop)
- if collector.has_await:
- node.has_local_scope = True
- self.visitchildren(node)
- return node
- # cdef variables
- def handle_bufferdefaults(self, decl):
- if not isinstance(decl.default, ExprNodes.DictNode):
- raise PostParseError(decl.pos, ERR_BUF_DEFAULTS)
- self.scope_node.buffer_defaults_node = decl.default
- self.scope_node.buffer_defaults_pos = decl.pos
- def visit_CVarDefNode(self, node):
- # This assumes only plain names and pointers are assignable on
- # declaration. Also, it makes use of the fact that a cdef decl
- # must appear before the first use, so we don't have to deal with
- # "i = 3; cdef int i = i" and can simply move the nodes around.
- try:
- self.visitchildren(node)
- stats = [node]
- newdecls = []
- for decl in node.declarators:
- declbase = decl
- while isinstance(declbase, Nodes.CPtrDeclaratorNode):
- declbase = declbase.base
- if isinstance(declbase, Nodes.CNameDeclaratorNode):
- if declbase.default is not None:
- if self.scope_type in ('cclass', 'pyclass', 'struct'):
- if isinstance(self.scope_node, Nodes.CClassDefNode):
- handler = self.specialattribute_handlers.get(decl.name)
- if handler:
- if decl is not declbase:
- raise PostParseError(decl.pos, ERR_INVALID_SPECIALATTR_TYPE)
- handler(decl)
- continue # Remove declaration
- raise PostParseError(decl.pos, ERR_CDEF_INCLASS)
- first_assignment = self.scope_type != 'module'
- stats.append(Nodes.SingleAssignmentNode(node.pos,
- lhs=ExprNodes.NameNode(node.pos, name=declbase.name),
- rhs=declbase.default, first=first_assignment))
- declbase.default = None
- newdecls.append(decl)
- node.declarators = newdecls
- return stats
- except PostParseError as e:
- # An error in a cdef clause is ok, simply remove the declaration
- # and try to move on to report more errors
- self.context.nonfatal_error(e)
- return None
- # Split parallel assignments (a,b = b,a) into separate partial
- # assignments that are executed rhs-first using temps. This
- # restructuring must be applied before type analysis so that known
- # types on rhs and lhs can be matched directly. It is required in
- # the case that the types cannot be coerced to a Python type in
- # order to assign from a tuple.
- def visit_SingleAssignmentNode(self, node):
- self.visitchildren(node)
- return self._visit_assignment_node(node, [node.lhs, node.rhs])
- def visit_CascadedAssignmentNode(self, node):
- self.visitchildren(node)
- return self._visit_assignment_node(node, node.lhs_list + [node.rhs])
- def _visit_assignment_node(self, node, expr_list):
- """Flatten parallel assignments into separate single
- assignments or cascaded assignments.
- """
- if sum([ 1 for expr in expr_list
- if expr.is_sequence_constructor or expr.is_string_literal ]) < 2:
- # no parallel assignments => nothing to do
- return node
- expr_list_list = []
- flatten_parallel_assignments(expr_list, expr_list_list)
- temp_refs = []
- eliminate_rhs_duplicates(expr_list_list, temp_refs)
- nodes = []
- for expr_list in expr_list_list:
- lhs_list = expr_list[:-1]
- rhs = expr_list[-1]
- if len(lhs_list) == 1:
- node = Nodes.SingleAssignmentNode(rhs.pos,
- lhs = lhs_list[0], rhs = rhs)
- else:
- node = Nodes.CascadedAssignmentNode(rhs.pos,
- lhs_list = lhs_list, rhs = rhs)
- nodes.append(node)
- if len(nodes) == 1:
- assign_node = nodes[0]
- else:
- assign_node = Nodes.ParallelAssignmentNode(nodes[0].pos, stats = nodes)
- if temp_refs:
- duplicates_and_temps = [ (temp.expression, temp)
- for temp in temp_refs ]
- sort_common_subsequences(duplicates_and_temps)
- for _, temp_ref in duplicates_and_temps[::-1]:
- assign_node = LetNode(temp_ref, assign_node)
- return assign_node
- def _flatten_sequence(self, seq, result):
- for arg in seq.args:
- if arg.is_sequence_constructor:
- self._flatten_sequence(arg, result)
- else:
- result.append(arg)
- return result
- def visit_DelStatNode(self, node):
- self.visitchildren(node)
- node.args = self._flatten_sequence(node, [])
- return node
- def visit_ExceptClauseNode(self, node):
- if node.is_except_as:
- # except-as must delete NameNode target at the end
- del_target = Nodes.DelStatNode(
- node.pos,
- args=[ExprNodes.NameNode(
- node.target.pos, name=node.target.name)],
- ignore_nonexisting=True)
- node.body = Nodes.StatListNode(
- node.pos,
- stats=[Nodes.TryFinallyStatNode(
- node.pos,
- body=node.body,
- finally_clause=Nodes.StatListNode(
- node.pos,
- stats=[del_target]))])
- self.visitchildren(node)
- return node
- def eliminate_rhs_duplicates(expr_list_list, ref_node_sequence):
- """Replace rhs items by LetRefNodes if they appear more than once.
- Creates a sequence of LetRefNodes that set up the required temps
- and appends them to ref_node_sequence. The input list is modified
- in-place.
- """
- seen_nodes = set()
- ref_nodes = {}
- def find_duplicates(node):
- if node.is_literal or node.is_name:
- # no need to replace those; can't include attributes here
- # as their access is not necessarily side-effect free
- return
- if node in seen_nodes:
- if node not in ref_nodes:
- ref_node = LetRefNode(node)
- ref_nodes[node] = ref_node
- ref_node_sequence.append(ref_node)
- else:
- seen_nodes.add(node)
- if node.is_sequence_constructor:
- for item in node.args:
- find_duplicates(item)
- for expr_list in expr_list_list:
- rhs = expr_list[-1]
- find_duplicates(rhs)
- if not ref_nodes:
- return
- def substitute_nodes(node):
- if node in ref_nodes:
- return ref_nodes[node]
- elif node.is_sequence_constructor:
- node.args = list(map(substitute_nodes, node.args))
- return node
- # replace nodes inside of the common subexpressions
- for node in ref_nodes:
- if node.is_sequence_constructor:
- node.args = list(map(substitute_nodes, node.args))
- # replace common subexpressions on all rhs items
- for expr_list in expr_list_list:
- expr_list[-1] = substitute_nodes(expr_list[-1])
- def sort_common_subsequences(items):
- """Sort items/subsequences so that all items and subsequences that
- an item contains appear before the item itself. This is needed
- because each rhs item must only be evaluated once, so its value
- must be evaluated first and then reused when packing sequences
- that contain it.
- This implies a partial order, and the sort must be stable to
- preserve the original order as much as possible, so we use a
- simple insertion sort (which is very fast for short sequences, the
- normal case in practice).
- """
- def contains(seq, x):
- for item in seq:
- if item is x:
- return True
- elif item.is_sequence_constructor and contains(item.args, x):
- return True
- return False
- def lower_than(a,b):
- return b.is_sequence_constructor and contains(b.args, a)
- for pos, item in enumerate(items):
- key = item[1] # the ResultRefNode which has already been injected into the sequences
- new_pos = pos
- for i in range(pos-1, -1, -1):
- if lower_than(key, items[i][0]):
- new_pos = i
- if new_pos != pos:
- for i in range(pos, new_pos, -1):
- items[i] = items[i-1]
- items[new_pos] = item
- def unpack_string_to_character_literals(literal):
- chars = []
- pos = literal.pos
- stype = literal.__class__
- sval = literal.value
- sval_type = sval.__class__
- for char in sval:
- cval = sval_type(char)
- chars.append(stype(pos, value=cval, constant_result=cval))
- return chars
- def flatten_parallel_assignments(input, output):
- # The input is a list of expression nodes, representing the LHSs
- # and RHS of one (possibly cascaded) assignment statement. For
- # sequence constructors, rearranges the matching parts of both
- # sides into a list of equivalent assignments between the
- # individual elements. This transformation is applied
- # recursively, so that nested structures get matched as well.
- rhs = input[-1]
- if (not (rhs.is_sequence_constructor or isinstance(rhs, ExprNodes.UnicodeNode))
- or not sum([lhs.is_sequence_constructor for lhs in input[:-1]])):
- output.append(input)
- return
- complete_assignments = []
- if rhs.is_sequence_constructor:
- rhs_args = rhs.args
- elif rhs.is_string_literal:
- rhs_args = unpack_string_to_character_literals(rhs)
- rhs_size = len(rhs_args)
- lhs_targets = [[] for _ in range(rhs_size)]
- starred_assignments = []
- for lhs in input[:-1]:
- if not lhs.is_sequence_constructor:
- if lhs.is_starred:
- error(lhs.pos, "starred assignment target must be in a list or tuple")
- complete_assignments.append(lhs)
- continue
- lhs_size = len(lhs.args)
- starred_targets = sum([1 for expr in lhs.args if expr.is_starred])
- if starred_targets > 1:
- error(lhs.pos, "more than 1 starred expression in assignment")
- output.append([lhs,rhs])
- continue
- elif lhs_size - starred_targets > rhs_size:
- error(lhs.pos, "need more than %d value%s to unpack"
- % (rhs_size, (rhs_size != 1) and 's' or ''))
- output.append([lhs,rhs])
- continue
- elif starred_targets:
- map_starred_assignment(lhs_targets, starred_assignments,
- lhs.args, rhs_args)
- elif lhs_size < rhs_size:
- error(lhs.pos, "too many values to unpack (expected %d, got %d)"
- % (lhs_size, rhs_size))
- output.append([lhs,rhs])
- continue
- else:
- for targets, expr in zip(lhs_targets, lhs.args):
- targets.append(expr)
- if complete_assignments:
- complete_assignments.append(rhs)
- output.append(complete_assignments)
- # recursively flatten partial assignments
- for cascade, rhs in zip(lhs_targets, rhs_args):
- if cascade:
- cascade.append(rhs)
- flatten_parallel_assignments(cascade, output)
- # recursively flatten starred assignments
- for cascade in starred_assignments:
- if cascade[0].is_sequence_constructor:
- flatten_parallel_assignments(cascade, output)
- else:
- output.append(cascade)
- def map_starred_assignment(lhs_targets, starred_assignments, lhs_args, rhs_args):
- # Appends the fixed-position LHS targets to the target list that
- # appear left and right of the starred argument.
- #
- # The starred_assignments list receives a new tuple
- # (lhs_target, rhs_values_list) that maps the remaining arguments
- # (those that match the starred target) to a list.
- # left side of the starred target
- for i, (targets, expr) in enumerate(zip(lhs_targets, lhs_args)):
- if expr.is_starred:
- starred = i
- lhs_remaining = len(lhs_args) - i - 1
- break
- targets.append(expr)
- else:
- raise InternalError("no starred arg found when splitting starred assignment")
- # right side of the starred target
- for i, (targets, expr) in enumerate(zip(lhs_targets[-lhs_remaining:],
- lhs_args[starred + 1:])):
- targets.append(expr)
- # the starred target itself, must be assigned a (potentially empty) list
- target = lhs_args[starred].target # unpack starred node
- starred_rhs = rhs_args[starred:]
- if lhs_remaining:
- starred_rhs = starred_rhs[:-lhs_remaining]
- if starred_rhs:
- pos = starred_rhs[0].pos
- else:
- pos = target.pos
- starred_assignments.append([
- target, ExprNodes.ListNode(pos=pos, args=starred_rhs)])
- class PxdPostParse(CythonTransform, SkipDeclarations):
- """
- Basic interpretation/validity checking that should only be
- done on pxd trees.
- A lot of this checking currently happens in the parser; but
- what is listed below happens here.
- - "def" functions are let through only if they fill the
- getbuffer/releasebuffer slots
- - cdef functions are let through only if they are on the
- top level and are declared "inline"
- """
- ERR_INLINE_ONLY = "function definition in pxd file must be declared 'cdef inline'"
- ERR_NOGO_WITH_INLINE = "inline function definition in pxd file cannot be '%s'"
- def __call__(self, node):
- self.scope_type = 'pxd'
- return super(PxdPostParse, self).__call__(node)
- def visit_CClassDefNode(self, node):
- old = self.scope_type
- self.scope_type = 'cclass'
- self.visitchildren(node)
- self.scope_type = old
- return node
- def visit_FuncDefNode(self, node):
- # FuncDefNode always come with an implementation (without
- # an imp they are CVarDefNodes..)
- err = self.ERR_INLINE_ONLY
- if (isinstance(node, Nodes.DefNode) and self.scope_type == 'cclass'
- and node.name in ('__getbuffer__', '__releasebuffer__')):
- err = None # allow these slots
- if isinstance(node, Nodes.CFuncDefNode):
- if (u'inline' in node.modifiers and
- self.scope_type in ('pxd', 'cclass')):
- node.inline_in_pxd = True
- if node.visibility != 'private':
- err = self.ERR_NOGO_WITH_INLINE % node.visibility
- elif node.api:
- err = self.ERR_NOGO_WITH_INLINE % 'api'
- else:
- err = None # allow inline function
- else:
- err = self.ERR_INLINE_ONLY
- if err:
- self.context.nonfatal_error(PostParseError(node.pos, err))
- return None
- else:
- return node
- class TrackNumpyAttributes(VisitorTransform, SkipDeclarations):
- # TODO: Make name handling as good as in InterpretCompilerDirectives() below - probably best to merge the two.
- def __init__(self):
- super(TrackNumpyAttributes, self).__init__()
- self.numpy_module_names = set()
- def visit_CImportStatNode(self, node):
- if node.module_name == u"numpy":
- self.numpy_module_names.add(node.as_name or u"numpy")
- return node
- def visit_AttributeNode(self, node):
- self.visitchildren(node)
- obj = node.obj
- if (obj.is_name and obj.name in self.numpy_module_names) or obj.is_numpy_attribute:
- node.is_numpy_attribute = True
- return node
- visit_Node = VisitorTransform.recurse_to_children
- class InterpretCompilerDirectives(CythonTransform):
- """
- After parsing, directives can be stored in a number of places:
- - #cython-comments at the top of the file (stored in ModuleNode)
- - Command-line arguments overriding these
- - @cython.directivename decorators
- - with cython.directivename: statements
- This transform is responsible for interpreting these various sources
- and store the directive in two ways:
- - Set the directives attribute of the ModuleNode for global directives.
- - Use a CompilerDirectivesNode to override directives for a subtree.
- (The first one is primarily to not have to modify with the tree
- structure, so that ModuleNode stay on top.)
- The directives are stored in dictionaries from name to value in effect.
- Each such dictionary is always filled in for all possible directives,
- using default values where no value is given by the user.
- The available directives are controlled in Options.py.
- Note that we have to run this prior to analysis, and so some minor
- duplication of functionality has to occur: We manually track cimports
- and which names the "cython" module may have been imported to.
- """
- unop_method_nodes = {
- 'typeof': ExprNodes.TypeofNode,
- 'operator.address': ExprNodes.AmpersandNode,
- 'operator.dereference': ExprNodes.DereferenceNode,
- 'operator.preincrement' : ExprNodes.inc_dec_constructor(True, '++'),
- 'operator.predecrement' : ExprNodes.inc_dec_constructor(True, '--'),
- 'operator.postincrement': ExprNodes.inc_dec_constructor(False, '++'),
- 'operator.postdecrement': ExprNodes.inc_dec_constructor(False, '--'),
- 'operator.typeid' : ExprNodes.TypeidNode,
- # For backwards compatibility.
- 'address': ExprNodes.AmpersandNode,
- }
- binop_method_nodes = {
- 'operator.comma' : ExprNodes.c_binop_constructor(','),
- }
- special_methods = set(['declare', 'union', 'struct', 'typedef',
- 'sizeof', 'cast', 'pointer', 'compiled',
- 'NULL', 'fused_type', 'parallel'])
- special_methods.update(unop_method_nodes)
- valid_parallel_directives = set([
- "parallel",
- "prange",
- "threadid",
- #"threadsavailable",
- ])
- def __init__(self, context, compilation_directive_defaults):
- super(InterpretCompilerDirectives, self).__init__(context)
- self.cython_module_names = set()
- self.directive_names = {'staticmethod': 'staticmethod'}
- self.parallel_directives = {}
- directives = copy.deepcopy(Options.get_directive_defaults())
- for key, value in compilation_directive_defaults.items():
- directives[_unicode(key)] = copy.deepcopy(value)
- self.directives = directives
- def check_directive_scope(self, pos, directive, scope):
- legal_scopes = Options.directive_scopes.get(directive, None)
- if legal_scopes and scope not in legal_scopes:
- self.context.nonfatal_error(PostParseError(pos, 'The %s compiler directive '
- 'is not allowed in %s scope' % (directive, scope)))
- return False
- else:
- if directive not in Options.directive_types:
- error(pos, "Invalid directive: '%s'." % (directive,))
- return True
- # Set up processing and handle the cython: comments.
- def visit_ModuleNode(self, node):
- for key in sorted(node.directive_comments):
- if not self.check_directive_scope(node.pos, key, 'module'):
- self.wrong_scope_error(node.pos, key, 'module')
- del node.directive_comments[key]
- self.module_scope = node.scope
- self.directives.update(node.directive_comments)
- node.directives = self.directives
- node.parallel_directives = self.parallel_directives
- self.visitchildren(node)
- node.cython_module_names = self.cython_module_names
- return node
- # The following four functions track imports and cimports that
- # begin with "cython"
- def is_cython_directive(self, name):
- return (name in Options.directive_types or
- name in self.special_methods or
- PyrexTypes.parse_basic_type(name))
- def is_parallel_directive(self, full_name, pos):
- """
- Checks to see if fullname (e.g. cython.parallel.prange) is a valid
- parallel directive. If it is a star import it also updates the
- parallel_directives.
- """
- result = (full_name + ".").startswith("cython.parallel.")
- if result:
- directive = full_name.split('.')
- if full_name == u"cython.parallel":
- self.parallel_directives[u"parallel"] = u"cython.parallel"
- elif full_name == u"cython.parallel.*":
- for name in self.valid_parallel_directives:
- self.parallel_directives[name] = u"cython.parallel.%s" % name
- elif (len(directive) != 3 or
- directive[-1] not in self.valid_parallel_directives):
- error(pos, "No such directive: %s" % full_name)
- self.module_scope.use_utility_code(
- UtilityCode.load_cached("InitThreads", "ModuleSetupCode.c"))
- return result
- def visit_CImportStatNode(self, node):
- if node.module_name == u"cython":
- self.cython_module_names.add(node.as_name or u"cython")
- elif node.module_name.startswith(u"cython."):
- if node.module_name.startswith(u"cython.parallel."):
- error(node.pos, node.module_name + " is not a module")
- if node.module_name == u"cython.parallel":
- if node.as_name and node.as_name != u"cython":
- self.parallel_directives[node.as_name] = node.module_name
- else:
- self.cython_module_names.add(u"cython")
- self.parallel_directives[
- u"cython.parallel"] = node.module_name
- self.module_scope.use_utility_code(
- UtilityCode.load_cached("InitThreads", "ModuleSetupCode.c"))
- elif node.as_name:
- self.directive_names[node.as_name] = node.module_name[7:]
- else:
- self.cython_module_names.add(u"cython")
- # if this cimport was a compiler directive, we don't
- # want to leave the cimport node sitting in the tree
- return None
- return node
- def visit_FromCImportStatNode(self, node):
- if not node.relative_level and (
- node.module_name == u"cython" or node.module_name.startswith(u"cython.")):
- submodule = (node.module_name + u".")[7:]
- newimp = []
- for pos, name, as_name, kind in node.imported_names:
- full_name = submodule + name
- qualified_name = u"cython." + full_name
- if self.is_parallel_directive(qualified_name, node.pos):
- # from cython cimport parallel, or
- # from cython.parallel cimport parallel, prange, ...
- self.parallel_directives[as_name or name] = qualified_name
- elif self.is_cython_directive(full_name):
- self.directive_names[as_name or name] = full_name
- if kind is not None:
- self.context.nonfatal_error(PostParseError(pos,
- "Compiler directive imports must be plain imports"))
- else:
- newimp.append((pos, name, as_name, kind))
- if not newimp:
- return None
- node.imported_names = newimp
- return node
- def visit_FromImportStatNode(self, node):
- if (node.module.module_name.value == u"cython") or \
- node.module.module_name.value.startswith(u"cython."):
- submodule = (node.module.module_name.value + u".")[7:]
- newimp = []
- for name, name_node in node.items:
- full_name = submodule + name
- qualified_name = u"cython." + full_name
- if self.is_parallel_directive(qualified_name, node.pos):
- self.parallel_directives[name_node.name] = qualified_name
- elif self.is_cython_directive(full_name):
- self.directive_names[name_node.name] = full_name
- else:
- newimp.append((name, name_node))
- if not newimp:
- return None
- node.items = newimp
- return node
- def visit_SingleAssignmentNode(self, node):
- if isinstance(node.rhs, ExprNodes.ImportNode):
- module_name = node.rhs.module_name.value
- is_parallel = (module_name + u".").startswith(u"cython.parallel.")
- if module_name != u"cython" and not is_parallel:
- return node
- module_name = node.rhs.module_name.value
- as_name = node.lhs.name
- node = Nodes.CImportStatNode(node.pos,
- module_name = module_name,
- as_name = as_name)
- node = self.visit_CImportStatNode(node)
- else:
- self.visitchildren(node)
- return node
- def visit_NameNode(self, node):
- if node.name in self.cython_module_names:
- node.is_cython_module = True
- else:
- directive = self.directive_names.get(node.name)
- if directive is not None:
- node.cython_attribute = directive
- return node
- def visit_NewExprNode(self, node):
- self.visit(node.cppclass)
- self.visitchildren(node)
- return node
- def try_to_parse_directives(self, node):
- # If node is the contents of an directive (in a with statement or
- # decorator), returns a list of (directivename, value) pairs.
- # Otherwise, returns None
- if isinstance(node, ExprNodes.CallNode):
- self.visit(node.function)
- optname = node.function.as_cython_attribute()
- if optname:
- directivetype = Options.directive_types.get(optname)
- if directivetype:
- args, kwds = node.explicit_args_kwds()
- directives = []
- key_value_pairs = []
- if kwds is not None and directivetype is not dict:
- for keyvalue in kwds.key_value_pairs:
- key, value = keyvalue
- sub_optname = "%s.%s" % (optname, key.value)
- if Options.directive_types.get(sub_optname):
- directives.append(self.try_to_parse_directive(sub_optname, [value], None, keyvalue.pos))
- else:
- key_value_pairs.append(keyvalue)
- if not key_value_pairs:
- kwds = None
- else:
- kwds.key_value_pairs = key_value_pairs
- if directives and not kwds and not args:
- return directives
- directives.append(self.try_to_parse_directive(optname, args, kwds, node.function.pos))
- return directives
- elif isinstance(node, (ExprNodes.AttributeNode, ExprNodes.NameNode)):
- self.visit(node)
- optname = node.as_cython_attribute()
- if optname:
- directivetype = Options.directive_types.get(optname)
- if directivetype is bool:
- arg = ExprNodes.BoolNode(node.pos, value=True)
- return [self.try_to_parse_directive(optname, [arg], None, node.pos)]
- elif directivetype is None:
- return [(optname, None)]
- else:
- raise PostParseError(
- node.pos, "The '%s' directive should be used as a function call." % optname)
- return None
- def try_to_parse_directive(self, optname, args, kwds, pos):
- if optname == 'np_pythran' and not self.context.cpp:
- raise PostParseError(pos, 'The %s directive can only be used in C++ mode.' % optname)
- elif optname == 'exceptval':
- # default: exceptval(None, check=True)
- arg_error = len(args) > 1
- check = True
- if kwds and kwds.key_value_pairs:
- kw = kwds.key_value_pairs[0]
- if (len(kwds.key_value_pairs) == 1 and
- kw.key.is_string_literal and kw.key.value == 'check' and
- isinstance(kw.value, ExprNodes.BoolNode)):
- check = kw.value.value
- else:
- arg_error = True
- if arg_error:
- raise PostParseError(
- pos, 'The exceptval directive takes 0 or 1 positional arguments and the boolean keyword "check"')
- return ('exceptval', (args[0] if args else None, check))
- directivetype = Options.directive_types.get(optname)
- if len(args) == 1 and isinstance(args[0], ExprNodes.NoneNode):
- return optname, Options.get_directive_defaults()[optname]
- elif directivetype is bool:
- if kwds is not None or len(args) != 1 or not isinstance(args[0], ExprNodes.BoolNode):
- raise PostParseError(pos,
- 'The %s directive takes one compile-time boolean argument' % optname)
- return (optname, args[0].value)
- elif directivetype is int:
- if kwds is not None or len(args) != 1 or not isinstance(args[0], ExprNodes.IntNode):
- raise PostParseError(pos,
- 'The %s directive takes one compile-time integer argument' % optname)
- return (optname, int(args[0].value))
- elif directivetype is str:
- if kwds is not None or len(args) != 1 or not isinstance(
- args[0], (ExprNodes.StringNode, ExprNodes.UnicodeNode)):
- raise PostParseError(pos,
- 'The %s directive takes one compile-time string argument' % optname)
- return (optname, str(args[0].value))
- elif directivetype is type:
- if kwds is not None or len(args) != 1:
- raise PostParseError(pos,
- 'The %s directive takes one type argument' % optname)
- return (optname, args[0])
- elif directivetype is dict:
- if len(args) != 0:
- raise PostParseError(pos,
- 'The %s directive takes no prepositional arguments' % optname)
- return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs])
- elif directivetype is list:
- if kwds and len(kwds.key_value_pairs) != 0:
- raise PostParseError(pos,
- 'The %s directive takes no keyword arguments' % optname)
- return optname, [ str(arg.value) for arg in args ]
- elif callable(directivetype):
- if kwds is not None or len(args) != 1 or not isinstance(
- args[0], (ExprNodes.StringNode, ExprNodes.UnicodeNode)):
- raise PostParseError(pos,
- 'The %s directive takes one compile-time string argument' % optname)
- return (optname, directivetype(optname, str(args[0].value)))
- else:
- assert False
- def visit_with_directives(self, node, directives):
- if not directives:
- return self.visit_Node(node)
- old_directives = self.directives
- new_directives = dict(old_directives)
- new_directives.update(directives)
- if new_directives == old_directives:
- return self.visit_Node(node)
- self.directives = new_directives
- retbody = self.visit_Node(node)
- self.directives = old_directives
- if not isinstance(retbody, Nodes.StatListNode):
- retbody = Nodes.StatListNode(node.pos, stats=[retbody])
- return Nodes.CompilerDirectivesNode(
- pos=retbody.pos, body=retbody, directives=new_directives)
- # Handle decorators
- def visit_FuncDefNode(self, node):
- directives = self._extract_directives(node, 'function')
- return self.visit_with_directives(node, directives)
- def visit_CVarDefNode(self, node):
- directives = self._extract_directives(node, 'function')
- for name, value in directives.items():
- if name == 'locals':
- node.directive_locals = value
- elif name not in ('final', 'staticmethod'):
- self.context.nonfatal_error(PostParseError(
- node.pos,
- "Cdef functions can only take cython.locals(), "
- "staticmethod, or final decorators, got %s." % name))
- return self.visit_with_directives(node, directives)
- def visit_CClassDefNode(self, node):
- directives = self._extract_directives(node, 'cclass')
- return self.visit_with_directives(node, directives)
- def visit_CppClassNode(self, node):
- directives = self._extract_directives(node, 'cppclass')
- return self.visit_with_directives(node, directives)
- def visit_PyClassDefNode(self, node):
- directives = self._extract_directives(node, 'class')
- return self.visit_with_directives(node, directives)
- def _extract_directives(self, node, scope_name):
- if not node.decorators:
- return {}
- # Split the decorators into two lists -- real decorators and directives
- directives = []
- realdecs = []
- both = []
- # Decorators coming first take precedence.
- for dec in node.decorators[::-1]:
- new_directives = self.try_to_parse_directives(dec.decorator)
- if new_directives is not None:
- for directive in new_directives:
- if self.check_directive_scope(node.pos, directive[0], scope_name):
- name, value = directive
- if self.directives.get(name, object()) != value:
- directives.append(directive)
- if directive[0] == 'staticmethod':
- both.append(dec)
- # Adapt scope type based on decorators that change it.
- if directive[0] == 'cclass' and scope_name == 'class':
- scope_name = 'cclass'
- else:
- realdecs.append(dec)
- if realdecs and (scope_name == 'cclass' or
- isinstance(node, (Nodes.CFuncDefNode, Nodes.CClassDefNode, Nodes.CVarDefNode))):
- raise PostParseError(realdecs[0].pos, "Cdef functions/classes cannot take arbitrary decorators.")
- node.decorators = realdecs[::-1] + both[::-1]
- # merge or override repeated directives
- optdict = {}
- for directive in directives:
- name, value = directive
- if name in optdict:
- old_value = optdict[name]
- # keywords and arg lists can be merged, everything
- # else overrides completely
- if isinstance(old_value, dict):
- old_value.update(value)
- elif isinstance(old_value, list):
- old_value.extend(value)
- else:
- optdict[name] = value
- else:
- optdict[name] = value
- return optdict
- # Handle with-statements
- def visit_WithStatNode(self, node):
- directive_dict = {}
- for directive in self.try_to_parse_directives(node.manager) or []:
- if directive is not None:
- if node.target is not None:
- self.context.nonfatal_error(
- PostParseError(node.pos, "Compiler directive with statements cannot contain 'as'"))
- else:
- name, value = directive
- if name in ('nogil', 'gil'):
- # special case: in pure mode, "with nogil" spells "with cython.nogil"
- node = Nodes.GILStatNode(node.pos, state = name, body = node.body)
- return self.visit_Node(node)
- if self.check_directive_scope(node.pos, name, 'with statement'):
- directive_dict[name] = value
- if directive_dict:
- return self.visit_with_directives(node.body, directive_dict)
- return self.visit_Node(node)
- class ParallelRangeTransform(CythonTransform, SkipDeclarations):
- """
- Transform cython.parallel stuff. The parallel_directives come from the
- module node, set there by InterpretCompilerDirectives.
- x = cython.parallel.threadavailable() -> ParallelThreadAvailableNode
- with nogil, cython.parallel.parallel(): -> ParallelWithBlockNode
- print cython.parallel.threadid() -> ParallelThreadIdNode
- for i in cython.parallel.prange(...): -> ParallelRangeNode
- ...
- """
- # a list of names, maps 'cython.parallel.prange' in the code to
- # ['cython', 'parallel', 'prange']
- parallel_directive = None
- # Indicates whether a namenode in an expression is the cython module
- namenode_is_cython_module = False
- # Keep track of whether we are the context manager of a 'with' statement
- in_context_manager_section = False
- # One of 'prange' or 'with parallel'. This is used to disallow closely
- # nested 'with parallel:' blocks
- state = None
- directive_to_node = {
- u"cython.parallel.parallel": Nodes.ParallelWithBlockNode,
- # u"cython.parallel.threadsavailable": ExprNodes.ParallelThreadsAvailableNode,
- u"cython.parallel.threadid": ExprNodes.ParallelThreadIdNode,
- u"cython.parallel.prange": Nodes.ParallelRangeNode,
- }
- def node_is_parallel_directive(self, node):
- return node.name in self.parallel_directives or node.is_cython_module
- def get_directive_class_node(self, node):
- """
- Figure out which parallel directive was used and return the associated
- Node class.
- E.g. for a cython.parallel.prange() call we return ParallelRangeNode
- """
- if self.namenode_is_cython_module:
- directive = '.'.join(self.parallel_directive)
- else:
- directive = self.parallel_directives[self.parallel_directive[0]]
- directive = '%s.%s' % (directive,
- '.'.join(self.parallel_directive[1:]))
- directive = directive.rstrip('.')
- cls = self.directive_to_node.get(directive)
- if cls is None and not (self.namenode_is_cython_module and
- self.parallel_directive[0] != 'parallel'):
- error(node.pos, "Invalid directive: %s" % directive)
- self.namenode_is_cython_module = False
- self.parallel_directive = None
- return cls
- def visit_ModuleNode(self, node):
- """
- If any parallel directives were imported, copy them over and visit
- the AST
- """
- if node.parallel_directives:
- self.parallel_directives = node.parallel_directives
- return self.visit_Node(node)
- # No parallel directives were imported, so they can't be used :)
- return node
- def visit_NameNode(self, node):
- if self.node_is_parallel_directive(node):
- self.parallel_directive = [node.name]
- self.namenode_is_cython_module = node.is_cython_module
- return node
- def visit_AttributeNode(self, node):
- self.visitchildren(node)
- if self.parallel_directive:
- self.parallel_directive.append(node.attribute)
- return node
- def visit_CallNode(self, node):
- self.visit(node.function)
- if not self.parallel_directive:
- self.visitchildren(node, exclude=('function',))
- return node
- # We are a parallel directive, replace this node with the
- # corresponding ParallelSomethingSomething node
- if isinstance(node, ExprNodes.GeneralCallNode):
- args = node.positional_args.args
- kwargs = node.keyword_args
- else:
- args = node.args
- kwargs = {}
- parallel_directive_class = self.get_directive_class_node(node)
- if parallel_directive_class:
- # Note: in case of a parallel() the body is set by
- # visit_WithStatNode
- node = parallel_directive_class(node.pos, args=args, kwargs=kwargs)
- return node
- def visit_WithStatNode(self, node):
- "Rewrite with cython.parallel.parallel() blocks"
- newnode = self.visit(node.manager)
- if isinstance(newnode, Nodes.ParallelWithBlockNode):
- if self.state == 'parallel with':
- error(node.manager.pos,
- "Nested parallel with blocks are disallowed")
- self.state = 'parallel with'
- body = self.visit(node.body)
- self.state = None
- newnode.body = body
- return newnode
- elif self.parallel_directive:
- parallel_directive_class = self.get_directive_class_node(node)
- if not parallel_directive_class:
- # There was an error, stop here and now
- return None
- if parallel_directive_class is Nodes.ParallelWithBlockNode:
- error(node.pos, "The parallel directive must be called")
- return None
- node.body = self.visit(node.body)
- return node
- def visit_ForInStatNode(self, node):
- "Rewrite 'for i in cython.parallel.prange(...):'"
- self.visit(node.iterator)
- self.visit(node.target)
- in_prange = isinstance(node.iterator.sequence,
- Nodes.ParallelRangeNode)
- previous_state = self.state
- if in_prange:
- # This will replace the entire ForInStatNode, so copy the
- # attributes
- parallel_range_node = node.iterator.sequence
- parallel_range_node.target = node.target
- parallel_range_node.body = node.body
- parallel_range_node.else_clause = node.else_clause
- node = parallel_range_node
- if not isinstance(node.target, ExprNodes.NameNode):
- error(node.target.pos,
- "Can only iterate over an iteration variable")
- self.state = 'prange'
- self.visit(node.body)
- self.state = previous_state
- self.visit(node.else_clause)
- return node
- def visit(self, node):
- "Visit a node that may be None"
- if node is not None:
- return super(ParallelRangeTransform, self).visit(node)
- class WithTransform(CythonTransform, SkipDeclarations):
- def visit_WithStatNode(self, node):
- self.visitchildren(node, 'body')
- pos = node.pos
- is_async = node.is_async
- body, target, manager = node.body, node.target, node.manager
- node.enter_call = ExprNodes.SimpleCallNode(
- pos, function=ExprNodes.AttributeNode(
- pos, obj=ExprNodes.CloneNode(manager),
- attribute=EncodedString('__aenter__' if is_async else '__enter__'),
- is_special_lookup=True),
- args=[],
- is_temp=True)
- if is_async:
- node.enter_call = ExprNodes.AwaitExprNode(pos, arg=node.enter_call)
- if target is not None:
- body = Nodes.StatListNode(
- pos, stats=[
- Nodes.WithTargetAssignmentStatNode(
- pos, lhs=target, with_node=node),
- body])
- excinfo_target = ExprNodes.TupleNode(pos, slow=True, args=[
- ExprNodes.ExcValueNode(pos) for _ in range(3)])
- except_clause = Nodes.ExceptClauseNode(
- pos, body=Nodes.IfStatNode(
- pos, if_clauses=[
- Nodes.IfClauseNode(
- pos, condition=ExprNodes.NotNode(
- pos, operand=ExprNodes.WithExitCallNode(
- pos, with_stat=node,
- test_if_run=False,
- args=excinfo_target,
- await_expr=ExprNodes.AwaitExprNode(pos, arg=None) if is_async else None)),
- body=Nodes.ReraiseStatNode(pos),
- ),
- ],
- else_clause=None),
- pattern=None,
- target=None,
- excinfo_target=excinfo_target,
- )
- node.body = Nodes.TryFinallyStatNode(
- pos, body=Nodes.TryExceptStatNode(
- pos, body=body,
- except_clauses=[except_clause],
- else_clause=None,
- ),
- finally_clause=Nodes.ExprStatNode(
- pos, expr=ExprNodes.WithExitCallNode(
- pos, with_stat=node,
- test_if_run=True,
- args=ExprNodes.TupleNode(
- pos, args=[ExprNodes.NoneNode(pos) for _ in range(3)]),
- await_expr=ExprNodes.AwaitExprNode(pos, arg=None) if is_async else None)),
- handle_error_case=False,
- )
- return node
- def visit_ExprNode(self, node):
- # With statements are never inside expressions.
- return node
- class DecoratorTransform(ScopeTrackingTransform, SkipDeclarations):
- """
- Transforms method decorators in cdef classes into nested calls or properties.
- Python-style decorator properties are transformed into a PropertyNode
- with up to the three getter, setter and deleter DefNodes.
- The functional style isn't supported yet.
- """
- _properties = None
- _map_property_attribute = {
- 'getter': '__get__',
- 'setter': '__set__',
- 'deleter': '__del__',
- }.get
- def visit_CClassDefNode(self, node):
- if self._properties is None:
- self._properties = []
- self._properties.append({})
- super(DecoratorTransform, self).visit_CClassDefNode(node)
- self._properties.pop()
- return node
- def visit_PropertyNode(self, node):
- # Low-level warning for other code until we can convert all our uses over.
- level = 2 if isinstance(node.pos[0], str) else 0
- warning(node.pos, "'property %s:' syntax is deprecated, use '@property'" % node.name, level)
- return node
- def visit_DefNode(self, node):
- scope_type = self.scope_type
- node = self.visit_FuncDefNode(node)
- if scope_type != 'cclass' or not node.decorators:
- return node
- # transform @property decorators
- properties = self._properties[-1]
- for decorator_node in node.decorators[::-1]:
- decorator = decorator_node.decorator
- if decorator.is_name and decorator.name == 'property':
- if len(node.decorators) > 1:
- return self._reject_decorated_property(node, decorator_node)
- name = node.name
- node.name = EncodedString('__get__')
- node.decorators.remove(decorator_node)
- stat_list = [node]
- if name in properties:
- prop = properties[name]
- prop.pos = node.pos
- prop.doc = node.doc
- prop.body.stats = stat_list
- return []
- prop = Nodes.PropertyNode(node.pos, name=name)
- prop.doc = node.doc
- prop.body = Nodes.StatListNode(node.pos, stats=stat_list)
- properties[name] = prop
- return [prop]
- elif decorator.is_attribute and decorator.obj.name in properties:
- handler_name = self._map_property_attribute(decorator.attribute)
- if handler_name:
- if decorator.obj.name != node.name:
- # CPython does not generate an error or warning, but not something useful either.
- error(decorator_node.pos,
- "Mismatching property names, expected '%s', got '%s'" % (
- decorator.obj.name, node.name))
- elif len(node.decorators) > 1:
- return self._reject_decorated_property(node, decorator_node)
- else:
- return self._add_to_property(properties, node, handler_name, decorator_node)
- # we clear node.decorators, so we need to set the
- # is_staticmethod/is_classmethod attributes now
- for decorator in node.decorators:
- func = decorator.decorator
- if func.is_name:
- node.is_classmethod |= func.name == 'classmethod'
- node.is_staticmethod |= func.name == 'staticmethod'
- # transform normal decorators
- decs = node.decorators
- node.decorators = None
- return self.chain_decorators(node, decs, node.name)
- @staticmethod
- def _reject_decorated_property(node, decorator_node):
- # restrict transformation to outermost decorator as wrapped properties will probably not work
- for deco in node.decorators:
- if deco != decorator_node:
- error(deco.pos, "Property methods with additional decorators are not supported")
- return node
- @staticmethod
- def _add_to_property(properties, node, name, decorator):
- prop = properties[node.name]
- node.name = name
- node.decorators.remove(decorator)
- stats = prop.body.stats
- for i, stat in enumerate(stats):
- if stat.name == name:
- stats[i] = node
- break
- else:
- stats.append(node)
- return []
- @staticmethod
- def chain_decorators(node, decorators, name):
- """
- Decorators are applied directly in DefNode and PyClassDefNode to avoid
- reassignments to the function/class name - except for cdef class methods.
- For those, the reassignment is required as methods are originally
- defined in the PyMethodDef struct.
- The IndirectionNode allows DefNode to override the decorator.
- """
- decorator_result = ExprNodes.NameNode(node.pos, name=name)
- for decorator in decorators[::-1]:
- decorator_result = ExprNodes.SimpleCallNode(
- decorator.pos,
- function=decorator.decorator,
- args=[decorator_result])
- name_node = ExprNodes.NameNode(node.pos, name=name)
- reassignment = Nodes.SingleAssignmentNode(
- node.pos,
- lhs=name_node,
- rhs=decorator_result)
- reassignment = Nodes.IndirectionNode([reassignment])
- node.decorator_indirection = reassignment
- return [node, reassignment]
- class CnameDirectivesTransform(CythonTransform, SkipDeclarations):
- """
- Only part of the CythonUtilityCode pipeline. Must be run before
- DecoratorTransform in case this is a decorator for a cdef class.
- It filters out @cname('my_cname') decorators and rewrites them to
- CnameDecoratorNodes.
- """
- def handle_function(self, node):
- if not getattr(node, 'decorators', None):
- return self.visit_Node(node)
- for i, decorator in enumerate(node.decorators):
- decorator = decorator.decorator
- if (isinstance(decorator, ExprNodes.CallNode) and
- decorator.function.is_name and
- decorator.function.name == 'cname'):
- args, kwargs = decorator.explicit_args_kwds()
- if kwargs:
- raise AssertionError(
- "cname decorator does not take keyword arguments")
- if len(args) != 1:
- raise AssertionError(
- "cname decorator takes exactly one argument")
- if not (args[0].is_literal and
- args[0].type == Builtin.str_type):
- raise AssertionError(
- "argument to cname decorator must be a string literal")
- cname = args[0].compile_time_value(None)
- del node.decorators[i]
- node = Nodes.CnameDecoratorNode(pos=node.pos, node=node,
- cname=cname)
- break
- return self.visit_Node(node)
- visit_FuncDefNode = handle_function
- visit_CClassDefNode = handle_function
- visit_CEnumDefNode = handle_function
- visit_CStructOrUnionDefNode = handle_function
- class ForwardDeclareTypes(CythonTransform):
- def visit_CompilerDirectivesNode(self, node):
- env = self.module_scope
- old = env.directives
- env.directives = node.directives
- self.visitchildren(node)
- env.directives = old
- return node
- def visit_ModuleNode(self, node):
- self.module_scope = node.scope
- self.module_scope.directives = node.directives
- self.visitchildren(node)
- return node
- def visit_CDefExternNode(self, node):
- old_cinclude_flag = self.module_scope.in_cinclude
- self.module_scope.in_cinclude = 1
- self.visitchildren(node)
- self.module_scope.in_cinclude = old_cinclude_flag
- return node
- def visit_CEnumDefNode(self, node):
- node.declare(self.module_scope)
- return node
- def visit_CStructOrUnionDefNode(self, node):
- if node.name not in self.module_scope.entries:
- node.declare(self.module_scope)
- return node
- def visit_CClassDefNode(self, node):
- if node.class_name not in self.module_scope.entries:
- node.declare(self.module_scope)
- # Expand fused methods of .pxd declared types to construct the final vtable order.
- type = self.module_scope.entries[node.class_name].type
- if type is not None and type.is_extension_type and not type.is_builtin_type and type.scope:
- scope = type.scope
- for entry in scope.cfunc_entries:
- if entry.type and entry.type.is_fused:
- entry.type.get_all_specialized_function_types()
- return node
- class AnalyseDeclarationsTransform(EnvTransform):
- basic_property = TreeFragment(u"""
- property NAME:
- def __get__(self):
- return ATTR
- def __set__(self, value):
- ATTR = value
- """, level='c_class', pipeline=[NormalizeTree(None)])
- basic_pyobject_property = TreeFragment(u"""
- property NAME:
- def __get__(self):
- return ATTR
- def __set__(self, value):
- ATTR = value
- def __del__(self):
- ATTR = None
- """, level='c_class', pipeline=[NormalizeTree(None)])
- basic_property_ro = TreeFragment(u"""
- property NAME:
- def __get__(self):
- return ATTR
- """, level='c_class', pipeline=[NormalizeTree(None)])
- struct_or_union_wrapper = TreeFragment(u"""
- cdef class NAME:
- cdef TYPE value
- def __init__(self, MEMBER=None):
- cdef int count
- count = 0
- INIT_ASSIGNMENTS
- if IS_UNION and count > 1:
- raise ValueError, "At most one union member should be specified."
- def __str__(self):
- return STR_FORMAT % MEMBER_TUPLE
- def __repr__(self):
- return REPR_FORMAT % MEMBER_TUPLE
- """, pipeline=[NormalizeTree(None)])
- init_assignment = TreeFragment(u"""
- if VALUE is not None:
- ATTR = VALUE
- count += 1
- """, pipeline=[NormalizeTree(None)])
- fused_function = None
- in_lambda = 0
- def __call__(self, root):
- # needed to determine if a cdef var is declared after it's used.
- self.seen_vars_stack = []
- self.fused_error_funcs = set()
- super_class = super(AnalyseDeclarationsTransform, self)
- self._super_visit_FuncDefNode = super_class.visit_FuncDefNode
- return super_class.__call__(root)
- def visit_NameNode(self, node):
- self.seen_vars_stack[-1].add(node.name)
- return node
- def visit_ModuleNode(self, node):
- # Pickling support requires injecting module-level nodes.
- self.extra_module_declarations = []
- self.seen_vars_stack.append(set())
- node.analyse_declarations(self.current_env())
- self.visitchildren(node)
- self.seen_vars_stack.pop()
- node.body.stats.extend(self.extra_module_declarations)
- return node
- def visit_LambdaNode(self, node):
- self.in_lambda += 1
- node.analyse_declarations(self.current_env())
- self.visitchildren(node)
- self.in_lambda -= 1
- return node
- def visit_CClassDefNode(self, node):
- node = self.visit_ClassDefNode(node)
- if node.scope and node.scope.implemented and node.body:
- stats = []
- for entry in node.scope.var_entries:
- if entry.needs_property:
- property = self.create_Property(entry)
- property.analyse_declarations(node.scope)
- self.visit(property)
- stats.append(property)
- if stats:
- node.body.stats += stats
- if (node.visibility != 'extern'
- and not node.scope.lookup('__reduce__')
- and not node.scope.lookup('__reduce_ex__')):
- self._inject_pickle_methods(node)
- return node
- def _inject_pickle_methods(self, node):
- env = self.current_env()
- if node.scope.directives['auto_pickle'] is False: # None means attempt it.
- # Old behavior of not doing anything.
- return
- auto_pickle_forced = node.scope.directives['auto_pickle'] is True
- all_members = []
- cls = node.entry.type
- cinit = None
- inherited_reduce = None
- while cls is not None:
- all_members.extend(e for e in cls.scope.var_entries if e.name not in ('__weakref__', '__dict__'))
- cinit = cinit or cls.scope.lookup('__cinit__')
- inherited_reduce = inherited_reduce or cls.scope.lookup('__reduce__') or cls.scope.lookup('__reduce_ex__')
- cls = cls.base_type
- all_members.sort(key=lambda e: e.name)
- if inherited_reduce:
- # This is not failsafe, as we may not know whether a cimported class defines a __reduce__.
- # This is why we define __reduce_cython__ and only replace __reduce__
- # (via ExtensionTypes.SetupReduce utility code) at runtime on class creation.
- return
- non_py = [
- e for e in all_members
- if not e.type.is_pyobject and (not e.type.can_coerce_to_pyobject(env)
- or not e.type.can_coerce_from_pyobject(env))
- ]
- structs = [e for e in all_members if e.type.is_struct_or_union]
- if cinit or non_py or (structs and not auto_pickle_forced):
- if cinit:
- # TODO(robertwb): We could allow this if __cinit__ has no require arguments.
- msg = 'no default __reduce__ due to non-trivial __cinit__'
- elif non_py:
- msg = "%s cannot be converted to a Python object for pickling" % ','.join("self.%s" % e.name for e in non_py)
- else:
- # Extern structs may be only partially defined.
- # TODO(robertwb): Limit the restriction to extern
- # (and recursively extern-containing) structs.
- msg = ("Pickling of struct members such as %s must be explicitly requested "
- "with @auto_pickle(True)" % ','.join("self.%s" % e.name for e in structs))
- if auto_pickle_forced:
- error(node.pos, msg)
- pickle_func = TreeFragment(u"""
- def __reduce_cython__(self):
- raise TypeError("%(msg)s")
- def __setstate_cython__(self, __pyx_state):
- raise TypeError("%(msg)s")
- """ % {'msg': msg},
- level='c_class', pipeline=[NormalizeTree(None)]).substitute({})
- pickle_func.analyse_declarations(node.scope)
- self.visit(pickle_func)
- node.body.stats.append(pickle_func)
- else:
- for e in all_members:
- if not e.type.is_pyobject:
- e.type.create_to_py_utility_code(env)
- e.type.create_from_py_utility_code(env)
- all_members_names = [e.name for e in all_members]
- checksums = _calculate_pickle_checksums(all_members_names)
- unpickle_func_name = '__pyx_unpickle_%s' % node.class_name
- # TODO(robertwb): Move the state into the third argument
- # so it can be pickled *after* self is memoized.
- unpickle_func = TreeFragment(u"""
- def %(unpickle_func_name)s(__pyx_type, long __pyx_checksum, __pyx_state):
- cdef object __pyx_PickleError
- cdef object __pyx_result
- if __pyx_checksum not in %(checksums)s:
- from pickle import PickleError as __pyx_PickleError
- raise __pyx_PickleError("Incompatible checksums (0x%%x vs %(checksums)s = (%(members)s))" %% __pyx_checksum)
- __pyx_result = %(class_name)s.__new__(__pyx_type)
- if __pyx_state is not None:
- %(unpickle_func_name)s__set_state(<%(class_name)s> __pyx_result, __pyx_state)
- return __pyx_result
- cdef %(unpickle_func_name)s__set_state(%(class_name)s __pyx_result, tuple __pyx_state):
- %(assignments)s
- if len(__pyx_state) > %(num_members)d and hasattr(__pyx_result, '__dict__'):
- __pyx_result.__dict__.update(__pyx_state[%(num_members)d])
- """ % {
- 'unpickle_func_name': unpickle_func_name,
- 'checksums': "(%s)" % ', '.join(checksums),
- 'members': ', '.join(all_members_names),
- 'class_name': node.class_name,
- 'assignments': '; '.join(
- '__pyx_result.%s = __pyx_state[%s]' % (v, ix)
- for ix, v in enumerate(all_members_names)),
- 'num_members': len(all_members_names),
- }, level='module', pipeline=[NormalizeTree(None)]).substitute({})
- unpickle_func.analyse_declarations(node.entry.scope)
- self.visit(unpickle_func)
- self.extra_module_declarations.append(unpickle_func)
- pickle_func = TreeFragment(u"""
- def __reduce_cython__(self):
- cdef tuple state
- cdef object _dict
- cdef bint use_setstate
- state = (%(members)s)
- _dict = getattr(self, '__dict__', None)
- if _dict is not None:
- state += (_dict,)
- use_setstate = True
- else:
- use_setstate = %(any_notnone_members)s
- if use_setstate:
- return %(unpickle_func_name)s, (type(self), %(checksum)s, None), state
- else:
- return %(unpickle_func_name)s, (type(self), %(checksum)s, state)
- def __setstate_cython__(self, __pyx_state):
- %(unpickle_func_name)s__set_state(self, __pyx_state)
- """ % {
- 'unpickle_func_name': unpickle_func_name,
- 'checksum': checksums[0],
- 'members': ', '.join('self.%s' % v for v in all_members_names) + (',' if len(all_members_names) == 1 else ''),
- # Even better, we could check PyType_IS_GC.
- 'any_notnone_members' : ' or '.join(['self.%s is not None' % e.name for e in all_members if e.type.is_pyobject] or ['False']),
- },
- level='c_class', pipeline=[NormalizeTree(None)]).substitute({})
- pickle_func.analyse_declarations(node.scope)
- self.enter_scope(node, node.scope) # functions should be visited in the class scope
- self.visit(pickle_func)
- self.exit_scope()
- node.body.stats.append(pickle_func)
- def _handle_fused_def_decorators(self, old_decorators, env, node):
- """
- Create function calls to the decorators and reassignments to
- the function.
- """
- # Delete staticmethod and classmethod decorators, this is
- # handled directly by the fused function object.
- decorators = []
- for decorator in old_decorators:
- func = decorator.decorator
- if (not func.is_name or
- func.name not in ('staticmethod', 'classmethod') or
- env.lookup_here(func.name)):
- # not a static or classmethod
- decorators.append(decorator)
- if decorators:
- transform = DecoratorTransform(self.context)
- def_node = node.node
- _, reassignments = transform.chain_decorators(
- def_node, decorators, def_node.name)
- reassignments.analyse_declarations(env)
- node = [node, reassignments]
- return node
- def _handle_def(self, decorators, env, node):
- "Handle def or cpdef fused functions"
- # Create PyCFunction nodes for each specialization
- node.stats.insert(0, node.py_func)
- node.py_func = self.visit(node.py_func)
- node.update_fused_defnode_entry(env)
- pycfunc = ExprNodes.PyCFunctionNode.from_defnode(node.py_func, binding=True)
- pycfunc = ExprNodes.ProxyNode(pycfunc.coerce_to_temp(env))
- node.resulting_fused_function = pycfunc
- # Create assignment node for our def function
- node.fused_func_assignment = self._create_assignment(
- node.py_func, ExprNodes.CloneNode(pycfunc), env)
- if decorators:
- node = self._handle_fused_def_decorators(decorators, env, node)
- return node
- def _create_fused_function(self, env, node):
- "Create a fused function for a DefNode with fused arguments"
- from . import FusedNode
- if self.fused_function or self.in_lambda:
- if self.fused_function not in self.fused_error_funcs:
- if self.in_lambda:
- error(node.pos, "Fused lambdas not allowed")
- else:
- error(node.pos, "Cannot nest fused functions")
- self.fused_error_funcs.add(self.fused_function)
- node.body = Nodes.PassStatNode(node.pos)
- for arg in node.args:
- if arg.type.is_fused:
- arg.type = arg.type.get_fused_types()[0]
- return node
- decorators = getattr(node, 'decorators', None)
- node = FusedNode.FusedCFuncDefNode(node, env)
- self.fused_function = node
- self.visitchildren(node)
- self.fused_function = None
- if node.py_func:
- node = self._handle_def(decorators, env, node)
- return node
- def _handle_nogil_cleanup(self, lenv, node):
- "Handle cleanup for 'with gil' blocks in nogil functions."
- if lenv.nogil and lenv.has_with_gil_block:
- # Acquire the GIL for cleanup in 'nogil' functions, by wrapping
- # the entire function body in try/finally.
- # The corresponding release will be taken care of by
- # Nodes.FuncDefNode.generate_function_definitions()
- node.body = Nodes.NogilTryFinallyStatNode(
- node.body.pos,
- body=node.body,
- finally_clause=Nodes.EnsureGILNode(node.body.pos),
- finally_except_clause=Nodes.EnsureGILNode(node.body.pos))
- def _handle_fused(self, node):
- if node.is_generator and node.has_fused_arguments:
- node.has_fused_arguments = False
- error(node.pos, "Fused generators not supported")
- node.gbody = Nodes.StatListNode(node.pos,
- stats=[],
- body=Nodes.PassStatNode(node.pos))
- return node.has_fused_arguments
- def visit_FuncDefNode(self, node):
- """
- Analyse a function and its body, as that hasn't happened yet. Also
- analyse the directive_locals set by @cython.locals().
- Then, if we are a function with fused arguments, replace the function
- (after it has declared itself in the symbol table!) with a
- FusedCFuncDefNode, and analyse its children (which are in turn normal
- functions). If we're a normal function, just analyse the body of the
- function.
- """
- env = self.current_env()
- self.seen_vars_stack.append(set())
- lenv = node.local_scope
- node.declare_arguments(lenv)
- # @cython.locals(...)
- for var, type_node in node.directive_locals.items():
- if not lenv.lookup_here(var): # don't redeclare args
- type = type_node.analyse_as_type(lenv)
- if type:
- lenv.declare_var(var, type, type_node.pos)
- else:
- error(type_node.pos, "Not a type")
- if self._handle_fused(node):
- node = self._create_fused_function(env, node)
- else:
- node.body.analyse_declarations(lenv)
- self._handle_nogil_cleanup(lenv, node)
- self._super_visit_FuncDefNode(node)
- self.seen_vars_stack.pop()
- return node
- def visit_DefNode(self, node):
- node = self.visit_FuncDefNode(node)
- env = self.current_env()
- if isinstance(node, Nodes.DefNode) and node.is_wrapper:
- env = env.parent_scope
- if (not isinstance(node, Nodes.DefNode) or
- node.fused_py_func or node.is_generator_body or
- not node.needs_assignment_synthesis(env)):
- return node
- return [node, self._synthesize_assignment(node, env)]
- def visit_GeneratorBodyDefNode(self, node):
- return self.visit_FuncDefNode(node)
- def _synthesize_assignment(self, node, env):
- # Synthesize assignment node and put it right after defnode
- genv = env
- while genv.is_py_class_scope or genv.is_c_class_scope:
- genv = genv.outer_scope
- if genv.is_closure_scope:
- rhs = node.py_cfunc_node = ExprNodes.InnerFunctionNode(
- node.pos, def_node=node,
- pymethdef_cname=node.entry.pymethdef_cname,
- code_object=ExprNodes.CodeObjectNode(node))
- else:
- binding = self.current_directives.get('binding')
- rhs = ExprNodes.PyCFunctionNode.from_defnode(node, binding)
- node.code_object = rhs.code_object
- if node.is_generator:
- node.gbody.code_object = node.code_object
- if env.is_py_class_scope:
- rhs.binding = True
- node.is_cyfunction = rhs.binding
- return self._create_assignment(node, rhs, env)
- def _create_assignment(self, def_node, rhs, env):
- if def_node.decorators:
- for decorator in def_node.decorators[::-1]:
- rhs = ExprNodes.SimpleCallNode(
- decorator.pos,
- function = decorator.decorator,
- args = [rhs])
- def_node.decorators = None
- assmt = Nodes.SingleAssignmentNode(
- def_node.pos,
- lhs=ExprNodes.NameNode(def_node.pos, name=def_node.name),
- rhs=rhs)
- assmt.analyse_declarations(env)
- return assmt
- def visit_ScopedExprNode(self, node):
- env = self.current_env()
- node.analyse_declarations(env)
- # the node may or may not have a local scope
- if node.has_local_scope:
- self.seen_vars_stack.append(set(self.seen_vars_stack[-1]))
- self.enter_scope(node, node.expr_scope)
- node.analyse_scoped_declarations(node.expr_scope)
- self.visitchildren(node)
- self.exit_scope()
- self.seen_vars_stack.pop()
- else:
- node.analyse_scoped_declarations(env)
- self.visitchildren(node)
- return node
- def visit_TempResultFromStatNode(self, node):
- self.visitchildren(node)
- node.analyse_declarations(self.current_env())
- return node
- def visit_CppClassNode(self, node):
- if node.visibility == 'extern':
- return None
- else:
- return self.visit_ClassDefNode(node)
- def visit_CStructOrUnionDefNode(self, node):
- # Create a wrapper node if needed.
- # We want to use the struct type information (so it can't happen
- # before this phase) but also create new objects to be declared
- # (so it can't happen later).
- # Note that we don't return the original node, as it is
- # never used after this phase.
- if True: # private (default)
- return None
- self_value = ExprNodes.AttributeNode(
- pos = node.pos,
- obj = ExprNodes.NameNode(pos=node.pos, name=u"self"),
- attribute = EncodedString(u"value"))
- var_entries = node.entry.type.scope.var_entries
- attributes = []
- for entry in var_entries:
- attributes.append(ExprNodes.AttributeNode(pos = entry.pos,
- obj = self_value,
- attribute = entry.name))
- # __init__ assignments
- init_assignments = []
- for entry, attr in zip(var_entries, attributes):
- # TODO: branch on visibility
- init_assignments.append(self.init_assignment.substitute({
- u"VALUE": ExprNodes.NameNode(entry.pos, name = entry.name),
- u"ATTR": attr,
- }, pos = entry.pos))
- # create the class
- str_format = u"%s(%s)" % (node.entry.type.name, ("%s, " * len(attributes))[:-2])
- wrapper_class = self.struct_or_union_wrapper.substitute({
- u"INIT_ASSIGNMENTS": Nodes.StatListNode(node.pos, stats = init_assignments),
- u"IS_UNION": ExprNodes.BoolNode(node.pos, value = not node.entry.type.is_struct),
- u"MEMBER_TUPLE": ExprNodes.TupleNode(node.pos, args=attributes),
- u"STR_FORMAT": ExprNodes.StringNode(node.pos, value = EncodedString(str_format)),
- u"REPR_FORMAT": ExprNodes.StringNode(node.pos, value = EncodedString(str_format.replace("%s", "%r"))),
- }, pos = node.pos).stats[0]
- wrapper_class.class_name = node.name
- wrapper_class.shadow = True
- class_body = wrapper_class.body.stats
- # fix value type
- assert isinstance(class_body[0].base_type, Nodes.CSimpleBaseTypeNode)
- class_body[0].base_type.name = node.name
- # fix __init__ arguments
- init_method = class_body[1]
- assert isinstance(init_method, Nodes.DefNode) and init_method.name == '__init__'
- arg_template = init_method.args[1]
- if not node.entry.type.is_struct:
- arg_template.kw_only = True
- del init_method.args[1]
- for entry, attr in zip(var_entries, attributes):
- arg = copy.deepcopy(arg_template)
- arg.declarator.name = entry.name
- init_method.args.append(arg)
- # setters/getters
- for entry, attr in zip(var_entries, attributes):
- # TODO: branch on visibility
- if entry.type.is_pyobject:
- template = self.basic_pyobject_property
- else:
- template = self.basic_property
- property = template.substitute({
- u"ATTR": attr,
- }, pos = entry.pos).stats[0]
- property.name = entry.name
- wrapper_class.body.stats.append(property)
- wrapper_class.analyse_declarations(self.current_env())
- return self.visit_CClassDefNode(wrapper_class)
- # Some nodes are no longer needed after declaration
- # analysis and can be dropped. The analysis was performed
- # on these nodes in a separate recursive process from the
- # enclosing function or module, so we can simply drop them.
- def visit_CDeclaratorNode(self, node):
- # necessary to ensure that all CNameDeclaratorNodes are visited.
- self.visitchildren(node)
- return node
- def visit_CTypeDefNode(self, node):
- return node
- def visit_CBaseTypeNode(self, node):
- return None
- def visit_CEnumDefNode(self, node):
- if node.visibility == 'public':
- return node
- else:
- return None
- def visit_CNameDeclaratorNode(self, node):
- if node.name in self.seen_vars_stack[-1]:
- entry = self.current_env().lookup(node.name)
- if (entry is None or entry.visibility != 'extern'
- and not entry.scope.is_c_class_scope):
- warning(node.pos, "cdef variable '%s' declared after it is used" % node.name, 2)
- self.visitchildren(node)
- return node
- def visit_CVarDefNode(self, node):
- # to ensure all CNameDeclaratorNodes are visited.
- self.visitchildren(node)
- return None
- def visit_CnameDecoratorNode(self, node):
- child_node = self.visit(node.node)
- if not child_node:
- return None
- if type(child_node) is list: # Assignment synthesized
- node.child_node = child_node[0]
- return [node] + child_node[1:]
- node.node = child_node
- return node
- def create_Property(self, entry):
- if entry.visibility == 'public':
- if entry.type.is_pyobject:
- template = self.basic_pyobject_property
- else:
- template = self.basic_property
- elif entry.visibility == 'readonly':
- template = self.basic_property_ro
- property = template.substitute({
- u"ATTR": ExprNodes.AttributeNode(pos=entry.pos,
- obj=ExprNodes.NameNode(pos=entry.pos, name="self"),
- attribute=entry.name),
- }, pos=entry.pos).stats[0]
- property.name = entry.name
- property.doc = entry.doc
- return property
- def _calculate_pickle_checksums(member_names):
- # Cython 0.x used MD5 for the checksum, which a few Python installations remove for security reasons.
- # SHA-256 should be ok for years to come, but early Cython 3.0 alpha releases used SHA-1,
- # which may not be.
- member_names_string = ' '.join(member_names).encode('utf-8')
- hash_kwargs = {'usedforsecurity': False} if sys.version_info >= (3, 9) else {}
- checksums = []
- for algo_name in ['md5', 'sha256', 'sha1']:
- try:
- mkchecksum = getattr(hashlib, algo_name)
- checksum = mkchecksum(member_names_string, **hash_kwargs).hexdigest()
- except (AttributeError, ValueError):
- # The algorithm (i.e. MD5) might not be there at all, or might be blocked at runtime.
- continue
- checksums.append('0x' + checksum[:7])
- return checksums
- class CalculateQualifiedNamesTransform(EnvTransform):
- """
- Calculate and store the '__qualname__' and the global
- module name on some nodes.
- """
- def visit_ModuleNode(self, node):
- self.module_name = self.global_scope().qualified_name
- self.qualified_name = []
- _super = super(CalculateQualifiedNamesTransform, self)
- self._super_visit_FuncDefNode = _super.visit_FuncDefNode
- self._super_visit_ClassDefNode = _super.visit_ClassDefNode
- self.visitchildren(node)
- return node
- def _set_qualname(self, node, name=None):
- if name:
- qualname = self.qualified_name[:]
- qualname.append(name)
- else:
- qualname = self.qualified_name
- node.qualname = EncodedString('.'.join(qualname))
- node.module_name = self.module_name
- def _append_entry(self, entry):
- if entry.is_pyglobal and not entry.is_pyclass_attr:
- self.qualified_name = [entry.name]
- else:
- self.qualified_name.append(entry.name)
- def visit_ClassNode(self, node):
- self._set_qualname(node, node.name)
- self.visitchildren(node)
- return node
- def visit_PyClassNamespaceNode(self, node):
- # class name was already added by parent node
- self._set_qualname(node)
- self.visitchildren(node)
- return node
- def visit_PyCFunctionNode(self, node):
- orig_qualified_name = self.qualified_name[:]
- if node.def_node.is_wrapper and self.qualified_name and self.qualified_name[-1] == '<locals>':
- self.qualified_name.pop()
- self._set_qualname(node)
- else:
- self._set_qualname(node, node.def_node.name)
- self.visitchildren(node)
- self.qualified_name = orig_qualified_name
- return node
- def visit_DefNode(self, node):
- if node.is_wrapper and self.qualified_name:
- assert self.qualified_name[-1] == '<locals>', self.qualified_name
- orig_qualified_name = self.qualified_name[:]
- self.qualified_name.pop()
- self._set_qualname(node)
- self._super_visit_FuncDefNode(node)
- self.qualified_name = orig_qualified_name
- else:
- self._set_qualname(node, node.name)
- self.visit_FuncDefNode(node)
- return node
- def visit_FuncDefNode(self, node):
- orig_qualified_name = self.qualified_name[:]
- if getattr(node, 'name', None) == '<lambda>':
- self.qualified_name.append('<lambda>')
- else:
- self._append_entry(node.entry)
- self.qualified_name.append('<locals>')
- self._super_visit_FuncDefNode(node)
- self.qualified_name = orig_qualified_name
- return node
- def visit_ClassDefNode(self, node):
- orig_qualified_name = self.qualified_name[:]
- entry = (getattr(node, 'entry', None) or # PyClass
- self.current_env().lookup_here(node.name)) # CClass
- self._append_entry(entry)
- self._super_visit_ClassDefNode(node)
- self.qualified_name = orig_qualified_name
- return node
- class AnalyseExpressionsTransform(CythonTransform):
- def visit_ModuleNode(self, node):
- node.scope.infer_types()
- node.body = node.body.analyse_expressions(node.scope)
- self.visitchildren(node)
- return node
- def visit_FuncDefNode(self, node):
- node.local_scope.infer_types()
- node.body = node.body.analyse_expressions(node.local_scope)
- self.visitchildren(node)
- return node
- def visit_ScopedExprNode(self, node):
- if node.has_local_scope:
- node.expr_scope.infer_types()
- node = node.analyse_scoped_expressions(node.expr_scope)
- self.visitchildren(node)
- return node
- def visit_IndexNode(self, node):
- """
- Replace index nodes used to specialize cdef functions with fused
- argument types with the Attribute- or NameNode referring to the
- function. We then need to copy over the specialization properties to
- the attribute or name node.
- Because the indexing might be a Python indexing operation on a fused
- function, or (usually) a Cython indexing operation, we need to
- re-analyse the types.
- """
- self.visit_Node(node)
- if node.is_fused_index and not node.type.is_error:
- node = node.base
- return node
- class FindInvalidUseOfFusedTypes(CythonTransform):
- def visit_FuncDefNode(self, node):
- # Errors related to use in functions with fused args will already
- # have been detected
- if not node.has_fused_arguments:
- if not node.is_generator_body and node.return_type.is_fused:
- error(node.pos, "Return type is not specified as argument type")
- else:
- self.visitchildren(node)
- return node
- def visit_ExprNode(self, node):
- if node.type and node.type.is_fused:
- error(node.pos, "Invalid use of fused types, type cannot be specialized")
- else:
- self.visitchildren(node)
- return node
- class ExpandInplaceOperators(EnvTransform):
- def visit_InPlaceAssignmentNode(self, node):
- lhs = node.lhs
- rhs = node.rhs
- if lhs.type.is_cpp_class:
- # No getting around this exact operator here.
- return node
- if isinstance(lhs, ExprNodes.BufferIndexNode):
- # There is code to handle this case in InPlaceAssignmentNode
- return node
- env = self.current_env()
- def side_effect_free_reference(node, setting=False):
- if node.is_name:
- return node, []
- elif node.type.is_pyobject and not setting:
- node = LetRefNode(node)
- return node, [node]
- elif node.is_subscript:
- base, temps = side_effect_free_reference(node.base)
- index = LetRefNode(node.index)
- return ExprNodes.IndexNode(node.pos, base=base, index=index), temps + [index]
- elif node.is_attribute:
- obj, temps = side_effect_free_reference(node.obj)
- return ExprNodes.AttributeNode(node.pos, obj=obj, attribute=node.attribute), temps
- elif isinstance(node, ExprNodes.BufferIndexNode):
- raise ValueError("Don't allow things like attributes of buffer indexing operations")
- else:
- node = LetRefNode(node)
- return node, [node]
- try:
- lhs, let_ref_nodes = side_effect_free_reference(lhs, setting=True)
- except ValueError:
- return node
- dup = lhs.__class__(**lhs.__dict__)
- binop = ExprNodes.binop_node(node.pos,
- operator = node.operator,
- operand1 = dup,
- operand2 = rhs,
- inplace=True)
- # Manually analyse types for new node.
- lhs.analyse_target_types(env)
- dup.analyse_types(env)
- binop.analyse_operation(env)
- node = Nodes.SingleAssignmentNode(
- node.pos,
- lhs = lhs,
- rhs=binop.coerce_to(lhs.type, env))
- # Use LetRefNode to avoid side effects.
- let_ref_nodes.reverse()
- for t in let_ref_nodes:
- node = LetNode(t, node)
- return node
- def visit_ExprNode(self, node):
- # In-place assignments can't happen within an expression.
- return node
- class AdjustDefByDirectives(CythonTransform, SkipDeclarations):
- """
- Adjust function and class definitions by the decorator directives:
- @cython.cfunc
- @cython.cclass
- @cython.ccall
- @cython.inline
- @cython.nogil
- """
- def visit_ModuleNode(self, node):
- self.directives = node.directives
- self.in_py_class = False
- self.visitchildren(node)
- return node
- def visit_CompilerDirectivesNode(self, node):
- old_directives = self.directives
- self.directives = node.directives
- self.visitchildren(node)
- self.directives = old_directives
- return node
- def visit_DefNode(self, node):
- modifiers = []
- if 'inline' in self.directives:
- modifiers.append('inline')
- nogil = self.directives.get('nogil')
- except_val = self.directives.get('exceptval')
- return_type_node = self.directives.get('returns')
- if return_type_node is None and self.directives['annotation_typing']:
- return_type_node = node.return_type_annotation
- # for Python anntations, prefer safe exception handling by default
- if return_type_node is not None and except_val is None:
- except_val = (None, True) # except *
- elif except_val is None:
- # backward compatible default: no exception check
- except_val = (None, False)
- if 'ccall' in self.directives:
- node = node.as_cfunction(
- overridable=True, modifiers=modifiers, nogil=nogil,
- returns=return_type_node, except_val=except_val)
- return self.visit(node)
- if 'cfunc' in self.directives:
- if self.in_py_class:
- error(node.pos, "cfunc directive is not allowed here")
- else:
- node = node.as_cfunction(
- overridable=False, modifiers=modifiers, nogil=nogil,
- returns=return_type_node, except_val=except_val)
- return self.visit(node)
- if 'inline' in modifiers:
- error(node.pos, "Python functions cannot be declared 'inline'")
- if nogil:
- # TODO: turn this into a "with gil" declaration.
- error(node.pos, "Python functions cannot be declared 'nogil'")
- self.visitchildren(node)
- return node
- def visit_LambdaNode(self, node):
- # No directives should modify lambdas or generator expressions (and also nothing in them).
- return node
- def visit_PyClassDefNode(self, node):
- if 'cclass' in self.directives:
- node = node.as_cclass()
- return self.visit(node)
- else:
- old_in_pyclass = self.in_py_class
- self.in_py_class = True
- self.visitchildren(node)
- self.in_py_class = old_in_pyclass
- return node
- def visit_CClassDefNode(self, node):
- old_in_pyclass = self.in_py_class
- self.in_py_class = False
- self.visitchildren(node)
- self.in_py_class = old_in_pyclass
- return node
- class AlignFunctionDefinitions(CythonTransform):
- """
- This class takes the signatures from a .pxd file and applies them to
- the def methods in a .py file.
- """
- def visit_ModuleNode(self, node):
- self.scope = node.scope
- self.directives = node.directives
- self.imported_names = set() # hack, see visit_FromImportStatNode()
- self.visitchildren(node)
- return node
- def visit_PyClassDefNode(self, node):
- pxd_def = self.scope.lookup(node.name)
- if pxd_def:
- if pxd_def.is_cclass:
- return self.visit_CClassDefNode(node.as_cclass(), pxd_def)
- elif not pxd_def.scope or not pxd_def.scope.is_builtin_scope:
- error(node.pos, "'%s' redeclared" % node.name)
- if pxd_def.pos:
- error(pxd_def.pos, "previous declaration here")
- return None
- return node
- def visit_CClassDefNode(self, node, pxd_def=None):
- if pxd_def is None:
- pxd_def = self.scope.lookup(node.class_name)
- if pxd_def:
- if not pxd_def.defined_in_pxd:
- return node
- outer_scope = self.scope
- self.scope = pxd_def.type.scope
- self.visitchildren(node)
- if pxd_def:
- self.scope = outer_scope
- return node
- def visit_DefNode(self, node):
- pxd_def = self.scope.lookup(node.name)
- if pxd_def and (not pxd_def.scope or not pxd_def.scope.is_builtin_scope):
- if not pxd_def.is_cfunction:
- error(node.pos, "'%s' redeclared" % node.name)
- if pxd_def.pos:
- error(pxd_def.pos, "previous declaration here")
- return None
- node = node.as_cfunction(pxd_def)
- elif (self.scope.is_module_scope and self.directives['auto_cpdef']
- and not node.name in self.imported_names
- and node.is_cdef_func_compatible()):
- # FIXME: cpdef-ing should be done in analyse_declarations()
- node = node.as_cfunction(scope=self.scope)
- # Enable this when nested cdef functions are allowed.
- # self.visitchildren(node)
- return node
- def visit_FromImportStatNode(self, node):
- # hack to prevent conditional import fallback functions from
- # being cdpef-ed (global Python variables currently conflict
- # with imports)
- if self.scope.is_module_scope:
- for name, _ in node.items:
- self.imported_names.add(name)
- return node
- def visit_ExprNode(self, node):
- # ignore lambdas and everything else that appears in expressions
- return node
- class RemoveUnreachableCode(CythonTransform):
- def visit_StatListNode(self, node):
- if not self.current_directives['remove_unreachable']:
- return node
- self.visitchildren(node)
- for idx, stat in enumerate(node.stats):
- idx += 1
- if stat.is_terminator:
- if idx < len(node.stats):
- if self.current_directives['warn.unreachable']:
- warning(node.stats[idx].pos, "Unreachable code", 2)
- node.stats = node.stats[:idx]
- node.is_terminator = True
- break
- return node
- def visit_IfClauseNode(self, node):
- self.visitchildren(node)
- if node.body.is_terminator:
- node.is_terminator = True
- return node
- def visit_IfStatNode(self, node):
- self.visitchildren(node)
- if node.else_clause and node.else_clause.is_terminator:
- for clause in node.if_clauses:
- if not clause.is_terminator:
- break
- else:
- node.is_terminator = True
- return node
- def visit_TryExceptStatNode(self, node):
- self.visitchildren(node)
- if node.body.is_terminator and node.else_clause:
- if self.current_directives['warn.unreachable']:
- warning(node.else_clause.pos, "Unreachable code", 2)
- node.else_clause = None
- return node
- def visit_TryFinallyStatNode(self, node):
- self.visitchildren(node)
- if node.finally_clause.is_terminator:
- node.is_terminator = True
- return node
- class YieldNodeCollector(TreeVisitor):
- def __init__(self):
- super(YieldNodeCollector, self).__init__()
- self.yields = []
- self.returns = []
- self.finallys = []
- self.excepts = []
- self.has_return_value = False
- self.has_yield = False
- self.has_await = False
- def visit_Node(self, node):
- self.visitchildren(node)
- def visit_YieldExprNode(self, node):
- self.yields.append(node)
- self.has_yield = True
- self.visitchildren(node)
- def visit_AwaitExprNode(self, node):
- self.yields.append(node)
- self.has_await = True
- self.visitchildren(node)
- def visit_ReturnStatNode(self, node):
- self.visitchildren(node)
- if node.value:
- self.has_return_value = True
- self.returns.append(node)
- def visit_TryFinallyStatNode(self, node):
- self.visitchildren(node)
- self.finallys.append(node)
- def visit_TryExceptStatNode(self, node):
- self.visitchildren(node)
- self.excepts.append(node)
- def visit_ClassDefNode(self, node):
- pass
- def visit_FuncDefNode(self, node):
- pass
- def visit_LambdaNode(self, node):
- pass
- def visit_GeneratorExpressionNode(self, node):
- pass
- def visit_CArgDeclNode(self, node):
- # do not look into annotations
- # FIXME: support (yield) in default arguments (currently crashes)
- pass
- class MarkClosureVisitor(CythonTransform):
- def visit_ModuleNode(self, node):
- self.needs_closure = False
- self.visitchildren(node)
- return node
- def visit_FuncDefNode(self, node):
- self.needs_closure = False
- self.visitchildren(node)
- node.needs_closure = self.needs_closure
- self.needs_closure = True
- collector = YieldNodeCollector()
- collector.visitchildren(node)
- if node.is_async_def:
- coroutine_type = Nodes.AsyncDefNode
- if collector.has_yield:
- coroutine_type = Nodes.AsyncGenNode
- for yield_expr in collector.yields + collector.returns:
- yield_expr.in_async_gen = True
- elif self.current_directives['iterable_coroutine']:
- coroutine_type = Nodes.IterableAsyncDefNode
- elif collector.has_await:
- found = next(y for y in collector.yields if y.is_await)
- error(found.pos, "'await' not allowed in generators (use 'yield')")
- return node
- elif collector.has_yield:
- coroutine_type = Nodes.GeneratorDefNode
- else:
- return node
- for i, yield_expr in enumerate(collector.yields, 1):
- yield_expr.label_num = i
- for retnode in collector.returns + collector.finallys + collector.excepts:
- retnode.in_generator = True
- gbody = Nodes.GeneratorBodyDefNode(
- pos=node.pos, name=node.name, body=node.body,
- is_async_gen_body=node.is_async_def and collector.has_yield)
- coroutine = coroutine_type(
- pos=node.pos, name=node.name, args=node.args,
- star_arg=node.star_arg, starstar_arg=node.starstar_arg,
- doc=node.doc, decorators=node.decorators,
- gbody=gbody, lambda_name=node.lambda_name,
- return_type_annotation=node.return_type_annotation)
- return coroutine
- def visit_CFuncDefNode(self, node):
- self.needs_closure = False
- self.visitchildren(node)
- node.needs_closure = self.needs_closure
- self.needs_closure = True
- if node.needs_closure and node.overridable:
- error(node.pos, "closures inside cpdef functions not yet supported")
- return node
- def visit_LambdaNode(self, node):
- self.needs_closure = False
- self.visitchildren(node)
- node.needs_closure = self.needs_closure
- self.needs_closure = True
- return node
- def visit_ClassDefNode(self, node):
- self.visitchildren(node)
- self.needs_closure = True
- return node
- class CreateClosureClasses(CythonTransform):
- # Output closure classes in module scope for all functions
- # that really need it.
- def __init__(self, context):
- super(CreateClosureClasses, self).__init__(context)
- self.path = []
- self.in_lambda = False
- def visit_ModuleNode(self, node):
- self.module_scope = node.scope
- self.visitchildren(node)
- return node
- def find_entries_used_in_closures(self, node):
- from_closure = []
- in_closure = []
- for scope in node.local_scope.iter_local_scopes():
- for name, entry in scope.entries.items():
- if not name:
- continue
- if entry.from_closure:
- from_closure.append((name, entry))
- elif entry.in_closure:
- in_closure.append((name, entry))
- return from_closure, in_closure
- def create_class_from_scope(self, node, target_module_scope, inner_node=None):
- # move local variables into closure
- if node.is_generator:
- for scope in node.local_scope.iter_local_scopes():
- for entry in scope.entries.values():
- if not (entry.from_closure or entry.is_pyglobal or entry.is_cglobal):
- entry.in_closure = True
- from_closure, in_closure = self.find_entries_used_in_closures(node)
- in_closure.sort()
- # Now from the beginning
- node.needs_closure = False
- node.needs_outer_scope = False
- func_scope = node.local_scope
- cscope = node.entry.scope
- while cscope.is_py_class_scope or cscope.is_c_class_scope:
- cscope = cscope.outer_scope
- if not from_closure and (self.path or inner_node):
- if not inner_node:
- if not node.py_cfunc_node:
- raise InternalError("DefNode does not have assignment node")
- inner_node = node.py_cfunc_node
- inner_node.needs_self_code = False
- node.needs_outer_scope = False
- if node.is_generator:
- pass
- elif not in_closure and not from_closure:
- return
- elif not in_closure:
- func_scope.is_passthrough = True
- func_scope.scope_class = cscope.scope_class
- node.needs_outer_scope = True
- return
- # entry.cname can contain periods (eg. a derived C method of a class).
- # We want to use the cname as part of a C struct name, so we replace
- # periods with double underscores.
- as_name = '%s_%s' % (
- target_module_scope.next_id(Naming.closure_class_prefix),
- node.entry.cname.replace('.','__'))
- entry = target_module_scope.declare_c_class(
- name=as_name, pos=node.pos, defining=True,
- implementing=True)
- entry.type.is_final_type = True
- func_scope.scope_class = entry
- class_scope = entry.type.scope
- class_scope.is_internal = True
- class_scope.is_closure_class_scope = True
- if node.is_async_def or node.is_generator:
- # Generators need their closure intact during cleanup as they resume to handle GeneratorExit
- class_scope.directives['no_gc_clear'] = True
- if Options.closure_freelist_size:
- class_scope.directives['freelist'] = Options.closure_freelist_size
- if from_closure:
- assert cscope.is_closure_scope
- class_scope.declare_var(pos=node.pos,
- name=Naming.outer_scope_cname,
- cname=Naming.outer_scope_cname,
- type=cscope.scope_class.type,
- is_cdef=True)
- node.needs_outer_scope = True
- for name, entry in in_closure:
- closure_entry = class_scope.declare_var(
- pos=entry.pos,
- name=entry.name if not entry.in_subscope else None,
- cname=entry.cname,
- type=entry.type,
- is_cdef=True)
- if entry.is_declared_generic:
- closure_entry.is_declared_generic = 1
- node.needs_closure = True
- # Do it here because other classes are already checked
- target_module_scope.check_c_class(func_scope.scope_class)
- def visit_LambdaNode(self, node):
- if not isinstance(node.def_node, Nodes.DefNode):
- # fused function, an error has been previously issued
- return node
- was_in_lambda = self.in_lambda
- self.in_lambda = True
- self.create_class_from_scope(node.def_node, self.module_scope, node)
- self.visitchildren(node)
- self.in_lambda = was_in_lambda
- return node
- def visit_FuncDefNode(self, node):
- if self.in_lambda:
- self.visitchildren(node)
- return node
- if node.needs_closure or self.path:
- self.create_class_from_scope(node, self.module_scope)
- self.path.append(node)
- self.visitchildren(node)
- self.path.pop()
- return node
- def visit_GeneratorBodyDefNode(self, node):
- self.visitchildren(node)
- return node
- def visit_CFuncDefNode(self, node):
- if not node.overridable:
- return self.visit_FuncDefNode(node)
- else:
- self.visitchildren(node)
- return node
- class InjectGilHandling(VisitorTransform, SkipDeclarations):
- """
- Allow certain Python operations inside of nogil blocks by implicitly acquiring the GIL.
- Must run before the AnalyseDeclarationsTransform to make sure the GILStatNodes get
- set up, parallel sections know that the GIL is acquired inside of them, etc.
- """
- def __call__(self, root):
- self.nogil = False
- return super(InjectGilHandling, self).__call__(root)
- # special node handling
- def visit_RaiseStatNode(self, node):
- """Allow raising exceptions in nogil sections by wrapping them in a 'with gil' block."""
- if self.nogil:
- node = Nodes.GILStatNode(node.pos, state='gil', body=node)
- return node
- # further candidates:
- # def visit_AssertStatNode(self, node):
- # def visit_ReraiseStatNode(self, node):
- # nogil tracking
- def visit_GILStatNode(self, node):
- was_nogil = self.nogil
- self.nogil = (node.state == 'nogil')
- self.visitchildren(node)
- self.nogil = was_nogil
- return node
- def visit_CFuncDefNode(self, node):
- was_nogil = self.nogil
- if isinstance(node.declarator, Nodes.CFuncDeclaratorNode):
- self.nogil = node.declarator.nogil and not node.declarator.with_gil
- self.visitchildren(node)
- self.nogil = was_nogil
- return node
- def visit_ParallelRangeNode(self, node):
- was_nogil = self.nogil
- self.nogil = node.nogil
- self.visitchildren(node)
- self.nogil = was_nogil
- return node
- def visit_ExprNode(self, node):
- # No special GIL handling inside of expressions for now.
- return node
- visit_Node = VisitorTransform.recurse_to_children
- class GilCheck(VisitorTransform):
- """
- Call `node.gil_check(env)` on each node to make sure we hold the
- GIL when we need it. Raise an error when on Python operations
- inside a `nogil` environment.
- Additionally, raise exceptions for closely nested with gil or with nogil
- statements. The latter would abort Python.
- """
- def __call__(self, root):
- self.env_stack = [root.scope]
- self.nogil = False
- # True for 'cdef func() nogil:' functions, as the GIL may be held while
- # calling this function (thus contained 'nogil' blocks may be valid).
- self.nogil_declarator_only = False
- return super(GilCheck, self).__call__(root)
- def _visit_scoped_children(self, node, gil_state):
- was_nogil = self.nogil
- outer_attrs = node.outer_attrs
- if outer_attrs and len(self.env_stack) > 1:
- self.nogil = self.env_stack[-2].nogil
- self.visitchildren(node, outer_attrs)
- self.nogil = gil_state
- self.visitchildren(node, attrs=None, exclude=outer_attrs)
- self.nogil = was_nogil
- def visit_FuncDefNode(self, node):
- self.env_stack.append(node.local_scope)
- inner_nogil = node.local_scope.nogil
- if inner_nogil:
- self.nogil_declarator_only = True
- if inner_nogil and node.nogil_check:
- node.nogil_check(node.local_scope)
- self._visit_scoped_children(node, inner_nogil)
- # This cannot be nested, so it doesn't need backup/restore
- self.nogil_declarator_only = False
- self.env_stack.pop()
- return node
- def visit_GILStatNode(self, node):
- if self.nogil and node.nogil_check:
- node.nogil_check()
- was_nogil = self.nogil
- is_nogil = (node.state == 'nogil')
- if was_nogil == is_nogil and not self.nogil_declarator_only:
- if not was_nogil:
- error(node.pos, "Trying to acquire the GIL while it is "
- "already held.")
- else:
- error(node.pos, "Trying to release the GIL while it was "
- "previously released.")
- if isinstance(node.finally_clause, Nodes.StatListNode):
- # The finally clause of the GILStatNode is a GILExitNode,
- # which is wrapped in a StatListNode. Just unpack that.
- node.finally_clause, = node.finally_clause.stats
- self._visit_scoped_children(node, is_nogil)
- return node
- def visit_ParallelRangeNode(self, node):
- if node.nogil:
- node.nogil = False
- node = Nodes.GILStatNode(node.pos, state='nogil', body=node)
- return self.visit_GILStatNode(node)
- if not self.nogil:
- error(node.pos, "prange() can only be used without the GIL")
- # Forget about any GIL-related errors that may occur in the body
- return None
- node.nogil_check(self.env_stack[-1])
- self.visitchildren(node)
- return node
- def visit_ParallelWithBlockNode(self, node):
- if not self.nogil:
- error(node.pos, "The parallel section may only be used without "
- "the GIL")
- return None
- if node.nogil_check:
- # It does not currently implement this, but test for it anyway to
- # avoid potential future surprises
- node.nogil_check(self.env_stack[-1])
- self.visitchildren(node)
- return node
- def visit_TryFinallyStatNode(self, node):
- """
- Take care of try/finally statements in nogil code sections.
- """
- if not self.nogil or isinstance(node, Nodes.GILStatNode):
- return self.visit_Node(node)
- node.nogil_check = None
- node.is_try_finally_in_nogil = True
- self.visitchildren(node)
- return node
- def visit_Node(self, node):
- if self.env_stack and self.nogil and node.nogil_check:
- node.nogil_check(self.env_stack[-1])
- if node.outer_attrs:
- self._visit_scoped_children(node, self.nogil)
- else:
- self.visitchildren(node)
- if self.nogil:
- node.in_nogil_context = True
- return node
- class TransformBuiltinMethods(EnvTransform):
- """
- Replace Cython's own cython.* builtins by the corresponding tree nodes.
- """
- def visit_SingleAssignmentNode(self, node):
- if node.declaration_only:
- return None
- else:
- self.visitchildren(node)
- return node
- def visit_AttributeNode(self, node):
- self.visitchildren(node)
- return self.visit_cython_attribute(node)
- def visit_NameNode(self, node):
- return self.visit_cython_attribute(node)
- def visit_cython_attribute(self, node):
- attribute = node.as_cython_attribute()
- if attribute:
- if attribute == u'compiled':
- node = ExprNodes.BoolNode(node.pos, value=True)
- elif attribute == u'__version__':
- from .. import __version__ as version
- node = ExprNodes.StringNode(node.pos, value=EncodedString(version))
- elif attribute == u'NULL':
- node = ExprNodes.NullNode(node.pos)
- elif attribute in (u'set', u'frozenset', u'staticmethod'):
- node = ExprNodes.NameNode(node.pos, name=EncodedString(attribute),
- entry=self.current_env().builtin_scope().lookup_here(attribute))
- elif PyrexTypes.parse_basic_type(attribute):
- pass
- elif self.context.cython_scope.lookup_qualified_name(attribute):
- pass
- else:
- error(node.pos, u"'%s' not a valid cython attribute or is being used incorrectly" % attribute)
- return node
- def visit_ExecStatNode(self, node):
- lenv = self.current_env()
- self.visitchildren(node)
- if len(node.args) == 1:
- node.args.append(ExprNodes.GlobalsExprNode(node.pos))
- if not lenv.is_module_scope:
- node.args.append(
- ExprNodes.LocalsExprNode(
- node.pos, self.current_scope_node(), lenv))
- return node
- def _inject_locals(self, node, func_name):
- # locals()/dir()/vars() builtins
- lenv = self.current_env()
- entry = lenv.lookup_here(func_name)
- if entry:
- # not the builtin
- return node
- pos = node.pos
- if func_name in ('locals', 'vars'):
- if func_name == 'locals' and len(node.args) > 0:
- error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d"
- % len(node.args))
- return node
- elif func_name == 'vars':
- if len(node.args) > 1:
- error(self.pos, "Builtin 'vars()' called with wrong number of args, expected 0-1, got %d"
- % len(node.args))
- if len(node.args) > 0:
- return node # nothing to do
- return ExprNodes.LocalsExprNode(pos, self.current_scope_node(), lenv)
- else: # dir()
- if len(node.args) > 1:
- error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d"
- % len(node.args))
- if len(node.args) > 0:
- # optimised in Builtin.py
- return node
- if lenv.is_py_class_scope or lenv.is_module_scope:
- if lenv.is_py_class_scope:
- pyclass = self.current_scope_node()
- locals_dict = ExprNodes.CloneNode(pyclass.dict)
- else:
- locals_dict = ExprNodes.GlobalsExprNode(pos)
- return ExprNodes.SortedDictKeysNode(locals_dict)
- local_names = sorted(var.name for var in lenv.entries.values() if var.name)
- items = [ExprNodes.IdentifierStringNode(pos, value=var)
- for var in local_names]
- return ExprNodes.ListNode(pos, args=items)
- def visit_PrimaryCmpNode(self, node):
- # special case: for in/not-in test, we do not need to sort locals()
- self.visitchildren(node)
- if node.operator in 'not_in': # in/not_in
- if isinstance(node.operand2, ExprNodes.SortedDictKeysNode):
- arg = node.operand2.arg
- if isinstance(arg, ExprNodes.NoneCheckNode):
- arg = arg.arg
- node.operand2 = arg
- return node
- def visit_CascadedCmpNode(self, node):
- return self.visit_PrimaryCmpNode(node)
- def _inject_eval(self, node, func_name):
- lenv = self.current_env()
- entry = lenv.lookup_here(func_name)
- if entry or len(node.args) != 1:
- return node
- # Inject globals and locals
- node.args.append(ExprNodes.GlobalsExprNode(node.pos))
- if not lenv.is_module_scope:
- node.args.append(
- ExprNodes.LocalsExprNode(
- node.pos, self.current_scope_node(), lenv))
- return node
- def _inject_super(self, node, func_name):
- lenv = self.current_env()
- entry = lenv.lookup_here(func_name)
- if entry or node.args:
- return node
- # Inject no-args super
- def_node = self.current_scope_node()
- if (not isinstance(def_node, Nodes.DefNode) or not def_node.args or
- len(self.env_stack) < 2):
- return node
- class_node, class_scope = self.env_stack[-2]
- if class_scope.is_py_class_scope:
- def_node.requires_classobj = True
- class_node.class_cell.is_active = True
- node.args = [
- ExprNodes.ClassCellNode(
- node.pos, is_generator=def_node.is_generator),
- ExprNodes.NameNode(node.pos, name=def_node.args[0].name)
- ]
- elif class_scope.is_c_class_scope:
- node.args = [
- ExprNodes.NameNode(
- node.pos, name=class_node.scope.name,
- entry=class_node.entry),
- ExprNodes.NameNode(node.pos, name=def_node.args[0].name)
- ]
- return node
- def visit_SimpleCallNode(self, node):
- # cython.foo
- function = node.function.as_cython_attribute()
- if function:
- if function in InterpretCompilerDirectives.unop_method_nodes:
- if len(node.args) != 1:
- error(node.function.pos, u"%s() takes exactly one argument" % function)
- else:
- node = InterpretCompilerDirectives.unop_method_nodes[function](
- node.function.pos, operand=node.args[0])
- elif function in InterpretCompilerDirectives.binop_method_nodes:
- if len(node.args) != 2:
- error(node.function.pos, u"%s() takes exactly two arguments" % function)
- else:
- node = InterpretCompilerDirectives.binop_method_nodes[function](
- node.function.pos, operand1=node.args[0], operand2=node.args[1])
- elif function == u'cast':
- if len(node.args) != 2:
- error(node.function.pos,
- u"cast() takes exactly two arguments and an optional typecheck keyword")
- else:
- type = node.args[0].analyse_as_type(self.current_env())
- if type:
- node = ExprNodes.TypecastNode(
- node.function.pos, type=type, operand=node.args[1], typecheck=False)
- else:
- error(node.args[0].pos, "Not a type")
- elif function == u'sizeof':
- if len(node.args) != 1:
- error(node.function.pos, u"sizeof() takes exactly one argument")
- else:
- type = node.args[0].analyse_as_type(self.current_env())
- if type:
- node = ExprNodes.SizeofTypeNode(node.function.pos, arg_type=type)
- else:
- node = ExprNodes.SizeofVarNode(node.function.pos, operand=node.args[0])
- elif function == 'cmod':
- if len(node.args) != 2:
- error(node.function.pos, u"cmod() takes exactly two arguments")
- else:
- node = ExprNodes.binop_node(node.function.pos, '%', node.args[0], node.args[1])
- node.cdivision = True
- elif function == 'cdiv':
- if len(node.args) != 2:
- error(node.function.pos, u"cdiv() takes exactly two arguments")
- else:
- node = ExprNodes.binop_node(node.function.pos, '/', node.args[0], node.args[1])
- node.cdivision = True
- elif function == u'set':
- node.function = ExprNodes.NameNode(node.pos, name=EncodedString('set'))
- elif function == u'staticmethod':
- node.function = ExprNodes.NameNode(node.pos, name=EncodedString('staticmethod'))
- elif self.context.cython_scope.lookup_qualified_name(function):
- pass
- else:
- error(node.function.pos,
- u"'%s' not a valid cython language construct" % function)
- self.visitchildren(node)
- if isinstance(node, ExprNodes.SimpleCallNode) and node.function.is_name:
- func_name = node.function.name
- if func_name in ('dir', 'locals', 'vars'):
- return self._inject_locals(node, func_name)
- if func_name == 'eval':
- return self._inject_eval(node, func_name)
- if func_name == 'super':
- return self._inject_super(node, func_name)
- return node
- def visit_GeneralCallNode(self, node):
- function = node.function.as_cython_attribute()
- if function == u'cast':
- # NOTE: assuming simple tuple/dict nodes for positional_args and keyword_args
- args = node.positional_args.args
- kwargs = node.keyword_args.compile_time_value(None)
- if (len(args) != 2 or len(kwargs) > 1 or
- (len(kwargs) == 1 and 'typecheck' not in kwargs)):
- error(node.function.pos,
- u"cast() takes exactly two arguments and an optional typecheck keyword")
- else:
- type = args[0].analyse_as_type(self.current_env())
- if type:
- typecheck = kwargs.get('typecheck', False)
- node = ExprNodes.TypecastNode(
- node.function.pos, type=type, operand=args[1], typecheck=typecheck)
- else:
- error(args[0].pos, "Not a type")
- self.visitchildren(node)
- return node
- class ReplaceFusedTypeChecks(VisitorTransform):
- """
- This is not a transform in the pipeline. It is invoked on the specific
- versions of a cdef function with fused argument types. It filters out any
- type branches that don't match. e.g.
- if fused_t is mytype:
- ...
- elif fused_t in other_fused_type:
- ...
- """
- def __init__(self, local_scope):
- super(ReplaceFusedTypeChecks, self).__init__()
- self.local_scope = local_scope
- # defer the import until now to avoid circular import time dependencies
- from .Optimize import ConstantFolding
- self.transform = ConstantFolding(reevaluate=True)
- def visit_IfStatNode(self, node):
- """
- Filters out any if clauses with false compile time type check
- expression.
- """
- self.visitchildren(node)
- return self.transform(node)
- def visit_PrimaryCmpNode(self, node):
- with Errors.local_errors(ignore=True):
- type1 = node.operand1.analyse_as_type(self.local_scope)
- type2 = node.operand2.analyse_as_type(self.local_scope)
- if type1 and type2:
- false_node = ExprNodes.BoolNode(node.pos, value=False)
- true_node = ExprNodes.BoolNode(node.pos, value=True)
- type1 = self.specialize_type(type1, node.operand1.pos)
- op = node.operator
- if op in ('is', 'is_not', '==', '!='):
- type2 = self.specialize_type(type2, node.operand2.pos)
- is_same = type1.same_as(type2)
- eq = op in ('is', '==')
- if (is_same and eq) or (not is_same and not eq):
- return true_node
- elif op in ('in', 'not_in'):
- # We have to do an instance check directly, as operand2
- # needs to be a fused type and not a type with a subtype
- # that is fused. First unpack the typedef
- if isinstance(type2, PyrexTypes.CTypedefType):
- type2 = type2.typedef_base_type
- if type1.is_fused:
- error(node.operand1.pos, "Type is fused")
- elif not type2.is_fused:
- error(node.operand2.pos,
- "Can only use 'in' or 'not in' on a fused type")
- else:
- types = PyrexTypes.get_specialized_types(type2)
- for specialized_type in types:
- if type1.same_as(specialized_type):
- if op == 'in':
- return true_node
- else:
- return false_node
- if op == 'not_in':
- return true_node
- return false_node
- return node
- def specialize_type(self, type, pos):
- try:
- return type.specialize(self.local_scope.fused_to_specific)
- except KeyError:
- error(pos, "Type is not specific")
- return type
- def visit_Node(self, node):
- self.visitchildren(node)
- return node
- class DebugTransform(CythonTransform):
- """
- Write debug information for this Cython module.
- """
- def __init__(self, context, options, result):
- super(DebugTransform, self).__init__(context)
- self.visited = set()
- # our treebuilder and debug output writer
- # (see Cython.Debugger.debug_output.CythonDebugWriter)
- self.tb = self.context.gdb_debug_outputwriter
- #self.c_output_file = options.output_file
- self.c_output_file = result.c_file
- # Closure support, basically treat nested functions as if the AST were
- # never nested
- self.nested_funcdefs = []
- # tells visit_NameNode whether it should register step-into functions
- self.register_stepinto = False
- def visit_ModuleNode(self, node):
- self.tb.module_name = node.full_module_name
- attrs = dict(
- module_name=node.full_module_name,
- filename=node.pos[0].filename,
- c_filename=self.c_output_file)
- self.tb.start('Module', attrs)
- # serialize functions
- self.tb.start('Functions')
- # First, serialize functions normally...
- self.visitchildren(node)
- # ... then, serialize nested functions
- for nested_funcdef in self.nested_funcdefs:
- self.visit_FuncDefNode(nested_funcdef)
- self.register_stepinto = True
- self.serialize_modulenode_as_function(node)
- self.register_stepinto = False
- self.tb.end('Functions')
- # 2.3 compatibility. Serialize global variables
- self.tb.start('Globals')
- entries = {}
- for k, v in node.scope.entries.items():
- if (v.qualified_name not in self.visited and not
- v.name.startswith('__pyx_') and not
- v.type.is_cfunction and not
- v.type.is_extension_type):
- entries[k]= v
- self.serialize_local_variables(entries)
- self.tb.end('Globals')
- # self.tb.end('Module') # end Module after the line number mapping in
- # Cython.Compiler.ModuleNode.ModuleNode._serialize_lineno_map
- return node
- def visit_FuncDefNode(self, node):
- self.visited.add(node.local_scope.qualified_name)
- if getattr(node, 'is_wrapper', False):
- return node
- if self.register_stepinto:
- self.nested_funcdefs.append(node)
- return node
- # node.entry.visibility = 'extern'
- if node.py_func is None:
- pf_cname = ''
- else:
- pf_cname = node.py_func.entry.func_cname
- attrs = dict(
- name=node.entry.name or getattr(node, 'name', '<unknown>'),
- cname=node.entry.func_cname,
- pf_cname=pf_cname,
- qualified_name=node.local_scope.qualified_name,
- lineno=str(node.pos[1]))
- self.tb.start('Function', attrs=attrs)
- self.tb.start('Locals')
- self.serialize_local_variables(node.local_scope.entries)
- self.tb.end('Locals')
- self.tb.start('Arguments')
- for arg in node.local_scope.arg_entries:
- self.tb.start(arg.name)
- self.tb.end(arg.name)
- self.tb.end('Arguments')
- self.tb.start('StepIntoFunctions')
- self.register_stepinto = True
- self.visitchildren(node)
- self.register_stepinto = False
- self.tb.end('StepIntoFunctions')
- self.tb.end('Function')
- return node
- def visit_NameNode(self, node):
- if (self.register_stepinto and
- node.type is not None and
- node.type.is_cfunction and
- getattr(node, 'is_called', False) and
- node.entry.func_cname is not None):
- # don't check node.entry.in_cinclude, as 'cdef extern: ...'
- # declared functions are not 'in_cinclude'.
- # This means we will list called 'cdef' functions as
- # "step into functions", but this is not an issue as they will be
- # recognized as Cython functions anyway.
- attrs = dict(name=node.entry.func_cname)
- self.tb.start('StepIntoFunction', attrs=attrs)
- self.tb.end('StepIntoFunction')
- self.visitchildren(node)
- return node
- def serialize_modulenode_as_function(self, node):
- """
- Serialize the module-level code as a function so the debugger will know
- it's a "relevant frame" and it will know where to set the breakpoint
- for 'break modulename'.
- """
- name = node.full_module_name.rpartition('.')[-1]
- cname_py2 = 'init' + name
- cname_py3 = 'PyInit_' + name
- py2_attrs = dict(
- name=name,
- cname=cname_py2,
- pf_cname='',
- # Ignore the qualified_name, breakpoints should be set using
- # `cy break modulename:lineno` for module-level breakpoints.
- qualified_name='',
- lineno='1',
- is_initmodule_function="True",
- )
- py3_attrs = dict(py2_attrs, cname=cname_py3)
- self._serialize_modulenode_as_function(node, py2_attrs)
- self._serialize_modulenode_as_function(node, py3_attrs)
- def _serialize_modulenode_as_function(self, node, attrs):
- self.tb.start('Function', attrs=attrs)
- self.tb.start('Locals')
- self.serialize_local_variables(node.scope.entries)
- self.tb.end('Locals')
- self.tb.start('Arguments')
- self.tb.end('Arguments')
- self.tb.start('StepIntoFunctions')
- self.register_stepinto = True
- self.visitchildren(node)
- self.register_stepinto = False
- self.tb.end('StepIntoFunctions')
- self.tb.end('Function')
- def serialize_local_variables(self, entries):
- for entry in entries.values():
- if not entry.cname:
- # not a local variable
- continue
- if entry.type.is_pyobject:
- vartype = 'PythonObject'
- else:
- vartype = 'CObject'
- if entry.from_closure:
- # We're dealing with a closure where a variable from an outer
- # scope is accessed, get it from the scope object.
- cname = '%s->%s' % (Naming.cur_scope_cname,
- entry.outer_entry.cname)
- qname = '%s.%s.%s' % (entry.scope.outer_scope.qualified_name,
- entry.scope.name,
- entry.name)
- elif entry.in_closure:
- cname = '%s->%s' % (Naming.cur_scope_cname,
- entry.cname)
- qname = entry.qualified_name
- else:
- cname = entry.cname
- qname = entry.qualified_name
- if not entry.pos:
- # this happens for variables that are not in the user's code,
- # e.g. for the global __builtins__, __doc__, etc. We can just
- # set the lineno to 0 for those.
- lineno = '0'
- else:
- lineno = str(entry.pos[1])
- attrs = dict(
- name=entry.name,
- cname=cname,
- qualified_name=qname,
- type=vartype,
- lineno=lineno)
- self.tb.start('LocalVar', attrs)
- self.tb.end('LocalVar')
|