2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/representation.h"
19 #include <boost/algorithm/string.hpp>
23 #include <folly/Conv.h>
24 #include <folly/Format.h>
25 #include <folly/String.h>
26 #include <folly/Range.h>
27 #include <folly/gen/Base.h>
28 #include <folly/gen/String.h>
30 #include "hphp/hhbbc/analyze.h"
31 #include "hphp/hhbbc/cfg.h"
32 #include "hphp/hhbbc/class-util.h"
33 #include "hphp/hhbbc/context.h"
34 #include "hphp/hhbbc/func-util.h"
35 #include "hphp/hhbbc/index.h"
36 #include "hphp/hhbbc/type-structure.h"
37 #include "hphp/hhbbc/type-system.h"
39 #include "hphp/util/text-util.h"
41 namespace HPHP::HHBBC
{
43 //////////////////////////////////////////////////////////////////////
46 * Pretty printer for debuggin'.
49 //////////////////////////////////////////////////////////////////////
53 // Truncate stringization of ArrLikeVals above this size.
54 constexpr size_t maxArrLikeValSize
= 1000;
56 std::string
indent(int level
, const std::string
& s
) {
57 // Whee; make as many std::string copies as possible.
58 auto const space
= std::string(level
, ' ');
59 return space
+ boost::trim_copy(
60 boost::replace_all_copy(s
, "\n", "\n" + space
)) + "\n";
63 void appendExnTreeString(const php::Func
& func
,
67 ret
+= " " + folly::to
<std::string
>(p
);
68 p
= func
.exnNodes
[p
].parent
;
69 } while (p
!= NoExnNodeId
);
72 std::string
escaped_string(SString str
) {
78 auto const sl
= str
->slice();
79 folly::toAppend("\"", folly::cEscape
<std::string
>(sl
), "\"", &ret
);
83 std::string
array_string(SArray arr
) {
85 staticArrayStreamer(const_cast<ArrayData
*>(arr
), str
);
91 //////////////////////////////////////////////////////////////////////
95 std::string
local_string(const Func
& func
, LocalId lid
) {
96 if (lid
== StackDupId
) return "Dup";
97 if (lid
== StackThisId
) return "This";
98 assertx(lid
<= MaxLocalId
);
99 auto const& loc
= func
.locals
[lid
];
101 ? folly::to
<std::string
>("$", loc
.name
->data())
102 : folly::to
<std::string
>("$<unnamed:", lid
, ">");
105 std::string
show(const Func
& func
, const Bytecode
& bc
) {
108 auto append_vsa
= [&] (const CompactVector
<LSString
>& keys
) {
111 for (auto& s
: keys
) {
112 ret
+= delim
+ escaped_string(s
);
118 auto append_switch
= [&] (const SwitchTab
& tab
) {
120 for (auto& target
: tab
) {
121 folly::toAppend(target
, " ", &ret
);
126 auto append_sswitch
= [&] (const SSwitchTab
& tab
) {
128 for (auto& kv
: tab
) {
129 folly::toAppend(escaped_string(kv
.first
), ":", kv
.second
, " ", &ret
);
134 auto append_mkey
= [&](MKey mkey
) {
135 ret
+= memberCodeString(mkey
.mcode
);
137 switch (mkey
.mcode
) {
139 folly::toAppend(':', local_string(func
, mkey
.local
.id
), &ret
);
142 folly::toAppend(':', mkey
.idx
, &ret
);
145 folly::toAppend(':', mkey
.int64
, &ret
);
147 case MET
: case MPT
: case MQT
:
149 ":\"", escapeStringForCPP(mkey
.litstr
->data(), mkey
.litstr
->size()),
158 auto append_lar
= [&](const LocalRange
& range
) {
160 folly::toAppend("L:-", &ret
);
162 folly::toAppend("L:", local_string(func
, range
.first
), "+",
167 auto append_ita
= [&](const IterArgs
& ita
) {
168 auto const print
= [&](int32_t local
) { return local_string(func
, local
); };
169 folly::toAppend(show(ita
, print
), &ret
);
172 #define IMM_BLA(n) ret += " "; append_switch(data.targets);
173 #define IMM_SLA(n) ret += " "; append_sswitch(data.targets);
174 #define IMM_IVA(n) folly::toAppend(" ", data.arg##n, &ret);
175 #define IMM_I64A(n) folly::toAppend(" ", data.arg##n, &ret);
176 #define IMM_LA(n) ret += " " + local_string(func, data.loc##n);
177 #define IMM_NLA(n) ret += " " + local_string(func, data.nloc##n.id);
178 #define IMM_ILA(n) ret += " " + local_string(func, data.loc##n);
179 #define IMM_IA(n) folly::toAppend(" iter:", data.iter##n, &ret);
180 #define IMM_DA(n) folly::toAppend(" ", data.dbl##n, &ret);
181 #define IMM_SA(n) folly::toAppend(" ", escaped_string(data.str##n), &ret);
182 #define IMM_RATA(n) folly::toAppend(" ", show(data.rat), &ret);
183 #define IMM_AA(n) ret += " " + array_string(data.arr##n);
184 #define IMM_BA(n) folly::toAppend(" <blk:", data.target##n, ">", &ret);
185 #define IMM_OA_IMPL(n) folly::toAppend(" ", subopToName(data.subop##n), &ret);
186 #define IMM_OA(type) IMM_OA_IMPL
187 #define IMM_VSA(n) ret += " "; append_vsa(data.keys);
188 #define IMM_KA(n) ret += " "; append_mkey(data.mkey);
189 #define IMM_LAR(n) ret += " "; append_lar(data.locrange);
190 #define IMM_ITA(n) ret += " "; append_ita(data.ita);
191 #define IMM_FCA(n) do { \
192 auto const aeTarget = data.fca.asyncEagerTarget() != NoBlockId \
193 ? folly::sformat("<aeblk:{}>", data.fca.asyncEagerTarget()) \
196 " ", show(data.fca.base(), data.fca.inoutArgs(), \
197 data.fca.readonlyArgs(), aeTarget, \
198 data.fca.context()), &ret); \
202 #define IMM_ONE(x) IMM_##x(1)
203 #define IMM_TWO(x, y) IMM_##x(1); IMM_##y(2);
204 #define IMM_THREE(x, y, z) IMM_TWO(x, y); IMM_##z(3);
205 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z); IMM_##n(4);
206 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n); IMM_##m(5);
207 #define IMM_SIX(x, y, z, n, m, o) IMM_FIVE(x, y, z, n, m); IMM_##o(6);
209 #define O(opcode, imms, inputs, outputs, flags) \
212 UNUSED auto const& data = bc.opcode; \
213 UNUSED auto const curOpcode = Op::opcode; \
214 folly::toAppend(#opcode, &ret); \
219 switch (bc
.op
) { OPCODES
}
253 std::string
show(const Func
& func
, const Block
& block
) {
256 if (block
.exnNodeId
!= NoExnNodeId
) {
258 appendExnTreeString(func
, ret
, block
.exnNodeId
);
262 for (auto& bc
: block
.hhbcs
) ret
+= show(func
, bc
) + "\n";
264 if (block
.fallthrough
!= NoBlockId
) {
273 if (block
.throwExit
!= NoBlockId
) {
274 ret
+= folly::sformat("(throw: blk:{})\n", block
.throwExit
);
280 std::string
dot_instructions(const Func
& func
, const Block
& b
) {
281 using namespace folly::gen
;
283 | map([&] (const Bytecode
& bc
) {
284 return "\"" + folly::cEscape
<std::string
>(show(func
, bc
)) + "\\n\"";
286 | unsplit
<std::string
>("+\n")
290 // Output DOT-format graph. Paste into dot -Txlib or similar.
291 std::string
dot_cfg(const php::WideFunc
& func
) {
293 for (auto const bid
: rpoSortAddDVs(func
)) {
294 auto const b
= func
.blocks()[bid
].get();
295 ret
+= folly::format(
296 "B{} [ label = \"blk:{}\\n\"+{} ]\n",
297 bid
, bid
, dot_instructions(*func
, *b
)).str();
298 bool outputed
= false;
299 forEachNormalSuccessor(*b
, [&] (BlockId target
) {
300 ret
+= folly::format("B{} -> B{};", bid
, target
).str();
303 if (outputed
) ret
+= "\n";
304 if (!is_single_nop(*b
) && b
->throwExit
!= NoBlockId
) {
305 ret
+= folly::sformat("B{} -> B{} [color=red];\n", bid
, b
->throwExit
);
311 std::string
show(const Func
& f
) {
313 auto const func
= php::WideFunc::cns(&f
);
315 #define X(what) if (func->what) folly::toAppend(#what "\n", &ret)
322 if (getenv("HHBBC_DUMP_DOT")) {
324 "digraph {} {{\n node [shape=box];\n{}}}\n",
325 func
->name
, indent(2, dot_cfg(func
)));
328 for (auto const bid
: func
.blockRange()) {
329 auto const blk
= func
.blocks()[bid
].get();
330 if (blk
->dead
) continue;
331 folly::format(&ret
, "block #{}\n{}", bid
, indent(2, show(*func
, *blk
)));
334 visitExnLeaves(*func
, [&] (const php::ExnNode
& node
) {
335 folly::format(&ret
, "exn node #{} ", node
.idx
);
336 if (node
.parent
!= NoExnNodeId
) {
337 folly::format(&ret
, "(^{}) ", node
.parent
);
339 ret
+= folly::to
<std::string
>("catch->", node
.region
.catchEntry
) + '\n';
345 std::string
show(const Class
& cls
) {
352 if (cls
.parentName
) {
353 folly::toAppend(" extends ", cls
.parentName
->data(), &ret
);
356 for (auto& i
: cls
.interfaceNames
) {
357 folly::toAppend(" implements ", i
->data(), "\n", &ret
);
359 for (auto& m
: cls
.methods
) {
363 m
->name
->data(), ":\n",
371 std::string
show(const Unit
& unit
,
372 const std::vector
<const php::Class
*>& classes
,
373 const std::vector
<const php::Func
*>& funcs
) {
376 "Unit ", unit
.filename
->data(), "\n",
380 for (auto const c
: classes
) {
387 for (auto const f
: funcs
) {
389 " function ", f
->name
->data(), ":\n",
395 folly::toAppend("\n", &ret
);
399 std::string
show(const Unit
& unit
, const Index
& index
) {
400 std::vector
<const php::Class
*> classes
;
401 std::vector
<const php::Func
*> funcs
;
402 index
.for_each_unit_class(
404 [&] (const php::Class
& c
) { classes
.emplace_back(&c
); }
406 index
.for_each_unit_func(
408 [&] (const php::Func
& f
) { funcs
.emplace_back(&f
); }
412 begin(classes
), end(classes
),
413 [] (const php::Class
* a
, const php::Class
* b
) {
414 return string_data_lt_type
{}(a
->name
, b
->name
);
418 begin(funcs
), end(funcs
),
419 [] (const php::Func
* a
, const php::Func
* b
) {
420 return string_data_lt
{}(a
->name
, b
->name
);
424 return show(unit
, classes
, funcs
);
427 std::string
show(const Unit
& unit
, const Program
& p
) {
428 std::vector
<const php::Class
*> classes
;
429 std::vector
<const php::Func
*> funcs
;
430 for (auto const& c
: p
.classes
) {
431 if (c
->unit
!= unit
.filename
) continue;
432 classes
.emplace_back(c
.get());
434 for (auto const& f
: p
.funcs
) {
435 if (f
->unit
!= unit
.filename
) continue;
436 funcs
.emplace_back(f
.get());
438 return show(unit
, classes
, funcs
);
441 std::string
show(const Program
& p
, const Index
& index
) {
442 using namespace folly::gen
;
444 | map([&] (const std::unique_ptr
<php::Unit
>& u
) { return show(*u
, index
); })
445 | unsplit
<std::string
>("--------------\n")
449 std::string
show(SrcLoc loc
) {
450 return folly::sformat("{}:{}-{}:{}",
451 loc
.start
.line
, loc
.start
.col
,
452 loc
.past
.line
, loc
.past
.col
);
457 //////////////////////////////////////////////////////////////////////
459 std::string
show(const Type
& t
) {
461 * Type pretty printing
463 * When you allow arbitrary trep combinations with specializations,
464 * pretty printing the Type in its most concise form can be tricky.
466 * treps (in general) are printed by attempting a greedy match among
467 * the predefined treps (the ones with names). For a trep b, until
468 * there are no bits left in b, find the largest (most bits)
469 * predefined trep which is fully contained within b. Add that
470 * predefined trep's name to the output list and remove its bits
471 * from b. This should produce the shortest list of names (joined
472 * with a |) which represents the trep.
474 * If a specialization is present, first all of the bits which
475 * support that specialization are gathered together (using the
476 * above scheme) and printed first. If there is more than one name,
477 * they are surrounded with {}. Then the specialization is attached
478 * to the end. Any remaining trep bits are then processed (again
479 * using the above scheme) and combined with |s as usual. This
480 * scheme makes it clear which bits the specialization applies to,
481 * and which ones it does not.
483 * TInitNull is treated a bit different. Instead of literally
484 * TInitNull, it is rendered as a ? prefix. Even a predefined type
485 * like TOptInt gets this treatment (it becomes ?Int). If the
486 * remaining bits have multiple names, they are again grouped inside
487 * a {} and the ? is applied to the front of the grouping. This
488 * makes it clear it binds to the group (like ?{Int|Str}), whereas
489 * something like ?Int|Str is more ambiguous.
491 * Putting it all together, for example, the union of
492 * TDict(Int:Int), TKeyset(Int,Int), TInitNull, TBool, and TObj
493 * would be rendered as:
495 * ?{{Dict|Keyset}(Int:Int)|Bool|Obj}
498 // NB: We want this function be usuable even on invalid/malformed
499 // types (for example if we want to print the type from within
500 // checkInvariants()). Therefore we don't make any assumptions about
501 // the Type being sane and don't assert anything. We also just deal
502 // with bits and don't copy or manipulate the actual Type.
504 // Sorted vector of all the predefined types, from highest bitcount
505 // to shortest. This lets us implement a greedy covering of bits to
507 static auto const sorted
= [] {
508 std::vector
<std::pair
<trep
, std::string
>> types
{
509 #define X(y, ...) { B##y, #y },
510 HHBBC_TYPE_PREDEFINED(X
)
514 types
.begin(), types
.end(),
515 [](auto const& a
, auto const& b
) {
516 auto const pop1
= folly::popcount((uint64_t)a
.first
);
517 auto const pop2
= folly::popcount((uint64_t)b
.first
);
518 if (pop1
!= pop2
) return pop1
> pop2
;
519 // Special case here: The static/counted axis of array bits
520 // has the same size as the empty/non-empty axis of array
521 // bits. We want to give preferencial treatment to the
522 // empty/non-empty bits to break this symmetric, and also
523 // because it produces more natural looking unions. This is
524 // purely an aesthetic choice.
525 auto const sizish
= [] (trep bits
) {
526 return couldBe(bits
, BArrLikeE
) != couldBe(bits
, BArrLikeN
);
528 auto const s1
= sizish(a
.first
);
529 auto const s2
= sizish(b
.first
);
530 if (s1
!= s2
) return s1
> s2
;
531 return a
.second
< b
.second
;
537 // Given a trep, produce a string of the predefined types (joined
538 // with |) which represent this type. The number of such names is
540 auto const gather
= [&] (trep bits
) -> std::pair
<std::string
, size_t> {
541 // Below loop only works if there's a bit. If there's not, we know
542 // we're Bottom anyways.
543 if (!bits
) return std::make_pair("Bottom", 1);
547 for (auto const& ty
: sorted
) {
548 // Bottom ends up in the list. Just skip it.
549 if (!ty
.first
) continue;
550 // Check if this predefined type is covered by our bits.
551 if ((ty
.first
& bits
) != ty
.first
) continue;
552 // It is, remove it from the bits and add its name to the list.
555 if (!ret
.empty()) ret
+= "|";
556 // Special handling of Opt* types, turn them into ?.
557 if (!ty
.second
.compare(0, 3, "Opt", 3)) {
558 auto temp
= ty
.second
.substr(2);
569 // We'll only get here if we exhausted all the predefined types
570 // and didn't consume all the bits. This can only happen with
571 // invalid types. However, since we want to be robust, just let
572 // it be known something's wrong and continue.
573 ret
+= ret
.empty() ? "???" : "|???";
576 return std::make_pair(ret
, count
);
579 auto const showElem
= [&] (const Type
& key
, const Type
& val
) -> std::string
{
580 return show(key
) + ":" + show(val
);
583 auto const showMapElem
= [&] (TypedValue k
, const MapElem
& m
) {
584 auto const key
= [&] {
585 if (isIntType(k
.m_type
)) return ival(k
.m_data
.num
);
586 assertx(k
.m_type
== KindOfPersistentString
);
587 switch (m
.keyStaticness
) {
588 case TriBool::Yes
: return sval(k
.m_data
.pstr
);
589 case TriBool::Maybe
: return sval_nonstatic(k
.m_data
.pstr
);
590 case TriBool::No
: return sval_counted(k
.m_data
.pstr
);
592 always_assert(false);
594 return showElem(key
, m
.val
);
597 // Given a trep, first gather together the support bits for any
598 // specialization. Add the specialization string, then gather the
599 // remaining (non-support) bits. If there's no specialization, just
600 // delegate to gather().
601 auto const gatherForSpec
= [&] (trep bits
) {
602 // Gather the supoprt and the non-support bits, then combine them
603 // into a string (with the spec in the middle).
604 auto const impl
= [&] (trep mask
, const std::string
& spec
) {
605 auto const [specPart
, specMatches
] = gather(bits
& mask
);
606 auto const [restPart
, restMatches
] = [&] {
607 return (bits
& ~mask
)
608 ? gather(bits
& ~mask
)
609 : std::make_pair(std::string
{}, size_t{0});
612 auto const ret
= folly::sformat(
614 specMatches
> 1 ? "{" : "",
616 specMatches
> 1 ? "}" : "",
618 restMatches
> 0 ? "|" : "",
621 return std::make_pair(ret
, specMatches
+ restMatches
);
624 auto const showDCls
= [&] (const DCls
& dcls
, bool isObj
) {
625 auto const lt
= [&] {
626 assertx(!dcls
.isExact());
627 return !isObj
&& !dcls
.containsNonRegular() ? "<-" : "<=";
630 auto const eq
= [&] (res::Class cls
) {
631 return isObj
|| dcls
.containsNonRegular() || cls
.hasCompleteChildren()
636 if (dcls
.isExact()) {
637 folly::toAppend(eq(dcls
.cls()), show(dcls
.cls()), &ret
);
638 } else if (dcls
.isSub()) {
639 folly::toAppend(lt(), show(dcls
.cls()), &ret
);
640 } else if (dcls
.isIsect()) {
645 using namespace folly::gen
;
646 return from(dcls
.isect())
647 | map([] (res::Class c
) { return show(c
); })
648 | unsplit
<std::string
>("&");
654 auto const [e
, i
] = dcls
.isectAndExact();
663 using namespace folly::gen
;
665 | map([] (res::Class c
) { return show(c
); })
666 | unsplit
<std::string
>("&");
672 if (dcls
.isCtx()) folly::toAppend(" this", &ret
);
676 switch (t
.m_dataTag
) {
678 return impl(BObj
, showDCls(t
.m_data
.dobj
, true));
679 case DataTag::WaitHandle
:
682 folly::sformat("=WaitH<{}>", show(t
.m_data
.dwh
->inner
))
685 return impl(BCls
, showDCls(t
.m_data
.dcls
, false));
686 case DataTag::ArrLikePacked
:
692 using namespace folly::gen
;
693 return from(t
.m_data
.packed
->elems
)
694 | map([&] (const Type
& t
) { return show(t
); })
695 | unsplit
<std::string
>(",");
699 case DataTag::ArrLikePackedN
:
702 folly::sformat("([{}])", show(t
.m_data
.packedn
->type
))
704 case DataTag::ArrLikeMap
:
710 using namespace folly::gen
;
711 return from(t
.m_data
.map
->map
)
712 | map([&] (const std::pair
<TypedValue
,MapElem
>& kv
) {
713 return showMapElem(kv
.first
, kv
.second
);
715 | unsplit
<std::string
>(",");
718 if (!t
.m_data
.map
->hasOptElements()) return std::string
{};
719 return folly::sformat(
721 showElem(t
.m_data
.map
->optKey
, t
.m_data
.map
->optVal
)
726 case DataTag::ArrLikeMapN
:
731 showElem(t
.m_data
.mapn
->key
, t
.m_data
.mapn
->val
)
734 case DataTag::ArrLikeVal
:
740 auto s
= array_string(t
.m_data
.aval
);
741 if (s
.size() > maxArrLikeValSize
) {
742 s
.resize(maxArrLikeValSize
);
743 s
+= "...(truncated)";
750 return impl(BStr
, folly::sformat("={}", escaped_string(t
.m_data
.sval
)));
751 case DataTag::LazyCls
:
752 return impl(BLazyCls
,
753 folly::sformat("={}",
754 escaped_string(t
.m_data
.lazyclsval
)));
755 case DataTag::EnumClassLabel
:
756 return impl(BEnumClassLabel
,
757 folly::sformat("={}", escaped_string(t
.m_data
.eclval
)));
758 case DataTag::Int
: return impl(BInt
, folly::sformat("={}", t
.m_data
.ival
));
759 case DataTag::Dbl
: return impl(BDbl
, folly::sformat("={}", t
.m_data
.dval
));
760 case DataTag::None
: return gather(bits
);
765 // Produce the string, then perform the TInitNull special processing
766 // as described above.
768 auto gathered
= gatherForSpec(t
.bits());
769 // If there trep has TInitNull in it, but the gathering only
770 // matched one thing, it was a Opt* type, and it was already
771 // turned into ?, so we're done. If not, redo the gathering with
772 // the TInitNull removed. Add it to the front of the resultant
774 if (couldBe(t
.bits(), BInitNull
) && gathered
.second
> 1) {
775 gathered
= gatherForSpec(t
.bits() & ~BInitNull
);
776 return folly::sformat(
778 gathered
.second
> 1 ? "{" : "",
780 gathered
.second
> 1 ? "}" : ""
783 return gathered
.first
;
789 //////////////////////////////////////////////////////////////////////
791 std::string
show(Context ctx
) {
794 if (!ctx
.unit
) return "-";
795 return ctx
.unit
->toCppString();
797 return ctx
.cls
->name
->toCppString();
799 auto ret
= std::string
{};
801 ret
= ctx
.cls
->name
->toCppString();
802 if (ctx
.cls
!= ctx
.func
->cls
) {
803 folly::format(&ret
, "({})", ctx
.func
->cls
->name
);
807 ret
+= ctx
.func
->name
->toCppString();
811 //////////////////////////////////////////////////////////////////////
813 std::string
show(const ConstIndex
& idx
) {
814 return folly::sformat("{}:{}", idx
.cls
, idx
.idx
);
817 std::string
show(const MethRef
& m
) {
818 return folly::sformat("{}:{}", m
.cls
, m
.idx
);
821 //////////////////////////////////////////////////////////////////////
823 std::string
show(FuncClsUnit fc
) {
824 if (auto const f
= fc
.func()) {
825 return folly::sformat("func {}", f
->name
);
826 } else if (auto const c
= fc
.cls()) {
827 return folly::sformat("class {}", c
->name
);
828 } else if (auto const u
= fc
.unit()) {
829 return folly::sformat("unit {}", u
->filename
);
835 //////////////////////////////////////////////////////////////////////
837 std::string
show(const PropLookupResult
& r
) {
838 return folly::sformat(
839 "{{{},ty:{},found:{},const:{},late:{},init:{},internal:{}}}",
845 r
.classInitMightRaise
,
850 std::string
show(const PropMergeResult
& r
) {
851 return folly::sformat(
852 "{{adjusted:{},throws:{}}}",
858 std::string
show(const ClsConstLookupResult
& r
) {
859 return folly::sformat(
860 "{{ty:{},found:{},throw:{}}}",
867 std::string
show(const ClsTypeConstLookupResult
& r
) {
868 return folly::sformat(
869 "{{ty:{},fail:{},sens:{},found:{},abstract:{}}}",
870 show(r
.resolution
.type
),
871 r
.resolution
.mightFail
,
872 r
.resolution
.contextSensitive
,
878 std::string
show(const ConstraintType
& t
) {
880 switch (t
.coerceClassToString
) {
881 case TriBool::Yes
: cts
= "yes"; break;
882 case TriBool::No
: cts
= "no"; break;
883 case TriBool::Maybe
: cts
= "maybe"; break;
885 return folly::sformat("ConstraintType{{lower:{}, upper:{}, coerceClassToString:{}, maybeMixed:{}}}",
889 t
.maybeMixed
? "true" : "false"
893 //////////////////////////////////////////////////////////////////////