diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs index fa048346e99b5..0576c46d3bd8f 100644 --- a/src/libgraphviz/lib.rs +++ b/src/libgraphviz/lib.rs @@ -421,6 +421,14 @@ pub trait Labeller<'a,N,E> { } impl<'a> LabelText<'a> { + pub fn label>(s: S) -> LabelText<'a> { + LabelStr(s.into_cow()) + } + + pub fn escaped>(s: S) -> LabelText<'a> { + EscStr(s.into_cow()) + } + fn escape_char(c: char, mut f: F) where F: FnMut(char) { match c { // not escaping \\, since Graphviz escString needs to @@ -505,11 +513,29 @@ pub trait GraphWalk<'a, N, E> { fn target(&'a self, edge: &E) -> N; } +#[deriving(Copy, PartialEq, Eq, Show)] +pub enum RenderOption { + NoEdgeLabels, + NoNodeLabels, +} + +/// Returns vec holding all the default render options. +pub fn default_options() -> Vec { vec![] } + /// Renders directed graph `g` into the writer `w` in DOT syntax. -/// (Main entry point for the library.) +/// (Simple wrapper around `render_opts` that passes a default set of options.) pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>( g: &'a G, - w: &mut W) -> io::IoResult<()> + w: &mut W) -> io::IoResult<()> { + render_opts(g, w, &[]) +} + +/// Renders directed graph `g` into the writer `w` in DOT syntax. +/// (Main entry point for the library.) +pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>( + g: &'a G, + w: &mut W, + options: &[RenderOption]) -> io::IoResult<()> { fn writeln(w: &mut W, arg: &[&str]) -> io::IoResult<()> { for &s in arg.iter() { try!(w.write_str(s)); } @@ -524,9 +550,13 @@ pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, for n in g.nodes().iter() { try!(indent(w)); let id = g.node_id(n); - let escaped = g.node_label(n).escape(); - try!(writeln(w, &[id.as_slice(), - "[label=\"", escaped.as_slice(), "\"];"])); + if options.contains(&RenderOption::NoNodeLabels) { + try!(writeln(w, &[id.as_slice(), ";"])); + } else { + let escaped = g.node_label(n).escape(); + try!(writeln(w, &[id.as_slice(), + "[label=\"", escaped.as_slice(), "\"];"])); + } } for e in g.edges().iter() { @@ -536,8 +566,14 @@ pub fn render<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, let target = g.target(e); let source_id = g.node_id(&source); let target_id = g.node_id(&target); - try!(writeln(w, &[source_id.as_slice(), " -> ", target_id.as_slice(), - "[label=\"", escaped_label.as_slice(), "\"];"])); + if options.contains(&RenderOption::NoEdgeLabels) { + try!(writeln(w, &[source_id.as_slice(), + " -> ", target_id.as_slice(), ";"])); + } else { + try!(writeln(w, &[source_id.as_slice(), + " -> ", target_id.as_slice(), + "[label=\"", escaped_label.as_slice(), "\"];"])); + } } writeln(w, &["}"]) diff --git a/src/librustc/middle/infer/mod.rs b/src/librustc/middle/infer/mod.rs index 2b1d8776365ec..3a84712016eae 100644 --- a/src/librustc/middle/infer/mod.rs +++ b/src/librustc/middle/infer/mod.rs @@ -814,8 +814,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { self.region_vars.new_bound(debruijn) } - pub fn resolve_regions_and_report_errors(&self) { - let errors = self.region_vars.resolve_regions(); + pub fn resolve_regions_and_report_errors(&self, subject_node_id: ast::NodeId) { + let errors = self.region_vars.resolve_regions(subject_node_id); self.report_region_errors(&errors); // see error_reporting.rs } diff --git a/src/librustc/middle/infer/region_inference/graphviz.rs b/src/librustc/middle/infer/region_inference/graphviz.rs new file mode 100644 index 0000000000000..720de357a273d --- /dev/null +++ b/src/librustc/middle/infer/region_inference/graphviz.rs @@ -0,0 +1,227 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This module provides linkage between libgraphviz traits and +//! `rustc::middle::typeck::infer::region_inference`, generating a +//! rendering of the graph represented by the list of `Constraint` +//! instances (which make up the edges of the graph), as well as the +//! origin for each constraint (which are attached to the labels on +//! each edge). + +/// For clarity, rename the graphviz crate locally to dot. +use graphviz as dot; + +use middle::ty; +use super::Constraint; +use middle::infer::SubregionOrigin; +use middle::infer::region_inference::RegionVarBindings; +use session::config; +use util::nodemap::{FnvHashMap, FnvHashSet}; +use util::ppaux::Repr; + +use std::collections::hash_map::Vacant; +use std::io::{mod, File}; +use std::os; +use std::sync::atomic; +use syntax::ast; + +fn print_help_message() { + println!("\ +-Z print-region-graph by default prints a region constraint graph for every \n\ +function body, to the path `/tmp/constraints.nodeXXX.dot`, where the XXX is \n\ +replaced with the node id of the function under analysis. \n\ + \n\ +To select one particular function body, set `RUST_REGION_GRAPH_NODE=XXX`, \n\ +where XXX is the node id desired. \n\ + \n\ +To generate output to some path other than the default \n\ +`/tmp/constraints.nodeXXX.dot`, set `RUST_REGION_GRAPH=/path/desired.dot`; \n\ +occurrences of the character `%` in the requested path will be replaced with\n\ +the node id of the function under analysis. \n\ + \n\ +(Since you requested help via RUST_REGION_GRAPH=help, no region constraint \n\ +graphs will be printed. \n\ +"); +} + +pub fn maybe_print_constraints_for<'a, 'tcx>(region_vars: &RegionVarBindings<'a, 'tcx>, + subject_node: ast::NodeId) { + let tcx = region_vars.tcx; + + if !region_vars.tcx.sess.debugging_opt(config::PRINT_REGION_GRAPH) { + return; + } + + let requested_node : Option = + os::getenv("RUST_REGION_GRAPH_NODE").and_then(|s|from_str(s.as_slice())); + + if requested_node.is_some() && requested_node != Some(subject_node) { + return; + } + + let requested_output = os::getenv("RUST_REGION_GRAPH"); + debug!("requested_output: {} requested_node: {}", + requested_output, requested_node); + + let output_path = { + let output_template = match requested_output { + Some(ref s) if s.as_slice() == "help" => { + static PRINTED_YET : atomic::AtomicBool = atomic::INIT_ATOMIC_BOOL; + if !PRINTED_YET.load(atomic::SeqCst) { + print_help_message(); + PRINTED_YET.store(true, atomic::SeqCst); + } + return; + } + + Some(other_path) => other_path, + None => "/tmp/constraints.node%.dot".to_string(), + }; + + if output_template.len() == 0 { + tcx.sess.bug("empty string provided as RUST_REGION_GRAPH"); + } + + if output_template.contains_char('%') { + let mut new_str = String::new(); + for c in output_template.chars() { + if c == '%' { + new_str.push_str(subject_node.to_string().as_slice()); + } else { + new_str.push(c); + } + } + new_str + } else { + output_template + } + }; + + let constraints = &*region_vars.constraints.borrow(); + match dump_region_constraints_to(tcx, constraints, output_path.as_slice()) { + Ok(()) => {} + Err(e) => { + let msg = format!("io error dumping region constraints: {}", e); + region_vars.tcx.sess.err(msg.as_slice()) + } + } +} + +struct ConstraintGraph<'a, 'tcx: 'a> { + tcx: &'a ty::ctxt<'tcx>, + graph_name: String, + map: &'a FnvHashMap>, + node_ids: FnvHashMap, +} + +#[deriving(Clone, Hash, PartialEq, Eq, Show)] +enum Node { + RegionVid(ty::RegionVid), + Region(ty::Region), +} + +type Edge = Constraint; + +impl<'a, 'tcx> ConstraintGraph<'a, 'tcx> { + fn new(tcx: &'a ty::ctxt<'tcx>, + name: String, + map: &'a ConstraintMap<'tcx>) -> ConstraintGraph<'a, 'tcx> { + let mut i = 0; + let mut node_ids = FnvHashMap::new(); + { + let add_node = |node| { + if let Vacant(e) = node_ids.entry(node) { + e.set(i); + i += 1; + } + }; + + for (n1, n2) in map.keys().map(|c|constraint_to_nodes(c)) { + add_node(n1); + add_node(n2); + } + } + + ConstraintGraph { tcx: tcx, + graph_name: name, + map: map, + node_ids: node_ids } + } +} + +impl<'a, 'tcx> dot::Labeller<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> { + fn graph_id(&self) -> dot::Id { + dot::Id::new(self.graph_name.as_slice()).unwrap() + } + fn node_id(&self, n: &Node) -> dot::Id { + dot::Id::new(format!("node_{}", self.node_ids.get(n).unwrap())).unwrap() + } + fn node_label(&self, n: &Node) -> dot::LabelText { + match *n { + Node::RegionVid(n_vid) => + dot::LabelText::label(format!("{}", n_vid)), + Node::Region(n_rgn) => + dot::LabelText::label(format!("{}", n_rgn.repr(self.tcx))), + } + } + fn edge_label(&self, e: &Edge) -> dot::LabelText { + dot::LabelText::label(format!("{}", self.map.get(e).unwrap().repr(self.tcx))) + } +} + +fn constraint_to_nodes(c: &Constraint) -> (Node, Node) { + match *c { + Constraint::ConstrainVarSubVar(rv_1, rv_2) => (Node::RegionVid(rv_1), + Node::RegionVid(rv_2)), + Constraint::ConstrainRegSubVar(r_1, rv_2) => (Node::Region(r_1), + Node::RegionVid(rv_2)), + Constraint::ConstrainVarSubReg(rv_1, r_2) => (Node::RegionVid(rv_1), + Node::Region(r_2)), + } +} + +impl<'a, 'tcx> dot::GraphWalk<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> { + fn nodes(&self) -> dot::Nodes { + let mut set = FnvHashSet::new(); + for constraint in self.map.keys() { + let (n1, n2) = constraint_to_nodes(constraint); + set.insert(n1); + set.insert(n2); + } + debug!("constraint graph has {} nodes", set.len()); + set.into_iter().collect() + } + fn edges(&self) -> dot::Edges { + debug!("constraint graph has {} edges", self.map.len()); + self.map.keys().map(|e|*e).collect() + } + fn source(&self, edge: &Edge) -> Node { + let (n1, _) = constraint_to_nodes(edge); + debug!("edge {} has source {}", edge, n1); + n1 + } + fn target(&self, edge: &Edge) -> Node { + let (_, n2) = constraint_to_nodes(edge); + debug!("edge {} has target {}", edge, n2); + n2 + } +} + +pub type ConstraintMap<'tcx> = FnvHashMap>; + +fn dump_region_constraints_to<'a, 'tcx:'a >(tcx: &'a ty::ctxt<'tcx>, + map: &ConstraintMap<'tcx>, + path: &str) -> io::IoResult<()> { + debug!("dump_region_constraints map (len: {}) path: {}", map.len(), path); + let g = ConstraintGraph::new(tcx, format!("region_constraints"), map); + let mut f = File::create(&Path::new(path)); + debug!("dump_region_constraints calling render"); + dot::render(&g, &mut f) +} diff --git a/src/librustc/middle/infer/region_inference/mod.rs b/src/librustc/middle/infer/region_inference/mod.rs index 98f69f66b27fc..a284dddc323d9 100644 --- a/src/librustc/middle/infer/region_inference/mod.rs +++ b/src/librustc/middle/infer/region_inference/mod.rs @@ -37,9 +37,10 @@ use std::uint; use syntax::ast; mod doc; +mod graphviz; // A constraint that influences the inference process. -#[deriving(PartialEq, Eq, Hash)] +#[deriving(Clone, PartialEq, Eq, Hash, Show)] pub enum Constraint { // One region variable is subregion of another ConstrainVarSubVar(RegionVid, RegionVid), @@ -706,10 +707,10 @@ impl<'a, 'tcx> RegionVarBindings<'a, 'tcx> { /// fixed-point iteration to find region values which satisfy all /// constraints, assuming such values can be found; if they cannot, /// errors are reported. - pub fn resolve_regions(&self) -> Vec> { + pub fn resolve_regions(&self, subject_node: ast::NodeId) -> Vec> { debug!("RegionVarBindings: resolve_regions()"); let mut errors = vec!(); - let v = self.infer_variable_values(&mut errors); + let v = self.infer_variable_values(&mut errors, subject_node); *self.values.borrow_mut() = Some(v); errors } @@ -958,14 +959,15 @@ type RegionGraph = graph::Graph<(), Constraint>; impl<'a, 'tcx> RegionVarBindings<'a, 'tcx> { fn infer_variable_values(&self, - errors: &mut Vec>) - -> Vec + errors: &mut Vec>, + subject: ast::NodeId) -> Vec { let mut var_data = self.construct_var_data(); // Dorky hack to cause `dump_constraints` to only get called // if debug mode is enabled: debug!("----() End constraint listing {}---", self.dump_constraints()); + graphviz::maybe_print_constraints_for(self, subject); self.expansion(var_data.as_mut_slice()); self.contraction(var_data.as_mut_slice()); diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index c7b5e1e8de9a5..b3b44b60b6ead 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -276,7 +276,8 @@ debugging_opts!( FLOWGRAPH_PRINT_MOVES, FLOWGRAPH_PRINT_ASSIGNS, FLOWGRAPH_PRINT_ALL, - PRINT_SYSROOT + PRINT_SYSROOT, + PRINT_REGION_GRAPH ] 0 ) @@ -322,7 +323,10 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> { ("flowgraph-print-all", "Include all dataflow analysis data in \ --pretty flowgraph output", FLOWGRAPH_PRINT_ALL), ("print-sysroot", "Print the sysroot as used by this rustc invocation", - PRINT_SYSROOT)] + PRINT_SYSROOT), + ("print-region-graph", "Prints region inference graph. \ + Use with RUST_REGION_GRAPH=help for more info", + PRINT_REGION_GRAPH)] } #[deriving(Clone)] diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index e0df94745d6a9..ad0046a1ad4b5 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -1190,7 +1190,7 @@ fn compare_impl_method<'tcx>(tcx: &ty::ctxt<'tcx>, // Finally, resolve all regions. This catches wily misuses of lifetime // parameters. - infcx.resolve_regions_and_report_errors(); + infcx.resolve_regions_and_report_errors(impl_m_body_id); /// Check that region bounds on impl method are the same as those on the trait. In principle, /// it could be ok for there to be fewer region bounds on the impl method, but this leads to an diff --git a/src/librustc_typeck/check/regionck.rs b/src/librustc_typeck/check/regionck.rs index 9f75b9764ebd8..501b9775f7f11 100644 --- a/src/librustc_typeck/check/regionck.rs +++ b/src/librustc_typeck/check/regionck.rs @@ -139,27 +139,31 @@ use syntax::visit::Visitor; use std::cell::{RefCell}; use std::collections::hash_map::{Vacant, Occupied}; +use self::RepeatingScope::Repeating; +use self::SubjectNode::Subject; + + /////////////////////////////////////////////////////////////////////////// // PUBLIC ENTRY POINTS pub fn regionck_expr(fcx: &FnCtxt, e: &ast::Expr) { - let mut rcx = Rcx::new(fcx, e.id); + let mut rcx = Rcx::new(fcx, Repeating(e.id), Subject(e.id)); if fcx.err_count_since_creation() == 0 { // regionck assumes typeck succeeded rcx.visit_expr(e); rcx.visit_region_obligations(e.id); } - fcx.infcx().resolve_regions_and_report_errors(); + rcx.resolve_regions_and_report_errors(); } pub fn regionck_item(fcx: &FnCtxt, item: &ast::Item) { - let mut rcx = Rcx::new(fcx, item.id); + let mut rcx = Rcx::new(fcx, Repeating(item.id), Subject(item.id)); rcx.visit_region_obligations(item.id); - fcx.infcx().resolve_regions_and_report_errors(); + rcx.resolve_regions_and_report_errors(); } pub fn regionck_fn(fcx: &FnCtxt, id: ast::NodeId, decl: &ast::FnDecl, blk: &ast::Block) { - let mut rcx = Rcx::new(fcx, blk.id); + let mut rcx = Rcx::new(fcx, Repeating(blk.id), Subject(id)); if fcx.err_count_since_creation() == 0 { // regionck assumes typeck succeeded rcx.visit_fn_body(id, decl, blk); @@ -169,7 +173,7 @@ pub fn regionck_fn(fcx: &FnCtxt, id: ast::NodeId, decl: &ast::FnDecl, blk: &ast: // particularly around closure bounds. vtable::select_all_fcx_obligations_or_error(fcx); - fcx.infcx().resolve_regions_and_report_errors(); + rcx.resolve_regions_and_report_errors(); } /// Checks that the types in `component_tys` are well-formed. This will add constraints into the @@ -177,7 +181,7 @@ pub fn regionck_fn(fcx: &FnCtxt, id: ast::NodeId, decl: &ast::FnDecl, blk: &ast: pub fn regionck_ensure_component_tys_wf<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, span: Span, component_tys: &[Ty<'tcx>]) { - let mut rcx = Rcx::new(fcx, 0); + let mut rcx = Rcx::new(fcx, Repeating(0), SubjectNode::None); for &component_ty in component_tys.iter() { // Check that each type outlives the empty region. Since the // empty region is a subregion of all others, this can't fail @@ -225,6 +229,9 @@ pub struct Rcx<'a, 'tcx: 'a> { // id of innermost fn or loop repeating_scope: ast::NodeId, + // id of AST node being analyzed (the subject of the analysis). + subject: SubjectNode, + // Possible region links we will establish if an upvar // turns out to be unique/mutable maybe_links: MaybeLinkMap<'tcx> @@ -251,11 +258,17 @@ fn region_of_def(fcx: &FnCtxt, def: def::Def) -> ty::Region { } } +pub enum RepeatingScope { Repeating(ast::NodeId) } +pub enum SubjectNode { Subject(ast::NodeId), None } + impl<'a, 'tcx> Rcx<'a, 'tcx> { pub fn new(fcx: &'a FnCtxt<'a, 'tcx>, - initial_repeating_scope: ast::NodeId) -> Rcx<'a, 'tcx> { + initial_repeating_scope: RepeatingScope, + subject: SubjectNode) -> Rcx<'a, 'tcx> { + let Repeating(initial_repeating_scope) = initial_repeating_scope; Rcx { fcx: fcx, repeating_scope: initial_repeating_scope, + subject: subject, region_param_pairs: Vec::new(), maybe_links: RefCell::new(FnvHashMap::new()) } } @@ -425,6 +438,18 @@ impl<'a, 'tcx> Rcx<'a, 'tcx> { debug!("<< relate_free_regions"); } + + fn resolve_regions_and_report_errors(&self) { + let subject_node_id = match self.subject { + Subject(s) => s, + SubjectNode::None => { + self.tcx().sess.bug("cannot resolve_regions_and_report_errors \ + without subject node"); + } + }; + + self.fcx.infcx().resolve_regions_and_report_errors(subject_node_id); + } } impl<'fcx, 'tcx> mc::Typer<'tcx> for Rcx<'fcx, 'tcx> { diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 61b8e6c956cab..962bb575dc841 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -2237,6 +2237,6 @@ fn check_method_self_type<'a, 'tcx, RS:RegionScope>( format!("mismatched self type: expected `{}`", ppaux::ty_to_string(crate_context.tcx, required_type)) })); - infcx.resolve_regions_and_report_errors(); + infcx.resolve_regions_and_report_errors(body_id); } }