diff --git a/.hhconfig b/.hhconfig index 4853ace..dc9eca5 100644 --- a/.hhconfig +++ b/.hhconfig @@ -10,3 +10,4 @@ disallow_array_literal = true disallow_silence = true allowed_decl_fixme_codes=2053,4045,4047 allowed_fixme_codes_strict=2011,2049,2050,2053,4027,4045,4047,4104,4106,4107,4108,4110,4128,4135,4188,4240,4323,4390,4401 +; enable_strict_string_concat_interp = true diff --git a/src/Assert.hack b/src/Assert.hack index ef99dda..70712ca 100644 --- a/src/Assert.hack +++ b/src/Assert.hack @@ -531,16 +531,29 @@ abstract class Assert { string $msg = '', string $path = '$actual', ): void { + // This is very not-Hack like. + // $expected is (object | KeyedTraversable) foreach ($expected as $key => $value) { if ($actual is KeyedContainer<_, _>) { + // $key is mixed, so `?as arraykey` can fail (to null) + // This makes `$actual_value` null as well... $actual_value = idx($actual, $key ?as arraykey); $part = '['.\var_export($key, true).']'; } else if (\is_object($actual)) { + // stdClass can have integer-like object properties. + // Indexing them with $actual->$int_one` is very weird, but okay. + // Indexing them with float, bool something that coerces, is a bug in your test. + // Let's fail loudly instead of silently depending on implicit casts. + invariant( + $key is arraykey, + 'Attempting to access an object property with a %s name', + \gettype($key), + ); $actual_value = /* HH_FIXME[2011] Dynamic property access */ $actual->$key; $part = '->'.$key; } else { $actual_value = null; - $part = null; + $part = ''; } if (is_any_array($value) || \is_object($value)) {