11package liquidjava .diagnostics ;
22
33import java .util .ArrayList ;
4- import java .util .LinkedHashMap ;
5- import java .util .LinkedHashSet ;
64import java .util .List ;
7- import java .util .Map ;
8- import java .util .Set ;
95
106import liquidjava .api .CommandLineLauncher ;
7+ import liquidjava .processor .VCImplication ;
118import liquidjava .rj_language .Predicate ;
129import liquidjava .rj_language .ast .BinaryExpression ;
1310import liquidjava .rj_language .ast .Expression ;
1411import liquidjava .rj_language .ast .GroupExpression ;
1512import liquidjava .utils .Utils ;
1613import spoon .reflect .cu .SourcePosition ;
14+ import spoon .reflect .reference .CtTypeReference ;
1715
1816/**
1917 * Centralised debug-mode logging for verification activity. Output is gated on the global {@code --debug} / {@code -d}
2927 */
3028public final class DebugLog {
3129
32- private static final String INFO_TAG = Colors .YELLOW + "[INFO]" + Colors .RESET ;
3330 private static final String SMT_TAG = Colors .BLUE + "[SMT]" + Colors .RESET ;
31+ private static final String SMT_CHECK = Colors .SALMON + "[SMT CHECK]" + Colors .RESET ;
3432
3533 private DebugLog () {
3634 }
@@ -39,57 +37,165 @@ public static boolean enabled() {
3937 return CommandLineLauncher .cmdArgs .debugMode ;
4038 }
4139
42- public static void info (String verb , Object expected , Object found , SourcePosition position ) {
43- if (!enabled ()) {
40+ /**
41+ * One-line header for a verification check: emits the absolute file path + line so terminals (iTerm2, VS Code,
42+ * WezTerm, …) make it ⌘/Ctrl-clickable. Replaces the older two-line {@code info()} prints.
43+ */
44+ public static void smtVerifying (SourcePosition position ) {
45+ if (!enabled () || position == null ) {
4446 return ;
4547 }
48+ String where = position .getFile ().getAbsolutePath () + ":" + position .getLine ();
4649 String exp = Utils .getExpressionFromPosition (position );
47- String where = position . getFile (). getName () + ":" + position . getLine ( );
48- System .out .println (INFO_TAG + " " + verb + ": " + found + " <: " + expected );
49- System . out . println ( INFO_TAG + " at " + where + (exp != null ? " on '" + exp + "'" : "" ));
50+ System . out . println ( SMT_CHECK );
51+ System .out .println (SMT_TAG + " Verifying " + Colors . CYAN + where + Colors . RESET
52+ + (exp != null ? " on '" + exp + "'" : "" ));
5053 }
5154
55+ private static final String SEPARATOR = Colors .GREY + " ────────────────────────────────────────" + Colors .RESET ;
56+
57+ /**
58+ * Flat-predicate fallback: prints top-level conjuncts in order with no per-variable grouping. Used by SMT entry
59+ * points that don't carry the structured per-variable {@link VCImplication} chain (e.g. ExpressionSimplifier).
60+ */
5261 public static void smtStart (Predicate premises , Predicate conclusion ) {
5362 if (!enabled ()) {
5463 return ;
5564 }
56- printPremisesByVariable (premises );
57- System .out .println (SMT_TAG + " conclusion: " + conclusion );
65+ List <Expression > conjuncts = new ArrayList <>();
66+ flattenConjunction (premises .getExpression (), conjuncts );
67+ System .out .println (SMT_TAG );
68+ for (Expression c : conjuncts ) {
69+ System .out .println (SMT_TAG + " " + c );
70+ }
71+ System .out .println (SMT_TAG + SEPARATOR );
72+ System .out .println (SMT_TAG + " " + formatConclusion (conclusion ));
5873 }
5974
6075 /**
61- * Splits the premise conjunction into top-level conjuncts and groups them by the variable they constrain. Conjuncts
62- * mentioning a single variable are printed under that variable; conjuncts mentioning zero or several variables are
63- * printed under a {@code <shared>} bucket.
76+ * Structured form: walks the {@link VCImplication} chain produced by {@code joinPredicates}, printing one line per
77+ * refined variable with all of its refinements together.
6478 */
65- private static void printPremisesByVariable (Predicate premises ) {
66- Expression root = premises .getExpression ();
67- List <Expression > conjuncts = new ArrayList <>();
68- flattenConjunction (root , conjuncts );
79+ public static void smtStart (VCImplication chain , Predicate conclusion ) {
80+ smtStart (chain , null , conclusion );
81+ }
6982
70- Map <String , List <String >> grouped = new LinkedHashMap <>();
71- for (Expression c : conjuncts ) {
72- List <String > names = new ArrayList <>();
73- c .getVariableNames (names );
74- Set <String > unique = new LinkedHashSet <>(names );
75- String key ;
76- if (unique .isEmpty ()) {
77- key = "" ;
78- } else if (unique .size () == 1 ) {
79- key = unique .iterator ().next ();
80- } else {
81- key = "<shared>" ;
83+ /**
84+ * Structured form with an extra unattributed premise (e.g. the "found" type appended in
85+ * {@code verifySMTSubtypeStates}).
86+ */
87+ public static void smtStart (VCImplication chain , Predicate extraPremise , Predicate conclusion ) {
88+ if (!enabled ()) {
89+ return ;
90+ }
91+ // Pre-compute label widths for column alignment across all printed rows.
92+ int labelWidth = 0 ;
93+ for (VCImplication node = chain ; node != null ; node = node .getNext ()) {
94+ if (node .getName () == null && node .getType () == null ) {
95+ continue ;
96+ }
97+ labelWidth = Math .max (labelWidth , plainLabel (node ).length ());
98+ }
99+ if (extraPremise != null && !extraPremise .isBooleanTrue ()) {
100+ labelWidth = Math .max (labelWidth , "found" .length ());
101+ }
102+
103+ System .out .println (SMT_TAG + " " );
104+ List <String > printedRefinements = new ArrayList <>();
105+ for (VCImplication node = chain ; node != null ; node = node .getNext ()) {
106+ if (node .getName () == null && node .getType () == null ) {
107+ continue ; // skip the empty trailing tail node
108+ }
109+ String refinement = formatRefinement (node .getRefinement ());
110+ printedRefinements .add (refinement );
111+ System .out .println (SMT_TAG + " " + paintLabel (node , labelWidth ) + " " + refinement );
112+ }
113+ if (extraPremise != null && !extraPremise .isBooleanTrue ()) {
114+ String extra = formatRefinement (extraPremise );
115+ // Skip when the appended "found" type is identical to a premise we just printed
116+ // (common in verifySMTSubtypeStates when `type` IS the variable's current refinement).
117+ if (!printedRefinements .contains (extra )) {
118+ String label = Colors .GREY + padRight ("found" , labelWidth ) + Colors .RESET ;
119+ System .out .println (SMT_TAG + " " + label + " " + extra );
120+ }
121+ }
122+ System .out .println (SMT_TAG + SEPARATOR );
123+ System .out .println (SMT_TAG + " " + formatConclusion (conclusion ));
124+ }
125+
126+ private static String plainLabel (VCImplication node ) {
127+ return node .getName () + " : " + simpleType (node .getType ());
128+ }
129+
130+ private static String paintLabel (VCImplication node , int width ) {
131+ String name = node .getName ();
132+ String type = simpleType (node .getType ());
133+ String padded = padRight (name + " : " + type , width );
134+ // Color only the name; keep alignment by computing padding on the plain string.
135+ String coloredName = Colors .CYAN + name + Colors .RESET ;
136+ String coloredType = Colors .GREY + type + Colors .RESET ;
137+ String tail = padded .substring ((name + " : " + type ).length ()); // padding spaces
138+ return coloredName + Colors .GREY + " : " + Colors .RESET + coloredType + tail ;
139+ }
140+
141+ private static String simpleType (CtTypeReference <?> type ) {
142+ if (type == null ) {
143+ return "?" ;
144+ }
145+ String qual = type .getQualifiedName ();
146+ return qual .contains ("." ) ? Utils .getSimpleName (qual ) : qual ;
147+ }
148+
149+ private static String padRight (String s , int width ) {
150+ if (s .length () >= width ) {
151+ return s ;
152+ }
153+ StringBuilder sb = new StringBuilder (s );
154+ for (int i = s .length (); i < width ; i ++) {
155+ sb .append (' ' );
156+ }
157+ return sb .toString ();
158+ }
159+
160+ /**
161+ * Render a refinement so multi-conjunct predicates are unambiguous on a single line: each top-level conjunct is
162+ * wrapped in parens and joined with ∧.
163+ */
164+ private static String formatRefinement (Predicate p ) {
165+ List <Expression > conjuncts = new ArrayList <>();
166+ flattenConjunction (p .getExpression (), conjuncts );
167+ if (conjuncts .size () <= 1 ) {
168+ return p .toString ();
169+ }
170+ StringBuilder sb = new StringBuilder ();
171+ for (int i = 0 ; i < conjuncts .size (); i ++) {
172+ if (i > 0 ) {
173+ sb .append (Colors .GREY ).append (" ∧ " ).append (Colors .RESET );
82174 }
83- grouped . computeIfAbsent ( key , k -> new ArrayList <>()). add ( c . toString () );
175+ sb . append ( '(' ). append ( conjuncts . get ( i )). append ( ')' );
84176 }
177+ return sb .toString ();
178+ }
85179
86- System .out .println (SMT_TAG + " premises:" );
87- for (Map .Entry <String , List <String >> entry : grouped .entrySet ()) {
88- String prefix = entry .getKey ().isEmpty () ? "" : entry .getKey () + ": " ;
89- for (String c : entry .getValue ()) {
90- System .out .println (SMT_TAG + " " + prefix + c );
180+ /**
181+ * Conclusion needs its own painter: nesting {@link #formatRefinement} (which already emits ANSI {@code RESET}
182+ * around its operators) inside an outer color would clear the outer color after the first inner reset, leaving the
183+ * tail of the line uncoloured. Paint each conjunct individually instead.
184+ */
185+ private static String formatConclusion (Predicate p ) {
186+ List <Expression > conjuncts = new ArrayList <>();
187+ flattenConjunction (p .getExpression (), conjuncts );
188+ if (conjuncts .size () <= 1 ) {
189+ return Colors .BOLD_YELLOW + p + Colors .RESET ;
190+ }
191+ StringBuilder sb = new StringBuilder ();
192+ for (int i = 0 ; i < conjuncts .size (); i ++) {
193+ if (i > 0 ) {
194+ sb .append (Colors .GREY ).append (" ∧ " ).append (Colors .RESET );
91195 }
196+ sb .append (Colors .BOLD_YELLOW ).append ('(' ).append (conjuncts .get (i )).append (')' ).append (Colors .RESET );
92197 }
198+ return sb .toString ();
93199 }
94200
95201 private static void flattenConjunction (Expression e , List <Expression > out ) {
@@ -126,4 +232,31 @@ public static void smtUnknown() {
126232 }
127233 System .out .println (SMT_TAG + " result: " + Colors .YELLOW + "UNKNOWN (treated as OK)" + Colors .RESET );
128234 }
235+
236+ /**
237+ * Print the result of an SMT check whose {@code smtStart} was emitted by the caller (e.g. VCChecker's structured
238+ * print). {@link liquidjava.smt.SMTResult} doesn't preserve UNKNOWN, so this maps OK → UNSAT and ERROR → SAT.
239+ */
240+ public static void smtResult (liquidjava .smt .SMTResult result ) {
241+ if (!enabled ()) {
242+ return ;
243+ }
244+ if (result .isError ()) {
245+ smtSat (result .getCounterexample ());
246+ } else {
247+ smtUnsat ();
248+ }
249+ }
250+
251+ /**
252+ * Print an SMT-side failure (e.g. Z3 sort mismatch) so the trace doesn't end with a dangling header. Caller is
253+ * still responsible for surfacing the user-facing error.
254+ */
255+ public static void smtError (String message ) {
256+ if (!enabled ()) {
257+ return ;
258+ }
259+ System .out .println (SMT_TAG + " result: " + Colors .RED + "ERROR" + Colors .RESET + " — "
260+ + (message == null ? "(no message)" : message ));
261+ }
129262}
0 commit comments