Skip to content

Commit 6b6fb7b

Browse files
Added pretty-printing for span expansion chains through span_to_expanded_string.
1 parent 2a0ce4a commit 6b6fb7b

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed

src/libsyntax/codemap.rs

+204
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,96 @@ impl CodeMap {
807807
hi.col.to_usize() + 1)).to_string()
808808
}
809809

810+
// Returns true if two spans have the same callee
811+
// (Assumes the same ExpnFormat implies same callee)
812+
fn match_callees(&self, sp_a: &Span, sp_b: &Span) -> bool {
813+
let fmt_a = self
814+
.with_expn_info(sp_a.expn_id,
815+
|ei| ei.map(|ei| ei.callee.format.clone()));
816+
817+
let fmt_b = self
818+
.with_expn_info(sp_b.expn_id,
819+
|ei| ei.map(|ei| ei.callee.format.clone()));
820+
fmt_a == fmt_b
821+
}
822+
823+
/// Returns a formatted string showing the expansion chain of a span
824+
///
825+
/// Spans are printed in the following format:
826+
///
827+
/// filename:start_line:col: end_line:col
828+
/// snippet
829+
/// Callee:
830+
/// Callee span
831+
/// Callsite:
832+
/// Callsite span
833+
///
834+
/// Callees and callsites are printed recursively (if available, otherwise header
835+
/// and span is omitted), expanding into their own callee/callsite spans.
836+
/// Each layer of recursion has an increased indent, and snippets are truncated
837+
/// to at most 50 characters. Finally, recursive calls to the same macro are squashed,
838+
/// with '...' used to represent any number of recursive calls.
839+
pub fn span_to_expanded_string(&self, sp: Span) -> String {
840+
self.span_to_expanded_string_internal(sp, "")
841+
}
842+
843+
fn span_to_expanded_string_internal(&self, sp:Span, indent: &str) -> String {
844+
let mut indent = indent.to_owned();
845+
let mut output = "".to_owned();
846+
let span_str = self.span_to_string(sp);
847+
let mut span_snip = self.span_to_snippet(sp)
848+
.unwrap_or("Snippet unavailable".to_owned());
849+
if span_snip.len() > 50 {
850+
span_snip.truncate(50);
851+
span_snip.push_str("...");
852+
}
853+
output.push_str(&format!("{}{}\n{}`{}`\n", indent, span_str, indent, span_snip));
854+
855+
if sp.expn_id == NO_EXPANSION || sp.expn_id == COMMAND_LINE_EXPN {
856+
return output;
857+
}
858+
859+
let mut callee = self.with_expn_info(sp.expn_id,
860+
|ei| ei.and_then(|ei| ei.callee.span.clone()));
861+
let mut callsite = self.with_expn_info(sp.expn_id,
862+
|ei| ei.map(|ei| ei.call_site.clone()));
863+
864+
indent.push_str(" ");
865+
let mut is_recursive = false;
866+
867+
while callee.is_some() && self.match_callees(&sp, &callee.unwrap()) {
868+
callee = self.with_expn_info(callee.unwrap().expn_id,
869+
|ei| ei.and_then(|ei| ei.callee.span.clone()));
870+
is_recursive = true;
871+
}
872+
if let Some(span) = callee {
873+
output.push_str(&indent);
874+
output.push_str("Callee:\n");
875+
if is_recursive {
876+
output.push_str(&indent);
877+
output.push_str("...\n");
878+
}
879+
output.push_str(&(self.span_to_expanded_string_internal(span, &indent)));
880+
}
881+
882+
is_recursive = false;
883+
while callsite.is_some() && self.match_callees(&sp, &callsite.unwrap()) {
884+
callsite = self.with_expn_info(callsite.unwrap().expn_id,
885+
|ei| ei.map(|ei| ei.call_site.clone()));
886+
is_recursive = true;
887+
}
888+
if let Some(span) = callsite {
889+
output.push_str(&indent);
890+
output.push_str("Callsite:\n");
891+
if is_recursive {
892+
output.push_str(&indent);
893+
output.push_str("...\n");
894+
}
895+
output.push_str(&(self.span_to_expanded_string_internal(span, &indent)));
896+
}
897+
output
898+
}
899+
810900
pub fn span_to_filename(&self, sp: Span) -> FileName {
811901
self.lookup_char_pos(sp.lo).file.name.to_string()
812902
}
@@ -1274,4 +1364,118 @@ mod tests {
12741364

12751365
assert_eq!(sstr, "blork.rs:2:1: 2:12");
12761366
}
1367+
1368+
#[test]
1369+
fn t10() {
1370+
// Test span_to_expanded_string works in base case (no expansion)
1371+
let cm = init_code_map();
1372+
let span = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION };
1373+
let sstr = cm.span_to_expanded_string(span);
1374+
assert_eq!(sstr, "blork.rs:1:1: 1:12\n`first line.`\n");
1375+
1376+
let span = Span { lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION };
1377+
let sstr = cm.span_to_expanded_string(span);
1378+
assert_eq!(sstr, "blork.rs:2:1: 2:12\n`second line`\n");
1379+
}
1380+
1381+
#[test]
1382+
fn t11() {
1383+
// Test span_to_expanded_string works with expansion
1384+
use ast::Name;
1385+
let cm = init_code_map();
1386+
let root = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION };
1387+
let format = ExpnFormat::MacroBang(Name(0u32));
1388+
let callee = NameAndSpan { format: format,
1389+
allow_internal_unstable: false,
1390+
span: None };
1391+
1392+
let info = ExpnInfo { call_site: root, callee: callee };
1393+
let id = cm.record_expansion(info);
1394+
let sp = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id };
1395+
1396+
let sstr = cm.span_to_expanded_string(sp);
1397+
assert_eq!(sstr,
1398+
"blork.rs:2:1: 2:12\n`second line`\n Callsite:\n \
1399+
blork.rs:1:1: 1:12\n `first line.`\n");
1400+
}
1401+
1402+
fn init_expansion_chain(cm: &CodeMap) -> Span {
1403+
// Creates an expansion chain containing two recursive calls
1404+
// root -> expA -> expA -> expB -> expB -> end
1405+
use ast::Name;
1406+
1407+
let root = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION };
1408+
1409+
let format_root = ExpnFormat::MacroBang(Name(0u32));
1410+
let callee_root = NameAndSpan { format: format_root,
1411+
allow_internal_unstable: false,
1412+
span: Some(root) };
1413+
1414+
let info_a1 = ExpnInfo { call_site: root, callee: callee_root };
1415+
let id_a1 = cm.record_expansion(info_a1);
1416+
let span_a1 = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a1 };
1417+
1418+
let format_a = ExpnFormat::MacroBang(Name(1u32));
1419+
let callee_a = NameAndSpan { format: format_a,
1420+
allow_internal_unstable: false,
1421+
span: Some(span_a1) };
1422+
1423+
let info_a2 = ExpnInfo { call_site: span_a1, callee: callee_a.clone() };
1424+
let id_a2 = cm.record_expansion(info_a2);
1425+
let span_a2 = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a2 };
1426+
1427+
let info_b1 = ExpnInfo { call_site: span_a2, callee: callee_a };
1428+
let id_b1 = cm.record_expansion(info_b1);
1429+
let span_b1 = Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b1 };
1430+
1431+
let format_b = ExpnFormat::MacroBang(Name(2u32));
1432+
let callee_b = NameAndSpan { format: format_b,
1433+
allow_internal_unstable: false,
1434+
span: None };
1435+
1436+
let info_b2 = ExpnInfo { call_site: span_b1, callee: callee_b.clone() };
1437+
let id_b2 = cm.record_expansion(info_b2);
1438+
let span_b2 = Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b2 };
1439+
1440+
let info_end = ExpnInfo { call_site: span_b2, callee: callee_b };
1441+
let id_end = cm.record_expansion(info_end);
1442+
Span { lo: BytePos(37), hi: BytePos(48), expn_id: id_end }
1443+
}
1444+
1445+
#[test]
1446+
fn t12() {
1447+
// Test span_to_expanded_string collapses recursive macros and handles
1448+
// recursive callsite and callee expansions
1449+
let cm = init_code_map();
1450+
let end = init_expansion_chain(&cm);
1451+
let sstr = cm.span_to_expanded_string(end);
1452+
let res_str =
1453+
r"blork2.rs:2:1: 2:12
1454+
`second line`
1455+
Callsite:
1456+
...
1457+
blork2.rs:1:1: 1:12
1458+
`first line.`
1459+
Callee:
1460+
blork.rs:2:1: 2:12
1461+
`second line`
1462+
Callee:
1463+
blork.rs:1:1: 1:12
1464+
`first line.`
1465+
Callsite:
1466+
blork.rs:1:1: 1:12
1467+
`first line.`
1468+
Callsite:
1469+
...
1470+
blork.rs:2:1: 2:12
1471+
`second line`
1472+
Callee:
1473+
blork.rs:1:1: 1:12
1474+
`first line.`
1475+
Callsite:
1476+
blork.rs:1:1: 1:12
1477+
`first line.`
1478+
";
1479+
assert_eq!(sstr, res_str);
1480+
}
12771481
}

0 commit comments

Comments
 (0)