resolve name conflicts rather than just giving up

this lets us merge libraries without having to mutate `other`, which
brings a bunch of problems around cache invalidation
This commit is contained in:
jan 2022-02-27 21:18:27 -08:00
parent 566ba99f9c
commit 2e5d51b0af
2 changed files with 56 additions and 20 deletions

View File

@ -88,7 +88,12 @@ class DeviceLibrary:
"""
self.generators[const.pattern.name] = lambda: const
def add(self: L, other: L) -> L:
def add(
self: D,
other: D,
use_ours: Callable[[str], bool] = lambda name: False,
use_theirs: Callable[[str], bool] = lambda name: False,
) -> D:
"""
Add keys from another library into this one.
@ -96,15 +101,25 @@ class DeviceLibrary:
Args:
other: The library to insert keys from
use_ours: Decision function for name conflicts. Will be called with duplicate cell names.
Should return `True` if the value from `self` should be used.
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
Should return `True` if the value from `other` should be used.
`use_ours` takes priority over `use_theirs`.
Returns:
self
"""
conflicts = [key for key in other.generators
if key in self.generators]
duplicates = set(self.keys()) & set(other.keys())
keep_ours = set(name for name in duplicates if use_ours(name))
keep_theirs = set(name for name in duplicates - keep_ours if use_theirs(name))
conflicts = duplicates - keep_ours - keep_theirs
if conflicts:
raise LibraryError('Duplicate keys encountered in library merge: ' + pformat(conflicts))
raise DeviceLibraryError('Duplicate keys encountered in DeviceLibrary merge: '
+ pformat(conflicts))
self.generators.update(other.generators)
self.cache.update(other.cache)
for name in set(other.generators.keys()) - keep_ours:
self.generators[name] = other.generators[name]
if name in other.cache:
self.cache[name] = other.cache[name]
return self

View File

@ -205,31 +205,52 @@ class Library:
_ = self.get_secondary(*key2)
return self
def add(self: L, other: L) -> L:
def add(
self: L,
other: L,
use_ours: Callable[[Union[str, Tuple[str, str]]], bool] = lambda name: False,
use_theirs: Callable[[Union[str, Tuple[str, str]]], bool] = lambda name: False,
) -> L:
"""
Add keys from another library into this one.
There must be no conflicting keys.
Args:
other: The library to insert keys from
use_ours: Decision function for name conflicts.
May be called with cell names and (name, tag) tuples for primary or
secondary cells, respectively.
Should return `True` if the value from `self` should be used.
use_theirs: Decision function for name conflicts. Same format as `use_ours`.
Should return `True` if the value from `other` should be used.
`use_ours` takes priority over `use_theirs`.
Returns:
self
"""
conflicts = [key for key in other.primary
if key in self.primary]
if conflicts:
raise LibraryError('Duplicate keys encountered in library merge: ' + pformat(conflicts))
duplicates1 = set(self.primary.keys()) & set(other.primary.keys())
duplicates2 = set(self.secondary.keys()) & set(other.secondary.keys())
keep_ours1 = set(name for name in duplicates1 if use_ours(name))
keep_ours2 = set(name for name in duplicates2 if use_ours(name))
keep_theirs1 = set(name for name in duplicates1 - keep_ours1 if use_theirs(name))
keep_theirs2 = set(name for name in duplicates2 - keep_ours2 if use_theirs(name))
conflicts1 = duplicates1 - keep_ours1 - keep_theirs1
conflicts2 = duplicates2 - keep_ours2 - keep_theirs2
if conflicts1:
raise LibraryError('Unresolved duplicate keys encountered in library merge: ' + pformat(conflicts1))
conflicts2 = [key2 for key2 in other.secondary
if key2 in self.secondary]
if conflicts2:
raise LibraryError('Duplicate secondary keys encountered in library merge: ' + pformat(conflicts2))
raise LibraryError('Unresolved duplicate secondary keys encountered in library merge: ' + pformat(conflicts2))
for key1 in set(other.primary.keys()) - keep_ours1:
self[key1] = other.primary[key1]
if key1 in other.cache:
self.cache[key1] = other.cache[key1]
for key2 in set(other.secondary.keys()) - keep_ours2:
self.set_secondary(*key2, other.secondary[key2])
if key2 in other.cache:
self.cache[key2] = other.cache[key2]
self.primary.update(other.primary)
self.secondary.update(other.secondary)
self.cache.update(other.cache)
return self
def demote(self, key: str) -> None: