enum Flag {Method, MethodOrField, Public} /* There is a negative version for each flag (METHOD and NOT_METHOD). Model this as two sets, one of positive flags, and one of netative flags, and interpret the bitfield to be a conjunction of both flags. */ sig Bitfield { , positiveFlags: set Flag , negativeFlags: set Flag } /* A filter state has filterFlags and excludeFlags, both represented as conjunctions. */ sig FilterState { , include: Bitfield , exclude: Bitfield } /* Initially, no search has happeneed for a scope, so its 'found' is not set to anything. */ one sig NotSet {} /* Finally, there's a search state (whether or not a particular scope has already been searched with a particular configuration). */ one sig SearchState { , var found: Bitfield + NotSet } pred bitfieldEmpty[b: Bitfield] { #b.positiveFlags = 0 and #b.negativeFlags = 0 } pred bitfieldEqual[b1: Bitfield, b2: Bitfield] { b1.positiveFlags = b2.positiveFlags and b1.negativeFlags = b2.negativeFlags } pred bitfieldIntersection[b1: Bitfield, b2: Bitfield, b3: Bitfield] { b3.positiveFlags = b1.positiveFlags & b2.positiveFlags b3.negativeFlags = b1.negativeFlags & b2.negativeFlags } pred bitfieldSubset[b1: Bitfield, b2: Bitfield] { b1.positiveFlags in b2.positiveFlags b1.negativeFlags in b2.negativeFlags } pred bitfieldIncomparable[b1: Bitfield, b2: Bitfield] { not bitfieldSubset[b1, b2] not bitfieldSubset[b2, b1] } pred addBitfieldFlag[b1: Bitfield, b2: Bitfield, flag: Flag] { b2.positiveFlags = b1.positiveFlags + flag b2.negativeFlags = b1.negativeFlags } pred addBitfieldFlagNeg[b1: Bitfield, b2: Bitfield, flag: Flag] { b2.negativeFlags = b1.negativeFlags + flag b2.positiveFlags = b1.positiveFlags } enum Property { PMethod, PField, PPublic } sig Props { properties: set Property } pred flagMatchesPropery[flag: Flag, property: Property] { (flag = Method and property = PMethod) or (flag = MethodOrField and (property = PMethod or property = PField)) or (flag = Public and property = PPublic) } pred bitfieldMatchesProperties[bitfield: Bitfield, props: Props] { // All positive flags must be satisifed all flag: bitfield.positiveFlags | some property: props.properties | flagMatchesPropery[flag, property] // All negative flags must not be satisfied all flag: bitfield.negativeFlags | no property: props.properties | flagMatchesPropery[flag, property] } pred configureState[filterState: FilterState] { some initialState: FilterState { // Each lookup in scope starts with empty filter and exclude flags bitfieldEmpty[initialState.include] and bitfieldEmpty[initialState.exclude] // The intermediate states (bf1) are used for sequencing of operations. some bf1 : Bitfield { // Add "Public" depending on skipPrivateVisibilities addBitfieldFlag[initialState.include, bf1, Public] or bitfieldEqual[initialState.include, bf1] // If it's a method receiver, add method or field restriction addBitfieldFlag[bf1, filterState.include, MethodOrField] or // if it's not a receiver, filter to non-methods (could be overridden) addBitfieldFlagNeg[bf1, filterState.include, Method] or // Maybe methods are not being included but it's not a receiver, so no change. bitfieldEqual[bf1, filterState.include] } // Exclude filter doesn't change here initialState.exclude = filterState.exclude } } pred oldUpdate[toSet: Bitfield + NotSet, setTo: FilterState] { toSet' in Bitfield and bitfieldIntersection[toSet, setTo.include, toSet'] } pred newUpdate[toSet: Bitfield + NotSet, setTo: FilterState] { (not bitfieldIncomparable[toSet, setTo.include] and oldUpdate[toSet, setTo]) or (bitfieldIncomparable[toSet, setTo.include] and toSet = toSet') } pred updateOrSet[toSet: Bitfield + NotSet, setTo: FilterState] { (toSet in NotSet and toSet' = setTo.include) or (toSet not in NotSet and oldUpdate[toSet, setTo]) } pred excludeBitfield[found: Bitfield + NotSet, exclude: Bitfield] { (found != NotSet and bitfieldEqual[found, exclude]) or (found = NotSet and bitfieldEmpty[exclude]) } fact init { all searchState: SearchState | searchState.found = NotSet } fact step { always { // Model that a new doLookupInScope could've occurred, with any combination of flags. all searchState: SearchState { some fs: FilterState { // This is a possible combination of lookup flags configureState[fs] // If a search has been performed before, take the intersection; otherwise, // just insert the current filter flags. updateOrSet[searchState.found, fs] } } } } example: run { all searchState: SearchState { // a way that subsequent results of searching it will miss things. eventually some props: Props, fs: FilterState, fsBroken: FilterState, exclude: Bitfield { // Some search (fs) will cause a transition / modification of the search state... configureState[fs] updateOrSet[searchState.found, fs] // Such that a later, valid search... (fsBroken) configureState[fsBroken] excludeBitfield[searchState.found', exclude] // Will allow for a set of properties... // ... that are left out of the original search... not bitfieldMatchesProperties[searchState.found, props] // ... and out of the current search not (bitfieldMatchesProperties[fs.include, props] and not bitfieldMatchesProperties[searchState.found, props]) // But would be matched by the broken search... bitfieldMatchesProperties[fsBroken.include, props] // ... to not be matched by a search with the new state: not (bitfieldMatchesProperties[fsBroken.include, props] and not bitfieldMatchesProperties[exclude, props]) } } }