Skip to content

Commit 4960f2f

Browse files
authored
Auto merge of #34374 - jseyfried:fix_hygiene_bug, r=nrc
Fix macro hygiene regression The regression was caused by #32923, which is currently in beta. The following is an example of regressed code: ```rust fn main() { let x = 0; macro_rules! foo { () => { println!("{}", x); // prints `0` on stable and after this PR, prints `1` on beta and nightly } } let x = 1; foo!(); } ``` For code to regress, the following is necessary (but not sufficient): - There must be a local variable before a macro in a block, and the macro must use the variable. - There must be a second local variable with the same name after the macro. - The macro must be invoked in a statement position after the second local variable. For example, if the `let x = 0;` from the breaking example were commented out, it would (correctly) not compile on beta/nightly. If the semicolon were removed from `foo!();`, it would (correctly) print `0` on beta and nightly. r? @nrc
2 parents 6dcc2c1 + eef8485 commit 4960f2f

File tree

3 files changed

+163
-596
lines changed

3 files changed

+163
-596
lines changed

src/libsyntax/ext/expand.rs

+3-261
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ pub struct IdentRenamer<'a> {
681681

682682
impl<'a> Folder for IdentRenamer<'a> {
683683
fn fold_ident(&mut self, id: Ident) -> Ident {
684-
Ident::new(id.name, mtwt::apply_renames(self.renames, id.ctxt))
684+
mtwt::apply_renames(self.renames, id)
685685
}
686686
fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
687687
fold::noop_fold_mac(mac, self)
@@ -705,8 +705,7 @@ impl<'a> Folder for PatIdentRenamer<'a> {
705705

706706
pat.map(|ast::Pat {id, node, span}| match node {
707707
PatKind::Ident(binding_mode, Spanned{span: sp, node: ident}, sub) => {
708-
let new_ident = Ident::new(ident.name,
709-
mtwt::apply_renames(self.renames, ident.ctxt));
708+
let new_ident = mtwt::apply_renames(self.renames, ident);
710709
let new_node =
711710
PatKind::Ident(binding_mode,
712711
Spanned{span: sp, node: new_ident},
@@ -1266,7 +1265,7 @@ mod tests {
12661265
use ext::mtwt;
12671266
use fold::Folder;
12681267
use parse;
1269-
use parse::token::{self, keywords};
1268+
use parse::token;
12701269
use util::parser_testing::{string_to_parser};
12711270
use util::parser_testing::{string_to_pat, string_to_crate, strs_to_idents};
12721271
use visit;
@@ -1396,267 +1395,10 @@ mod tests {
13961395
);
13971396
}
13981397

1399-
// renaming tests expand a crate and then check that the bindings match
1400-
// the right varrefs. The specification of the test case includes the
1401-
// text of the crate, and also an array of arrays. Each element in the
1402-
// outer array corresponds to a binding in the traversal of the AST
1403-
// induced by visit. Each of these arrays contains a list of indexes,
1404-
// interpreted as the varrefs in the varref traversal that this binding
1405-
// should match. So, for instance, in a program with two bindings and
1406-
// three varrefs, the array [[1, 2], [0]] would indicate that the first
1407-
// binding should match the second two varrefs, and the second binding
1408-
// should match the first varref.
1409-
//
1410-
// Put differently; this is a sparse representation of a boolean matrix
1411-
// indicating which bindings capture which identifiers.
1412-
//
1413-
// Note also that this matrix is dependent on the implicit ordering of
1414-
// the bindings and the varrefs discovered by the name-finder and the path-finder.
1415-
//
1416-
// The comparisons are done post-mtwt-resolve, so we're comparing renamed
1417-
// names; differences in marks don't matter any more.
1418-
//
1419-
// oog... I also want tests that check "bound-identifier-=?". That is,
1420-
// not just "do these have the same name", but "do they have the same
1421-
// name *and* the same marks"? Understanding this is really pretty painful.
1422-
// in principle, you might want to control this boolean on a per-varref basis,
1423-
// but that would make things even harder to understand, and might not be
1424-
// necessary for thorough testing.
1425-
type RenamingTest = (&'static str, Vec<Vec<usize>>, bool);
1426-
1427-
#[test]
1428-
fn automatic_renaming () {
1429-
let tests: Vec<RenamingTest> =
1430-
vec!(// b & c should get new names throughout, in the expr too:
1431-
("fn a() -> i32 { let b = 13; let c = b; b+c }",
1432-
vec!(vec!(0,1),vec!(2)), false),
1433-
// both x's should be renamed (how is this causing a bug?)
1434-
("fn main () {let x: i32 = 13;x;}",
1435-
vec!(vec!(0)), false),
1436-
// the use of b after the + should be renamed, the other one not:
1437-
("macro_rules! f (($x:ident) => (b + $x)); fn a() -> i32 { let b = 13; f!(b)}",
1438-
vec!(vec!(1)), false),
1439-
// the b before the plus should not be renamed (requires marks)
1440-
("macro_rules! f (($x:ident) => ({let b=9; ($x + b)})); fn a() -> i32 { f!(b)}",
1441-
vec!(vec!(1)), false),
1442-
// the marks going in and out of letty should cancel, allowing that $x to
1443-
// capture the one following the semicolon.
1444-
// this was an awesome test case, and caught a *lot* of bugs.
1445-
("macro_rules! letty(($x:ident) => (let $x = 15;));
1446-
macro_rules! user(($x:ident) => ({letty!($x); $x}));
1447-
fn main() -> i32 {user!(z)}",
1448-
vec!(vec!(0)), false)
1449-
);
1450-
for (idx,s) in tests.iter().enumerate() {
1451-
run_renaming_test(s,idx);
1452-
}
1453-
}
1454-
1455-
// no longer a fixme #8062: this test exposes a *potential* bug; our system does
1456-
// not behave exactly like MTWT, but a conversation with Matthew Flatt
1457-
// suggests that this can only occur in the presence of local-expand, which
1458-
// we have no plans to support. ... unless it's needed for item hygiene....
1459-
#[ignore]
1460-
#[test]
1461-
fn issue_8062(){
1462-
run_renaming_test(
1463-
&("fn main() {let hrcoo = 19; macro_rules! getx(()=>(hrcoo)); getx!();}",
1464-
vec!(vec!(0)), true), 0)
1465-
}
1466-
1467-
// FIXME #6994:
1468-
// the z flows into and out of two macros (g & f) along one path, and one
1469-
// (just g) along the other, so the result of the whole thing should
1470-
// be "let z_123 = 3; z_123"
1471-
#[ignore]
1472-
#[test]
1473-
fn issue_6994(){
1474-
run_renaming_test(
1475-
&("macro_rules! g (($x:ident) =>
1476-
({macro_rules! f(($y:ident)=>({let $y=3;$x}));f!($x)}));
1477-
fn a(){g!(z)}",
1478-
vec!(vec!(0)),false),
1479-
0)
1480-
}
1481-
1482-
// match variable hygiene. Should expand into
1483-
// fn z() {match 8 {x_1 => {match 9 {x_2 | x_2 if x_2 == x_1 => x_2 + x_1}}}}
1484-
#[test]
1485-
fn issue_9384(){
1486-
run_renaming_test(
1487-
&("macro_rules! bad_macro (($ex:expr) => ({match 9 {x | x if x == $ex => x + $ex}}));
1488-
fn z() {match 8 {x => bad_macro!(x)}}",
1489-
// NB: the third "binding" is the repeat of the second one.
1490-
vec!(vec!(1,3),vec!(0,2),vec!(0,2)),
1491-
true),
1492-
0)
1493-
}
1494-
1495-
// interpolated nodes weren't getting labeled.
1496-
// should expand into
1497-
// fn main(){let g1_1 = 13; g1_1}}
1498-
#[test]
1499-
fn pat_expand_issue_15221(){
1500-
run_renaming_test(
1501-
&("macro_rules! inner ( ($e:pat ) => ($e));
1502-
macro_rules! outer ( ($e:pat ) => (inner!($e)));
1503-
fn main() { let outer!(g) = 13; g;}",
1504-
vec!(vec!(0)),
1505-
true),
1506-
0)
1507-
}
1508-
15091398
// create a really evil test case where a $x appears inside a binding of $x
15101399
// but *shouldn't* bind because it was inserted by a different macro....
15111400
// can't write this test case until we have macro-generating macros.
15121401

1513-
// method arg hygiene
1514-
// method expands to fn get_x(&self_0, x_1: i32) {self_0 + self_2 + x_3 + x_1}
1515-
#[test]
1516-
fn method_arg_hygiene(){
1517-
run_renaming_test(
1518-
&("macro_rules! inject_x (()=>(x));
1519-
macro_rules! inject_self (()=>(self));
1520-
struct A;
1521-
impl A{fn get_x(&self, x: i32) {self + inject_self!() + inject_x!() + x;} }",
1522-
vec!(vec!(0),vec!(3)),
1523-
true),
1524-
0)
1525-
}
1526-
1527-
// ooh, got another bite?
1528-
// expands to struct A; impl A {fn thingy(&self_1) {self_1;}}
1529-
#[test]
1530-
fn method_arg_hygiene_2(){
1531-
run_renaming_test(
1532-
&("struct A;
1533-
macro_rules! add_method (($T:ty) =>
1534-
(impl $T { fn thingy(&self) {self;} }));
1535-
add_method!(A);",
1536-
vec!(vec!(0)),
1537-
true),
1538-
0)
1539-
}
1540-
1541-
// item fn hygiene
1542-
// expands to fn q(x_1: i32){fn g(x_2: i32){x_2 + x_1};}
1543-
#[test]
1544-
fn issue_9383(){
1545-
run_renaming_test(
1546-
&("macro_rules! bad_macro (($ex:expr) => (fn g(x: i32){ x + $ex }));
1547-
fn q(x: i32) { bad_macro!(x); }",
1548-
vec!(vec!(1),vec!(0)),true),
1549-
0)
1550-
}
1551-
1552-
// closure arg hygiene (ExprKind::Closure)
1553-
// expands to fn f(){(|x_1 : i32| {(x_2 + x_1)})(3);}
1554-
#[test]
1555-
fn closure_arg_hygiene(){
1556-
run_renaming_test(
1557-
&("macro_rules! inject_x (()=>(x));
1558-
fn f(){(|x : i32| {(inject_x!() + x)})(3);}",
1559-
vec!(vec!(1)),
1560-
true),
1561-
0)
1562-
}
1563-
1564-
// macro_rules in method position. Sadly, unimplemented.
1565-
#[test]
1566-
fn macro_in_method_posn(){
1567-
expand_crate_str(
1568-
"macro_rules! my_method (() => (fn thirteen(&self) -> i32 {13}));
1569-
struct A;
1570-
impl A{ my_method!(); }
1571-
fn f(){A.thirteen;}".to_string());
1572-
}
1573-
1574-
// another nested macro
1575-
// expands to impl Entries {fn size_hint(&self_1) {self_1;}
1576-
#[test]
1577-
fn item_macro_workaround(){
1578-
run_renaming_test(
1579-
&("macro_rules! item { ($i:item) => {$i}}
1580-
struct Entries;
1581-
macro_rules! iterator_impl {
1582-
() => { item!( impl Entries { fn size_hint(&self) { self;}});}}
1583-
iterator_impl! { }",
1584-
vec!(vec!(0)), true),
1585-
0)
1586-
}
1587-
1588-
// run one of the renaming tests
1589-
fn run_renaming_test(t: &RenamingTest, test_idx: usize) {
1590-
let invalid_name = keywords::Invalid.name();
1591-
let (teststr, bound_connections, bound_ident_check) = match *t {
1592-
(ref str,ref conns, bic) => (str.to_string(), conns.clone(), bic)
1593-
};
1594-
let cr = expand_crate_str(teststr.to_string());
1595-
let bindings = crate_bindings(&cr);
1596-
let varrefs = crate_varrefs(&cr);
1597-
1598-
// must be one check clause for each binding:
1599-
assert_eq!(bindings.len(),bound_connections.len());
1600-
for (binding_idx,shouldmatch) in bound_connections.iter().enumerate() {
1601-
let binding_name = mtwt::resolve(bindings[binding_idx]);
1602-
let binding_marks = mtwt::marksof(bindings[binding_idx].ctxt, invalid_name);
1603-
// shouldmatch can't name varrefs that don't exist:
1604-
assert!((shouldmatch.is_empty()) ||
1605-
(varrefs.len() > *shouldmatch.iter().max().unwrap()));
1606-
for (idx,varref) in varrefs.iter().enumerate() {
1607-
let print_hygiene_debug_info = || {
1608-
// good lord, you can't make a path with 0 segments, can you?
1609-
let final_varref_ident = match varref.segments.last() {
1610-
Some(pathsegment) => pathsegment.identifier,
1611-
None => panic!("varref with 0 path segments?")
1612-
};
1613-
let varref_name = mtwt::resolve(final_varref_ident);
1614-
let varref_idents : Vec<ast::Ident>
1615-
= varref.segments.iter().map(|s| s.identifier)
1616-
.collect();
1617-
println!("varref #{}: {:?}, resolves to {}",idx, varref_idents, varref_name);
1618-
println!("varref's first segment's string: \"{}\"", final_varref_ident);
1619-
println!("binding #{}: {}, resolves to {}",
1620-
binding_idx, bindings[binding_idx], binding_name);
1621-
mtwt::with_sctable(|x| mtwt::display_sctable(x));
1622-
};
1623-
if shouldmatch.contains(&idx) {
1624-
// it should be a path of length 1, and it should
1625-
// be free-identifier=? or bound-identifier=? to the given binding
1626-
assert_eq!(varref.segments.len(),1);
1627-
let varref_name = mtwt::resolve(varref.segments[0].identifier);
1628-
let varref_marks = mtwt::marksof(varref.segments[0]
1629-
.identifier
1630-
.ctxt,
1631-
invalid_name);
1632-
if !(varref_name==binding_name) {
1633-
println!("uh oh, should match but doesn't:");
1634-
print_hygiene_debug_info();
1635-
}
1636-
assert_eq!(varref_name,binding_name);
1637-
if bound_ident_check {
1638-
// we're checking bound-identifier=?, and the marks
1639-
// should be the same, too:
1640-
assert_eq!(varref_marks,binding_marks.clone());
1641-
}
1642-
} else {
1643-
let varref_name = mtwt::resolve(varref.segments[0].identifier);
1644-
let fail = (varref.segments.len() == 1)
1645-
&& (varref_name == binding_name);
1646-
// temp debugging:
1647-
if fail {
1648-
println!("failure on test {}",test_idx);
1649-
println!("text of test case: \"{}\"", teststr);
1650-
println!("");
1651-
println!("uh oh, matches but shouldn't:");
1652-
print_hygiene_debug_info();
1653-
}
1654-
assert!(!fail);
1655-
}
1656-
}
1657-
}
1658-
}
1659-
16601402
#[test]
16611403
fn fmt_in_macro_used_inside_module_macro() {
16621404
let crate_str = "macro_rules! fmt_wrap(($b:expr)=>($b.to_string()));

0 commit comments

Comments
 (0)