Mercurial > hg
comparison rust/hg-core/src/matchers.rs @ 51571:74230abb2504 stable
match: strengthen visit_children_set invariant, Recursive means "all files"
My previous interpretation of "Recursive" was too relaxed: I thought it
instructed the caller to do something like this:
> you can stop calling `visit_children_set` because you'll need to descend into
> every directory recursively, but you should still check every file if it
> matches or not
Whereas the real instruction seems to be:
> I guarantee that everything in this subtree matches, you can stop
> querying the matcher for all files and dirs altogether.
The evidence to support this:
- the test actually passes with the stronger invariant, revealing no
exceptions from this rule
- the implementation of `visit_children_set` for `DifferenceMatcher`
clearly relies on this requirement, so it must hold for that not to
lead to bugs.
author | Arseniy Alekseyev <aalekseyev@janestreet.com> |
---|---|
date | Mon, 15 Apr 2024 16:33:37 +0100 |
parents | b39057b713b1 |
children |
comparison
equal
deleted
inserted
replaced
51570:b39057b713b1 | 51571:74230abb2504 |
---|---|
2144 path: &'a HgPath, | 2144 path: &'a HgPath, |
2145 matching: &'a Tree, | 2145 matching: &'a Tree, |
2146 visit_children_set: &'a VisitChildrenSet, | 2146 visit_children_set: &'a VisitChildrenSet, |
2147 } | 2147 } |
2148 | 2148 |
2149 fn holds(matching: &Tree, vcs: &VisitChildrenSet) -> bool { | 2149 fn holds( |
2150 matching: &Tree, | |
2151 not_matching: &Tree, | |
2152 vcs: &VisitChildrenSet, | |
2153 ) -> bool { | |
2150 match vcs { | 2154 match vcs { |
2151 VisitChildrenSet::Empty => matching.is_empty(), | 2155 VisitChildrenSet::Empty => matching.is_empty(), |
2152 VisitChildrenSet::This => { | 2156 VisitChildrenSet::This => { |
2153 // `This` does not come with any obligations. | 2157 // `This` does not come with any obligations. |
2154 true | 2158 true |
2155 } | 2159 } |
2156 VisitChildrenSet::Recursive => { | 2160 VisitChildrenSet::Recursive => { |
2157 // `Recursive` does not come with any correctness | 2161 // `Recursive` requires that *everything* in the |
2158 // obligations. | 2162 // subtree matches. This |
2159 // It instructs the caller to stop calling | 2163 // requirement is relied on for example in |
2160 // `visit_children_set` for all | 2164 // DifferenceMatcher implementation. |
2161 // descendants, so may have negative performance | 2165 not_matching.is_empty() |
2162 // implications, but we're not testing against that | |
2163 // here. | |
2164 true | |
2165 } | 2166 } |
2166 VisitChildrenSet::Set(allowed_children) => { | 2167 VisitChildrenSet::Set(allowed_children) => { |
2167 // `allowed_children` does not distinguish between | 2168 // `allowed_children` does not distinguish between |
2168 // files and directories: if it's not included, it | 2169 // files and directories: if it's not included, it |
2169 // must not be matched. | 2170 // must not be matched. |
2184 | 2185 |
2185 pub fn check<M: Matcher + std::fmt::Debug>( | 2186 pub fn check<M: Matcher + std::fmt::Debug>( |
2186 matcher: &M, | 2187 matcher: &M, |
2187 path: &HgPath, | 2188 path: &HgPath, |
2188 matching: &Tree, | 2189 matching: &Tree, |
2190 not_matching: &Tree, | |
2189 visit_children_set: &VisitChildrenSet, | 2191 visit_children_set: &VisitChildrenSet, |
2190 ) { | 2192 ) { |
2191 if !holds(matching, visit_children_set) { | 2193 if !holds(matching, not_matching, visit_children_set) { |
2192 panic!( | 2194 panic!( |
2193 "{:#?}", | 2195 "{:#?}", |
2194 Error { | 2196 Error { |
2195 matcher, | 2197 matcher, |
2196 path, | 2198 path, |
2221 | 2223 |
2222 fn is_empty(&self) -> bool { | 2224 fn is_empty(&self) -> bool { |
2223 self.files.is_empty() && self.dirs.is_empty() | 2225 self.files.is_empty() && self.dirs.is_empty() |
2224 } | 2226 } |
2225 | 2227 |
2228 fn make( | |
2229 files: BTreeSet<HgPathBuf>, | |
2230 dirs: BTreeMap<HgPathBuf, Tree>, | |
2231 ) -> Self { | |
2232 Self { | |
2233 files, | |
2234 dirs: dirs | |
2235 .into_iter() | |
2236 .filter(|(_k, v)| (!(v.is_empty()))) | |
2237 .collect(), | |
2238 } | |
2239 } | |
2240 | |
2226 fn filter_and_check<M: Matcher + Debug>( | 2241 fn filter_and_check<M: Matcher + Debug>( |
2227 &self, | 2242 &self, |
2228 m: &M, | 2243 m: &M, |
2229 path: &HgPath, | 2244 path: &HgPath, |
2230 ) -> Self { | 2245 ) -> (Self, Self) { |
2231 let files: BTreeSet<HgPathBuf> = self | 2246 let (files1, files2): (BTreeSet<HgPathBuf>, BTreeSet<HgPathBuf>) = |
2232 .files | 2247 self.files |
2233 .iter() | 2248 .iter() |
2234 .filter(|v| m.matches(&path.join(v))) | 2249 .map(|v| v.to_owned()) |
2235 .map(|f| f.to_owned()) | 2250 .partition(|v| m.matches(&path.join(v))); |
2236 .collect(); | 2251 let (dirs1, dirs2): ( |
2237 let dirs: BTreeMap<HgPathBuf, Tree> = self | 2252 BTreeMap<HgPathBuf, Tree>, |
2253 BTreeMap<HgPathBuf, Tree>, | |
2254 ) = self | |
2238 .dirs | 2255 .dirs |
2239 .iter() | 2256 .iter() |
2240 .filter_map(|(k, v)| { | 2257 .map(|(k, v)| { |
2241 let path = path.join(k); | 2258 let path = path.join(k); |
2242 let v = v.filter_and_check(m, &path); | 2259 let (t1, t2) = v.filter_and_check(m, &path); |
2243 if v.is_empty() { | 2260 ((k.clone(), t1), (k.clone(), t2)) |
2244 None | |
2245 } else { | |
2246 Some((k.to_owned(), v)) | |
2247 } | |
2248 }) | 2261 }) |
2249 .collect(); | 2262 .unzip(); |
2250 let matching = Self { files, dirs }; | 2263 let matching = Self::make(files1, dirs1); |
2264 let not_matching = Self::make(files2, dirs2); | |
2251 let vcs = m.visit_children_set(path); | 2265 let vcs = m.visit_children_set(path); |
2252 invariants::visit_children_set::check(m, path, &matching, &vcs); | 2266 invariants::visit_children_set::check( |
2253 matching | 2267 m, |
2268 path, | |
2269 &matching, | |
2270 ¬_matching, | |
2271 &vcs, | |
2272 ); | |
2273 (matching, not_matching) | |
2254 } | 2274 } |
2255 | 2275 |
2256 fn check_matcher<M: Matcher + Debug>( | 2276 fn check_matcher<M: Matcher + Debug>( |
2257 &self, | 2277 &self, |
2258 m: &M, | 2278 m: &M, |
2259 expect_count: usize, | 2279 expect_count: usize, |
2260 ) { | 2280 ) { |
2261 let res = self.filter_and_check(m, &HgPathBuf::new()); | 2281 let res = self.filter_and_check(m, &HgPathBuf::new()); |
2262 if expect_count != res.len() { | 2282 if expect_count != res.0.len() { |
2263 eprintln!( | 2283 eprintln!( |
2264 "warning: expected {} matches, got {} for {:#?}", | 2284 "warning: expected {} matches, got {} for {:#?}", |
2265 expect_count, | 2285 expect_count, |
2266 res.len(), | 2286 res.0.len(), |
2267 m | 2287 m |
2268 ); | 2288 ); |
2269 } | 2289 } |
2270 } | 2290 } |
2271 } | 2291 } |