frompypy.annotation.modelimportSomeObject,s_ImpossibleValuefrompypy.annotation.modelimportSomeList,SomeStringfrompypy.annotation.modelimportunionof,TLS,UnionErrorclassTooLateForChange(Exception):passclassListChangeUnallowed(Exception):passclassListItem(object):mutated=False# True for lists mutated after creationresized=False# True for lists resized after creationrange_step=None# the step -- only for lists only created by a range()dont_change_any_more=False# set to True when too late for changesimmutable=False# for getattr out of _immutable_fields_ = ['attr[*]']must_not_resize=False# make_sure_not_resized()# what to do if range_step is different in merge.# - if one is a list (range_step is None), unify to a list.# - if both have a step, unify to use a variable step (indicated by 0)_step_map={(type(None),int):None,(int,type(None)):None,(int,int):0,}def__init__(self,bookkeeper,s_value):self.s_value=s_valueself.bookkeeper=bookkeeperself.itemof={}# set of all ListDefs using this ListItemself.read_locations={}ifbookkeeperisNone:self.dont_change_any_more=Truedefmutate(self):ifnotself.mutated:ifself.dont_change_any_more:raiseTooLateForChangeself.immutable=Falseself.mutated=Truedefresize(self):ifnotself.resized:ifself.dont_change_any_more:raiseTooLateForChangeifself.must_not_resize:raiseListChangeUnallowed("resizing list")self.resized=Truedefsetrangestep(self,step):ifstep!=self.range_step:ifself.dont_change_any_more:raiseTooLateForChangeself.range_step=stepdefmerge(self,other):ifselfisnotother:ifgetattr(TLS,'no_side_effects_in_union',0):raiseUnionError("merging list/dict items")ifother.dont_change_any_more:ifself.dont_change_any_more:raiseTooLateForChangeelse:# lists using 'other' don't expect it to change any more,# so we try merging into 'other', which will give# TooLateForChange if it actually tries to make# things more generalself,other=other,selfself.immutable&=other.immutableifother.must_not_resize:ifself.resized:raiseListChangeUnallowed("list merge with a resized")self.must_not_resize=Trueifother.mutated:self.mutate()ifother.resized:self.resize()ifother.range_step!=self.range_step:self.setrangestep(self._step_map[type(self.range_step),type(other.range_step)])self.itemof.update(other.itemof)read_locations=self.read_locations.copy()other_read_locations=other.read_locations.copy()self.read_locations.update(other.read_locations)s_value=self.s_values_other_value=other.s_values_new_value=unionof(s_value,s_other_value)ifs_new_value!=s_value:ifself.dont_change_any_more:raiseTooLateForChangeself.patch()# which should patch all refs to 'other'ifs_new_value!=s_value:self.s_value=s_new_value# reflow from reading pointsforposition_keyinread_locations:self.bookkeeper.annotator.reflowfromposition(position_key)ifs_new_value!=s_other_value:# reflow from reading pointsforposition_keyinother_read_locations:other.bookkeeper.annotator.reflowfromposition(position_key)defpatch(self):forlistdefinself.itemof:listdef.listitem=selfdefgeneralize(self,s_other_value):s_new_value=unionof(self.s_value,s_other_value)updated=s_new_value!=self.s_valueifupdated:ifself.dont_change_any_more:raiseTooLateForChangeself.s_value=s_new_value# reflow from all reading pointsforposition_keyinself.read_locations:self.bookkeeper.annotator.reflowfromposition(position_key)returnupdatedclassListDef(object):"""A list definition remembers how general the items in that particular list have to be. Every list creation makes a new ListDef, and the union of two lists merges the ListItems that each ListDef stores."""def__init__(self,bookkeeper,s_item=s_ImpossibleValue,mutated=False,resized=False):self.listitem=ListItem(bookkeeper,s_item)self.listitem.mutated=mutated|resizedself.listitem.resized=resizedself.listitem.itemof[self]=Trueself.bookkeeper=bookkeeperdefgetbookkeeper(self):ifself.bookkeeperisNone:frompypy.annotation.bookkeeperimportgetbookkeeperreturngetbookkeeper()else:returnself.bookkeeperdefread_item(self,position_key=None):ifposition_keyisNone:position_key=self.getbookkeeper().position_keyself.listitem.read_locations[position_key]=Truereturnself.listitem.s_valuedefsame_as(self,other):returnself.listitemisother.listitemdefunion(self,other):self.listitem.merge(other.listitem)returnselfdefagree(self,other):s_self_value=self.read_item()s_other_value=other.read_item()self.generalize(s_other_value)other.generalize(s_self_value)defoffspring(self,*others):s_self_value=self.read_item()s_other_values=[]forotherinothers:s_other_values.append(other.read_item())s_newlst=self.getbookkeeper().newlist(s_self_value,*s_other_values)s_newvalue=s_newlst.listdef.read_item()self.generalize(s_newvalue)forotherinothers:other.generalize(s_newvalue)returns_newlstdefgeneralize(self,s_value):self.listitem.generalize(s_value)defgeneralize_range_step(self,range_step):newlistitem=ListItem(self.listitem.bookkeeper,s_ImpossibleValue)newlistitem.range_step=range_stepself.listitem.merge(newlistitem)def__repr__(self):return'<[%r]%s%s%s%s>'%(self.listitem.s_value,self.listitem.mutatedand'm'or'',self.listitem.resizedand'r'or'',self.listitem.immutableand'I'or'',self.listitem.must_not_resizeand'!R'or'')defmutate(self):self.listitem.mutate()defresize(self):self.listitem.mutate()self.listitem.resize()defnever_resize(self):ifself.listitem.resized:raiseListChangeUnallowed("list already resized")self.listitem.must_not_resize=Truedefmark_as_immutable(self):# Sets the 'immutable' flag. Note that unlike "never resized",# the immutable flag is only a hint. It is cleared again e.g.# when we merge with a "normal" list that doesn't have it. It# is thus expected to live only shortly, mostly for the case# of writing 'x.list[n]'.self.never_resize()ifnotself.listitem.mutated:self.listitem.immutable=True#else: it's fine, don't set immutable=True at all (see# test_can_merge_immutable_list_with_regular_list)s_list_of_strings=SomeList(ListDef(None,SomeString(no_nul=True),resized=True))