hh_fanout test binary; start, stop, edges actions
[hiphop-php.git] / hphp / hack / src / utils / hh24_types / hh24_types.rs
blobb6479d13cc0375440cf83e63b59b69db18a36bd0
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 //! Common types used in the HH24 Hack typechecker rearchitecture.
8 // Common impls for types which wrap a hash value represented by u64.
9 macro_rules! u64_hash_wrapper_impls {
10     ($name:ident) => {
11         impl $name {
12             #[inline]
13             pub fn from_u64(hash: u64) -> Self {
14                 Self(hash)
15             }
16             #[inline]
17             pub fn as_u64(self) -> u64 {
18                 self.0
19             }
20         }
22         impl std::fmt::Debug for $name {
23             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24                 write!(f, concat!(stringify!($name), "({:x})"), self.0)
25             }
26         }
28         impl std::str::FromStr for $name {
29             type Err = std::num::ParseIntError;
30             fn from_str(s: &str) -> Result<Self, Self::Err> {
31                 Ok(Self(u64::from_str_radix(s, 16)?))
32             }
33         }
35         impl nohash_hasher::IsEnabled for $name {}
37         impl rusqlite::ToSql for $name {
38             fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
39                 Ok(rusqlite::types::ToSqlOutput::from(self.0 as i64))
40             }
41         }
43         impl rusqlite::types::FromSql for $name {
44             fn column_result(
45                 value: rusqlite::types::ValueRef<'_>,
46             ) -> rusqlite::types::FromSqlResult<Self> {
47                 Ok(Self(value.as_i64()? as u64))
48             }
49         }
50     };
53 /// TODO(ljw): add backtraces to the three expected cases.
54 /// But let's hold off until we've adopted thiserror 1.0.34 and rustc post backtrace stabilization
55 #[derive(thiserror::Error, Debug)]
56 pub enum HhError {
57     #[error("Unexpected: {0:#}")]
58     Unexpected(anyhow::Error),
60     #[error("Disk changed: {0} - do hh_decl --update then restart the operation. [{1}]")]
61     DiskChanged(std::path::PathBuf, String),
63     #[error("Decl-store changed its checksum: {0:?} - restart the operation. [{1}]")]
64     ChecksumChanged(Checksum, String),
66     #[error("Decl-store stopped - abandon the operation. [{0}]")]
67     Stopped(String),
70 /// TODO(ljw): once we adopt thiserror 1.0.34 and anyhow 1.0.64, then anyhow
71 /// stacks will always be present, and we'll have no need for peppering our
72 /// codebase with .hh_context("desc") to make up for their lack. At that time,
73 /// let's delete the HhErrorContext trait and all calls to it.
74 pub trait HhErrorContext<T> {
75     fn hh_context(self, context: &'static str) -> Result<T, HhError>;
78 impl<T> HhErrorContext<T> for Result<T, HhError> {
79     fn hh_context(self, ctx: &'static str) -> Result<T, HhError> {
80         match self {
81             Ok(r) => Ok(r),
82             Err(HhError::Unexpected(err)) => Err(HhError::Unexpected(err.context(ctx))),
83             Err(HhError::DiskChanged(path, ctx0)) => {
84                 Err(HhError::DiskChanged(path, format!("{}\n{}", ctx, ctx0)))
85             }
86             Err(HhError::ChecksumChanged(checksum, ctx0)) => Err(HhError::ChecksumChanged(
87                 checksum,
88                 format!("{}\n{}", ctx, ctx0),
89             )),
90             Err(HhError::Stopped(ctx0)) => Err(HhError::Stopped(format!("{}\n{}", ctx, ctx0))),
91         }
92     }
95 impl<T> HhErrorContext<T> for Result<T, std::io::Error> {
96     #[inline(never)]
97     fn hh_context(self, context: &'static str) -> Result<T, HhError> {
98         self.map_err(|err| HhError::Unexpected(anyhow::Error::new(err).context(context)))
99     }
102 impl<T> HhErrorContext<T> for Result<T, serde_json::error::Error> {
103     #[inline(never)]
104     fn hh_context(self, context: &'static str) -> Result<T, HhError> {
105         self.map_err(|err| HhError::Unexpected(anyhow::Error::new(err).context(context)))
106     }
109 impl<T> HhErrorContext<T> for Result<T, anyhow::Error> {
110     #[inline(never)]
111     fn hh_context(self, context: &'static str) -> Result<T, HhError> {
112         self.map_err(|err| HhError::Unexpected(err.context(context)))
113     }
116 /// Checksum is used to characterize state of every decl in the repository:
117 /// if a decl is added, removed, moved from one file, changed, then the overall
118 /// checksum of the repository will change.
119 #[derive(Copy, Clone, Hash, PartialEq, Eq, Default)]
120 #[derive(serde::Deserialize, serde::Serialize)]
121 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
122 pub struct Checksum(pub u64);
123 u64_hash_wrapper_impls! { Checksum }
125 impl Checksum {
126     pub fn addremove(
127         &mut self,
128         symbol_hash: ToplevelSymbolHash,
129         decl_hash: DeclHash,
130         path: &relative_path::RelativePath,
131     ) {
132         // CARE! This implementation must be identical to that in rust_decl_ffi.rs
133         // I wrote it out as a separate copy because I didn't want hh_server to take a dependency
134         // upon hh24_types
135         self.0 ^= hh_hash::hash(&(symbol_hash, decl_hash, path));
136     }
139 #[derive(Clone, Debug)]
140 #[derive(serde::Deserialize, serde::Serialize)]
141 pub struct RichChecksum {
142     pub checksum: Checksum,
143     pub timestamp: Timestamp,
144     pub example_symbol: String,
147 impl RichChecksum {
148     pub fn to_brief_string(&self) -> String {
149         format!(
150             "RichChecksum({:x}@{}@{})",
151             self.checksum,
152             self.timestamp.unix_epoch_secs(),
153             self.example_symbol
154         )
155     }
158 impl std::str::FromStr for RichChecksum {
159     type Err = ParseRichChecksumError;
160     fn from_str(s: &str) -> Result<Self, Self::Err> {
161         let s = s.trim_start_matches("RichChecksum(");
162         let s = s.trim_end_matches(')');
163         let mut iter = s.split('@');
164         match (iter.next(), iter.next(), iter.next(), iter.next()) {
165             (Some(checksum), Some(timestamp), Some(example_symbol), None) => Ok(Self {
166                 checksum: checksum
167                     .parse()
168                     .map_err(ParseRichChecksumError::InvalidChecksum)?,
169                 timestamp: timestamp
170                     .parse()
171                     .map_err(ParseRichChecksumError::InvalidTimestamp)?,
172                 example_symbol: String::from(example_symbol),
173             }),
174             _ => Err(ParseRichChecksumError::Invalid),
175         }
176     }
179 #[derive(thiserror::Error, Debug)]
180 pub enum ParseRichChecksumError {
181     #[error("expected \"RichChecksum(<checksum>@<timestamp>@<example_symbol>)\"")]
182     Invalid,
183     #[error("{0}")]
184     InvalidChecksum(#[source] std::num::ParseIntError),
185     #[error("{0}")]
186     InvalidTimestamp(#[source] std::num::ParseIntError),
189 /// A measurement of the system clock, useful for talking to external entities
190 /// like the file system or other processes. Wraps `std::time::SystemTime`, but
191 /// implements `serde::Serialize` and `serde::Deserialize`.
193 /// Invariant: always represents a time later than the unix epoch.
194 #[derive(Copy, Clone)]
195 pub struct Timestamp(std::time::SystemTime);
197 impl Timestamp {
198     /// Returns the system time corresponding to "now".
199     pub fn now() -> Self {
200         Self(std::time::SystemTime::now())
201     }
203     /// Returns the system time corresponding to the unix epoch plus the given
204     /// number of seconds.
205     pub fn from_unix_epoch_secs(secs: u64) -> Self {
206         Self(
207             std::time::SystemTime::UNIX_EPOCH
208                 .checked_add(std::time::Duration::from_secs(secs))
209                 .expect("Seconds since UNIX_EPOCH too large to fit in SystemTime"),
210         )
211     }
213     /// Returns the number of seconds elapsed between the unix epoch and this
214     /// `Timestamp`.
215     pub fn unix_epoch_secs(&self) -> u64 {
216         self.0
217             .duration_since(std::time::SystemTime::UNIX_EPOCH)
218             .expect("Timestamp before UNIX_EPOCH")
219             .as_secs()
220     }
222     /// Returns the `SystemTime` corresponding to this `Timestamp`.
223     pub fn as_system_time(&self) -> std::time::SystemTime {
224         self.0
225     }
228 impl std::fmt::Debug for Timestamp {
229     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230         write!(f, "Timestamp({})", self.unix_epoch_secs())
231     }
234 impl std::str::FromStr for Timestamp {
235     type Err = std::num::ParseIntError;
236     fn from_str(s: &str) -> Result<Self, Self::Err> {
237         let s = s.trim_start_matches("Timestamp(");
238         let s = s.trim_end_matches(')');
239         Ok(Self::from_unix_epoch_secs(s.parse()?))
240     }
243 impl serde::Serialize for Timestamp {
244     fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
245         serializer.serialize_u64(self.unix_epoch_secs())
246     }
249 impl<'de> serde::Deserialize<'de> for Timestamp {
250     fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
251         struct Visitor;
252         impl<'de> serde::de::Visitor<'de> for Visitor {
253             type Value = Timestamp;
255             fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256                 write!(formatter, "a u64 for Timestamp")
257             }
258             fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
259                 Ok(Self::Value::from_unix_epoch_secs(value))
260             }
261         }
262         deserializer.deserialize_u64(Visitor)
263     }
266 /// The hash of a toplevel symbol name, as it appears in the 64bit dependency graph.
267 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
268 #[derive(serde::Deserialize, serde::Serialize)]
269 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
270 pub struct ToplevelSymbolHash(u64);
271 u64_hash_wrapper_impls! { ToplevelSymbolHash }
273 impl ToplevelSymbolHash {
274     pub fn new(kind: file_info::NameType, symbol: &str) -> Self {
275         Self(typing_deps_hash::hash1(kind.into(), symbol.as_bytes()))
276     }
278     pub fn from_type(symbol: &str) -> Self {
279         // Could also be a NameType::Typedef, but both Class and Typedef are
280         // represented with DepType::Type. See test_dep_type_from_name_type below.
281         Self::new(file_info::NameType::Class, symbol)
282     }
284     pub fn from_fun(symbol: &str) -> Self {
285         Self::new(file_info::NameType::Fun, symbol)
286     }
288     pub fn from_const(symbol: &str) -> Self {
289         Self::new(file_info::NameType::Const, symbol)
290     }
292     pub fn from_module(symbol: &str) -> Self {
293         Self::new(file_info::NameType::Module, symbol)
294     }
296     #[inline(always)]
297     pub fn to_bytes(self) -> [u8; 8] {
298         self.0.to_be_bytes()
299     }
301     #[inline(always)]
302     pub fn from_bytes(bs: [u8; 8]) -> Self {
303         Self(u64::from_be_bytes(bs))
304     }
306     #[inline(always)]
307     pub fn to_dependency_hash(self) -> DependencyHash {
308         DependencyHash(self.0)
309     }
312 /// The "canon hash" of a toplevel symbol name (i.e., the hash of the symbol
313 /// name after ASCII characters in the name have been converted to lowercase),
314 /// as it appears in the naming table.
315 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
316 #[derive(serde::Deserialize, serde::Serialize)]
317 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
318 pub struct ToplevelCanonSymbolHash(u64);
319 u64_hash_wrapper_impls! { ToplevelCanonSymbolHash }
321 impl ToplevelCanonSymbolHash {
322     pub fn new(kind: file_info::NameType, mut symbol: String) -> Self {
323         symbol.make_ascii_lowercase();
324         Self(typing_deps_hash::hash1(kind.into(), symbol.as_bytes()))
325     }
327     pub fn from_type(symbol: String) -> Self {
328         // Could also be a NameType::Typedef, but both Class and Typedef are
329         // represented with DepType::Type. See test_dep_type_from_name_type below.
330         Self::new(file_info::NameType::Class, symbol)
331     }
333     pub fn from_fun(symbol: String) -> Self {
334         Self::new(file_info::NameType::Fun, symbol)
335     }
337     pub fn from_const(symbol: String) -> Self {
338         Self::new(file_info::NameType::Const, symbol)
339     }
341     pub fn from_module(symbol: String) -> Self {
342         Self::new(file_info::NameType::Module, symbol)
343     }
346 /// The hash of a toplevel symbol name, or the hash of a class member name, or
347 /// an Extends or AllMembers hash for a class name.
348 /// See `Typing_deps.Dep.(dependency variant)`.
349 #[derive(
350     Copy,
351     Clone,
352     PartialEq,
353     Eq,
354     PartialOrd,
355     Ord,
356     Hash,
357     serde::Deserialize,
358     serde::Serialize
360 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
361 pub struct DependencyHash(pub u64);
363 impl DependencyHash {
364     pub fn of_member(
365         dep_type: typing_deps_hash::DepType,
366         class_symbol: ToplevelSymbolHash,
367         member_name: &str,
368     ) -> DependencyHash {
369         let type_hash =
370             typing_deps_hash::hash1(typing_deps_hash::DepType::Type, &class_symbol.to_bytes());
371         Self(typing_deps_hash::hash2(
372             dep_type,
373             type_hash,
374             member_name.as_bytes(),
375         ))
376     }
378     pub fn of_class(
379         dep_type: typing_deps_hash::DepType,
380         class_symbol: ToplevelSymbolHash,
381     ) -> DependencyHash {
382         Self(typing_deps_hash::hash1(dep_type, &class_symbol.to_bytes()))
383     }
386 impl std::fmt::Debug for DependencyHash {
387     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388         write!(f, "DependencyHash({:x})", self.0)
389     }
392 impl std::str::FromStr for DependencyHash {
393     type Err = std::num::ParseIntError;
394     fn from_str(s: &str) -> Result<Self, Self::Err> {
395         Ok(Self(u64::from_str_radix(s, 16)?))
396     }
399 // A `ToplevelSymbolHash` is a valid `DependencyHash`, but not all
400 // `DependencyHash` values represent a toplevel symbol.
401 impl From<ToplevelSymbolHash> for DependencyHash {
402     fn from(hash: ToplevelSymbolHash) -> Self {
403         Self(hash.0)
404     }
407 impl From<DependencyHash> for ToplevelSymbolHash {
408     fn from(hash: DependencyHash) -> Self {
409         Self(hash.0)
410     }
413 impl DependencyHash {
414     #[inline(always)]
415     pub fn to_bytes(self) -> [u8; 8] {
416         self.0.to_be_bytes()
417     }
419     #[inline(always)]
420     pub fn from_bytes(bs: [u8; 8]) -> Self {
421         Self(u64::from_be_bytes(bs))
422     }
425 #[derive(
426     Copy,
427     Clone,
428     Debug,
429     PartialEq,
430     Eq,
431     Hash,
432     PartialOrd,
433     Ord,
434     serde::Deserialize,
435     serde::Serialize
437 pub struct DepgraphEdge {
438     pub dependency: DependencyHash,
439     pub dependent: ToplevelSymbolHash,
442 impl std::str::FromStr for DepgraphEdge {
443     type Err = ParseDepgraphEdgeError;
444     fn from_str(s: &str) -> Result<Self, Self::Err> {
445         let mut iter = s.split(':');
446         match (iter.next(), iter.next(), iter.next()) {
447             (Some(dependency), Some(dependent), None) => Ok(Self {
448                 dependency: dependency.parse()?,
449                 dependent: dependent.parse()?,
450             }),
451             _ => Err(ParseDepgraphEdgeError::Invalid(s.to_owned())),
452         }
453     }
456 #[derive(thiserror::Error, Debug)]
457 pub enum ParseDepgraphEdgeError {
458     #[error("expected dependency_hash:dependent_hash format. actual \"{0}\"")]
459     Invalid(String),
460     #[error("{0}")]
461     FromInt(#[from] std::num::ParseIntError),
464 /// The position-insensitive hash of the `Decls` produced by running the direct
465 /// decl parser on a file. Used in the NAMING_FILE_INFO table.
466 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
467 #[derive(serde::Deserialize, serde::Serialize)]
468 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
469 pub struct FileDeclsHash(u64);
470 u64_hash_wrapper_impls! { FileDeclsHash }
472 /// The position-insensitive hash of a decl (the type signature of a toplevel
473 /// declaration), as it appears in the naming table. Used in the NAMING_FUNS,
474 /// NAMING_CONSTS, and NAMING_TYPES tables (in the near future).
475 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
476 #[derive(serde::Deserialize, serde::Serialize)]
477 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
478 pub struct DeclHash(u64);
479 u64_hash_wrapper_impls! { DeclHash }
481 /// This type is for serializing an anyhow error. What you get out will print with
482 /// the same information as the original anyhow, but won't look quite as pretty
483 /// and doesn't support downcasting.
484 #[derive(Debug, serde::Serialize, serde::Deserialize)]
485 pub struct StringifiedError {
486     pub chain: Vec<String>, // invariant: this has at least 1 element
487     pub backtrace: String,
490 impl StringifiedError {
491     pub fn from_anyhow(err: anyhow::Error) -> Self {
492         let chain = err.chain().map(|c| format!("{}", c)).rev().collect();
493         let backtrace = format!("{:?}", err);
494         Self { chain, backtrace }
495     }
497     pub fn to_anyhow(self) -> anyhow::Error {
498         let mut e = anyhow::anyhow!("StringifiedError");
499         e = e.context(self.backtrace);
500         for cause in self.chain {
501             e = e.context(cause);
502         }
503         e
504     }
507 impl std::fmt::Display for StringifiedError {
508     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
509         write!(f, "{}", self.chain[self.chain.len() - 1])
510     }
513 #[cfg(test)]
514 mod tests {
515     use anyhow::Context;
517     use super::*; // to bring Result<T,E>.context into scope
519     fn stringify_inner() -> anyhow::Result<()> {
520         anyhow::bail!("oops");
521     }
523     fn stringify_middle() -> anyhow::Result<()> {
524         stringify_inner().context("ctx_middle")
525     }
527     fn stringify_outer() -> anyhow::Result<()> {
528         stringify_middle().context("ctx_outer")
529     }
531     #[test]
532     fn stringify_without_backtrace() {
533         match stringify_outer() {
534             Ok(()) => panic!("test wanted to see an error"),
535             Err(err1) => {
536                 let err2 = StringifiedError::from_anyhow(err1);
537                 let err3 = err2.to_anyhow();
538                 let display = format!("{}", err3);
539                 assert_eq!(display, "ctx_outer");
540                 let debug = format!("{:?}", err3);
541                 assert!(debug.contains("ctx_outer"));
542                 assert!(debug.contains("0: ctx_middle"));
543                 assert!(debug.contains("1: oops"));
544             }
545         }
546     }
548     #[test]
549     fn test_dep_type_from_name_type() {
550         assert_eq!(
551             typing_deps_hash::DepType::from(file_info::NameType::Class),
552             typing_deps_hash::DepType::from(file_info::NameType::Typedef)
553         );
554     }