1 // Copyright (c) Facebook, Inc. and its affiliates.
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 {
13 pub fn from_u64(hash: u64) -> Self {
17 pub fn as_u64(self) -> u64 {
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)
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)?))
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))
43 impl rusqlite::types::FromSql for $name {
45 value: rusqlite::types::ValueRef<'_>,
46 ) -> rusqlite::types::FromSqlResult<Self> {
47 Ok(Self(value.as_i64()? as u64))
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)]
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}]")]
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> {
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)))
86 Err(HhError::ChecksumChanged(checksum, ctx0)) => Err(HhError::ChecksumChanged(
88 format!("{}\n{}", ctx, ctx0),
90 Err(HhError::Stopped(ctx0)) => Err(HhError::Stopped(format!("{}\n{}", ctx, ctx0))),
95 impl<T> HhErrorContext<T> for Result<T, std::io::Error> {
97 fn hh_context(self, context: &'static str) -> Result<T, HhError> {
98 self.map_err(|err| HhError::Unexpected(anyhow::Error::new(err).context(context)))
102 impl<T> HhErrorContext<T> for Result<T, serde_json::error::Error> {
104 fn hh_context(self, context: &'static str) -> Result<T, HhError> {
105 self.map_err(|err| HhError::Unexpected(anyhow::Error::new(err).context(context)))
109 impl<T> HhErrorContext<T> for Result<T, anyhow::Error> {
111 fn hh_context(self, context: &'static str) -> Result<T, HhError> {
112 self.map_err(|err| HhError::Unexpected(err.context(context)))
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 }
128 symbol_hash: ToplevelSymbolHash,
130 path: &relative_path::RelativePath,
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
135 self.0 ^= hh_hash::hash(&(symbol_hash, decl_hash, path));
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,
148 pub fn to_brief_string(&self) -> String {
150 "RichChecksum({:x}@{}@{})",
152 self.timestamp.unix_epoch_secs(),
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 {
168 .map_err(ParseRichChecksumError::InvalidChecksum)?,
171 .map_err(ParseRichChecksumError::InvalidTimestamp)?,
172 example_symbol: String::from(example_symbol),
174 _ => Err(ParseRichChecksumError::Invalid),
179 #[derive(thiserror::Error, Debug)]
180 pub enum ParseRichChecksumError {
181 #[error("expected \"RichChecksum(<checksum>@<timestamp>@<example_symbol>)\"")]
184 InvalidChecksum(#[source] std::num::ParseIntError),
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);
198 /// Returns the system time corresponding to "now".
199 pub fn now() -> Self {
200 Self(std::time::SystemTime::now())
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 {
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"),
213 /// Returns the number of seconds elapsed between the unix epoch and this
215 pub fn unix_epoch_secs(&self) -> u64 {
217 .duration_since(std::time::SystemTime::UNIX_EPOCH)
218 .expect("Timestamp before UNIX_EPOCH")
222 /// Returns the `SystemTime` corresponding to this `Timestamp`.
223 pub fn as_system_time(&self) -> std::time::SystemTime {
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())
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()?))
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())
249 impl<'de> serde::Deserialize<'de> for Timestamp {
250 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
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")
258 fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
259 Ok(Self::Value::from_unix_epoch_secs(value))
262 deserializer.deserialize_u64(Visitor)
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()))
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)
284 pub fn from_fun(symbol: &str) -> Self {
285 Self::new(file_info::NameType::Fun, symbol)
288 pub fn from_const(symbol: &str) -> Self {
289 Self::new(file_info::NameType::Const, symbol)
292 pub fn from_module(symbol: &str) -> Self {
293 Self::new(file_info::NameType::Module, symbol)
297 pub fn to_bytes(self) -> [u8; 8] {
302 pub fn from_bytes(bs: [u8; 8]) -> Self {
303 Self(u64::from_be_bytes(bs))
307 pub fn to_dependency_hash(self) -> DependencyHash {
308 DependencyHash(self.0)
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()))
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)
333 pub fn from_fun(symbol: String) -> Self {
334 Self::new(file_info::NameType::Fun, symbol)
337 pub fn from_const(symbol: String) -> Self {
338 Self::new(file_info::NameType::Const, symbol)
341 pub fn from_module(symbol: String) -> Self {
342 Self::new(file_info::NameType::Module, symbol)
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)`.
360 #[derive(derive_more::UpperHex, derive_more::LowerHex)]
361 pub struct DependencyHash(pub u64);
363 impl DependencyHash {
365 dep_type: typing_deps_hash::DepType,
366 class_symbol: ToplevelSymbolHash,
368 ) -> DependencyHash {
370 typing_deps_hash::hash1(typing_deps_hash::DepType::Type, &class_symbol.to_bytes());
371 Self(typing_deps_hash::hash2(
374 member_name.as_bytes(),
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()))
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)
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)?))
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 {
407 impl From<DependencyHash> for ToplevelSymbolHash {
408 fn from(hash: DependencyHash) -> Self {
413 impl DependencyHash {
415 pub fn to_bytes(self) -> [u8; 8] {
420 pub fn from_bytes(bs: [u8; 8]) -> Self {
421 Self(u64::from_be_bytes(bs))
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()?,
451 _ => Err(ParseDepgraphEdgeError::Invalid(s.to_owned())),
456 #[derive(thiserror::Error, Debug)]
457 pub enum ParseDepgraphEdgeError {
458 #[error("expected dependency_hash:dependent_hash format. actual \"{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 }
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);
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])
517 use super::*; // to bring Result<T,E>.context into scope
519 fn stringify_inner() -> anyhow::Result<()> {
520 anyhow::bail!("oops");
523 fn stringify_middle() -> anyhow::Result<()> {
524 stringify_inner().context("ctx_middle")
527 fn stringify_outer() -> anyhow::Result<()> {
528 stringify_middle().context("ctx_outer")
532 fn stringify_without_backtrace() {
533 match stringify_outer() {
534 Ok(()) => panic!("test wanted to see an error"),
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"));
549 fn test_dep_type_from_name_type() {
551 typing_deps_hash::DepType::from(file_info::NameType::Class),
552 typing_deps_hash::DepType::from(file_info::NameType::Typedef)