auto merge of #13222 : nick29581/rust/dxr4, r=brson
Adds a -Z flag `save-analysis` which runs after the analysis phase of the compiler and saves a bunch of info into a CSV file for the crate. This is designed to work with the DXR code browser, but is frontend-independent, that is this info should be useful for all kinds of code browsers, IDEs, or other tools. I need to squash commits before landing (there will probably be a fair few to come), please ignore them for now and just comment on the changes.
This commit is contained in:
commit
c119903f62
11 changed files with 2531 additions and 37 deletions
|
@ -172,7 +172,8 @@ debugging_opts!(
|
|||
LTO,
|
||||
AST_JSON,
|
||||
AST_JSON_NOEXPAND,
|
||||
LS
|
||||
LS,
|
||||
SAVE_ANALYSIS
|
||||
]
|
||||
0
|
||||
)
|
||||
|
@ -206,7 +207,9 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
|
|||
("lto", "Perform LLVM link-time optimizations", LTO),
|
||||
("ast-json", "Print the AST as JSON and halt", AST_JSON),
|
||||
("ast-json-noexpand", "Print the pre-expansion AST as JSON and halt", AST_JSON_NOEXPAND),
|
||||
("ls", "List the symbols defined by a library crate", LS))
|
||||
("ls", "List the symbols defined by a library crate", LS),
|
||||
("save-analysis", "Write syntax and type analysis information \
|
||||
in addition to normal output", SAVE_ANALYSIS))
|
||||
}
|
||||
|
||||
/// Declare a macro that will define all CodegenOptions fields and parsers all
|
||||
|
|
|
@ -87,6 +87,7 @@ pub fn compile_input(sess: Session,
|
|||
if stop_after_phase_2(&sess) { return; }
|
||||
|
||||
let analysis = phase_3_run_analysis_passes(sess, &expanded_crate, ast_map);
|
||||
phase_save_analysis(&analysis.ty_cx.sess, &expanded_crate, &analysis, outdir);
|
||||
if stop_after_phase_3(&analysis.ty_cx.sess) { return; }
|
||||
let (tcx, trans) = phase_4_translate_to_llvm(expanded_crate,
|
||||
analysis, &outputs);
|
||||
|
@ -370,6 +371,17 @@ pub fn phase_3_run_analysis_passes(sess: Session,
|
|||
}
|
||||
}
|
||||
|
||||
pub fn phase_save_analysis(sess: &Session,
|
||||
krate: &ast::Crate,
|
||||
analysis: &CrateAnalysis,
|
||||
odir: &Option<Path>) {
|
||||
if (sess.opts.debugging_opts & config::SAVE_ANALYSIS) == 0 {
|
||||
return;
|
||||
}
|
||||
time(sess.time_passes(), "save analysis", krate, |krate|
|
||||
middle::save::process_crate(sess, krate, analysis, odir));
|
||||
}
|
||||
|
||||
pub struct CrateTranslation {
|
||||
pub context: ContextRef,
|
||||
pub module: ModuleRef,
|
||||
|
|
|
@ -28,6 +28,7 @@ use syntax::{ast, codemap};
|
|||
use std::os;
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
|
||||
pub struct Session {
|
||||
pub targ_cfg: config::Config,
|
||||
pub opts: config::Options,
|
||||
|
|
|
@ -84,6 +84,7 @@ pub mod middle {
|
|||
pub mod expr_use_visitor;
|
||||
pub mod dependency_format;
|
||||
pub mod weak_lang_items;
|
||||
pub mod save;
|
||||
}
|
||||
|
||||
pub mod front {
|
||||
|
|
1439
src/librustc/middle/save/mod.rs
Normal file
1439
src/librustc/middle/save/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
575
src/librustc/middle/save/recorder.rs
Normal file
575
src/librustc/middle/save/recorder.rs
Normal file
|
@ -0,0 +1,575 @@
|
|||
// Copyright 2012-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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use middle::save::escape;
|
||||
use middle::save::span_utils::SpanUtils;
|
||||
|
||||
use std::vec::Vec;
|
||||
|
||||
use syntax::ast;
|
||||
use syntax::ast::{NodeId,DefId};
|
||||
use syntax::codemap::*;
|
||||
|
||||
pub struct Recorder {
|
||||
// output file
|
||||
pub out: Box<Writer>,
|
||||
pub dump_spans: bool,
|
||||
}
|
||||
|
||||
impl Recorder {
|
||||
pub fn record(&mut self, info: &str) {
|
||||
match write!(self.out, "{}", info) {
|
||||
Err(_) => error!("Error writing output '{}'", info),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump_span(&mut self,
|
||||
su: SpanUtils,
|
||||
kind: &str,
|
||||
span: Span,
|
||||
_sub_span: Option<Span>) {
|
||||
assert!(self.dump_spans);
|
||||
let result = format!("span,kind,{},{},text,\"{}\"\n",
|
||||
kind, su.extent_str(span), escape(su.snippet(span)));
|
||||
self.record(result.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FmtStrs<'a> {
|
||||
pub recorder: Box<Recorder>,
|
||||
span: SpanUtils<'a>,
|
||||
krate: String,
|
||||
}
|
||||
|
||||
macro_rules! s { ($e:expr) => { format!("{}", $e) }}
|
||||
macro_rules! svec {
|
||||
($($e:expr),*) => ({
|
||||
// leading _ to allow empty construction without a warning.
|
||||
let mut _temp = ::std::vec::Vec::new();
|
||||
$(_temp.push(s!($e));)*
|
||||
_temp
|
||||
})
|
||||
}
|
||||
|
||||
pub enum Row {
|
||||
Variable,
|
||||
Enum,
|
||||
Variant,
|
||||
VariantStruct,
|
||||
Function,
|
||||
MethodDecl,
|
||||
Struct,
|
||||
Trait,
|
||||
Impl,
|
||||
Module,
|
||||
UseAlias,
|
||||
ExternCrate,
|
||||
Inheritance,
|
||||
MethodCall,
|
||||
Typedef,
|
||||
ExternalCrate,
|
||||
Crate,
|
||||
FnCall,
|
||||
ModRef,
|
||||
VarRef,
|
||||
TypeRef,
|
||||
StructRef,
|
||||
FnRef,
|
||||
}
|
||||
|
||||
impl<'a> FmtStrs<'a> {
|
||||
pub fn new(rec: Box<Recorder>, span: SpanUtils<'a>, krate: String) -> FmtStrs<'a> {
|
||||
FmtStrs {
|
||||
recorder: rec,
|
||||
span: span,
|
||||
krate: krate,
|
||||
}
|
||||
}
|
||||
|
||||
// A map from kind of item to a tuple of
|
||||
// a string representation of the name
|
||||
// a vector of field names
|
||||
// whether this kind requires a span
|
||||
// whether dump_spans should dump for this kind
|
||||
fn lookup_row(r: Row) -> (&'static str, Vec<&'static str>, bool, bool) {
|
||||
match r {
|
||||
Variable => ("variable",
|
||||
vec!("id","name","qualname","value","type","scopeid"),
|
||||
true, true),
|
||||
Enum => ("enum", vec!("id","qualname","scopeid"), true, true),
|
||||
Variant => ("variant", vec!("id","name","qualname","value","scopeid"), true, true),
|
||||
VariantStruct => ("variant_struct",
|
||||
vec!("id","ctor_id","qualname","value","scopeid"), true, true),
|
||||
Function => ("function", vec!("id","qualname","declid","declidcrate","scopeid"),
|
||||
true, true),
|
||||
MethodDecl => ("method_decl", vec!("id","qualname","scopeid"), true, true),
|
||||
Struct => ("struct", vec!("id","ctor_id","qualname","scopeid"), true, true),
|
||||
Trait => ("trait", vec!("id","qualname","scopeid"), true, true),
|
||||
Impl => ("impl", vec!("id","refid","refidcrate","scopeid"), true, true),
|
||||
Module => ("module", vec!("id","qualname","scopeid","def_file"), true, false),
|
||||
UseAlias => ("use_alias",
|
||||
vec!("id","refid","refidcrate","name","scopeid"),
|
||||
true, true),
|
||||
ExternCrate => ("extern_crate",
|
||||
vec!("id","name","location","crate","scopeid"),
|
||||
true, true),
|
||||
Inheritance => ("inheritance",
|
||||
vec!("base","basecrate","derived","derivedcrate"),
|
||||
true, false),
|
||||
MethodCall => ("method_call",
|
||||
vec!("refid","refidcrate","declid","declidcrate","scopeid"),
|
||||
true, true),
|
||||
Typedef => ("typedef", vec!("id","qualname","value"), true, true),
|
||||
ExternalCrate => ("external_crate", vec!("name","crate","file_name"), false, false),
|
||||
Crate => ("crate", vec!("name"), true, false),
|
||||
FnCall => ("fn_call", vec!("refid","refidcrate","qualname","scopeid"), true, true),
|
||||
ModRef => ("mod_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true),
|
||||
VarRef => ("var_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true),
|
||||
TypeRef => ("type_ref",
|
||||
vec!("refid","refidcrate","qualname","scopeid"),
|
||||
true, true),
|
||||
StructRef => ("struct_ref",
|
||||
vec!("refid","refidcrate","qualname","scopeid"),
|
||||
true, true),
|
||||
FnRef => ("fn_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_values_str(&self,
|
||||
kind: &'static str,
|
||||
fields: &Vec<&'static str>,
|
||||
values: Vec<String>,
|
||||
span: Span) -> Option<String> {
|
||||
if values.len() != fields.len() {
|
||||
self.span.sess.span_bug(span, format!(
|
||||
"Mismatch between length of fields for '{}', expected '{}', found '{}'",
|
||||
kind, fields.len(), values.len()).as_slice());
|
||||
}
|
||||
|
||||
let values = values.iter().map(|s| {
|
||||
if s.len() > 1020 {
|
||||
s.as_slice().slice_to(1020)
|
||||
} else {
|
||||
s.as_slice()
|
||||
}
|
||||
});
|
||||
|
||||
let pairs = fields.iter().zip(values);
|
||||
let mut strs = pairs.map(|(f, v)| format!(",{},\"{}\"", f, escape(
|
||||
if *f == "qualname" {
|
||||
self.krate.clone().append("::").append(v)
|
||||
} else {
|
||||
String::from_str(v)
|
||||
}
|
||||
)));
|
||||
Some(strs.fold(String::new(), |s, ss| s.append(ss.as_slice()))).map(|s| s.into_owned())
|
||||
}
|
||||
|
||||
pub fn record_without_span(&mut self,
|
||||
kind: Row,
|
||||
values: Vec<String>,
|
||||
span: Span) {
|
||||
let (label, ref fields, needs_span, dump_spans) = FmtStrs::lookup_row(kind);
|
||||
|
||||
if needs_span {
|
||||
self.span.sess.span_bug(span, format!(
|
||||
"Called record_without_span for '{}' which does requires a span",
|
||||
label).as_slice());
|
||||
}
|
||||
assert!(!dump_spans);
|
||||
|
||||
if self.recorder.dump_spans {
|
||||
return;
|
||||
}
|
||||
|
||||
let values_str = match self.make_values_str(label, fields, values, span) {
|
||||
Some(vs) => vs,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let result = String::from_str(label);
|
||||
self.recorder.record(result.append(values_str.as_slice()).append("\n").as_slice());
|
||||
}
|
||||
|
||||
pub fn record_with_span(&mut self,
|
||||
kind: Row,
|
||||
span: Span,
|
||||
sub_span: Span,
|
||||
values: Vec<String>) {
|
||||
let (label, ref fields, needs_span, dump_spans) = FmtStrs::lookup_row(kind);
|
||||
|
||||
if self.recorder.dump_spans {
|
||||
if dump_spans {
|
||||
self.recorder.dump_span(self.span, label, span, Some(sub_span));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if !needs_span {
|
||||
self.span.sess.span_bug(span,
|
||||
format!("Called record_with_span for '{}' \
|
||||
which does not require a span", label).as_slice());
|
||||
}
|
||||
|
||||
let values_str = match self.make_values_str(label, fields, values, span) {
|
||||
Some(vs) => vs,
|
||||
None => return,
|
||||
};
|
||||
let result = format!("{},{}{}\n", label, self.span.extent_str(sub_span), values_str);
|
||||
self.recorder.record(result.as_slice());
|
||||
}
|
||||
|
||||
pub fn check_and_record(&mut self,
|
||||
kind: Row,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
values: Vec<String>) {
|
||||
match sub_span {
|
||||
Some(sub_span) => self.record_with_span(kind, span, sub_span, values),
|
||||
None => {
|
||||
let (label, _, _, _) = FmtStrs::lookup_row(kind);
|
||||
self.span.report_span_err(label, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn variable_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
value: &str,
|
||||
typ: &str) {
|
||||
// Getting a fully qualified name for a variable is hard because in
|
||||
// the local case they can be overridden in one block and there is no nice way
|
||||
// to refer to such a scope in english, so we just hack it by appending the
|
||||
// variable def's node id
|
||||
let qualname = String::from_str(name).append("$").append(id.to_str().as_slice());
|
||||
self.check_and_record(Variable,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, qualname, value, typ, 0));
|
||||
}
|
||||
|
||||
// formal parameters
|
||||
pub fn formal_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
fn_name: &str,
|
||||
name: &str,
|
||||
typ: &str) {
|
||||
let qualname = String::from_str(fn_name).append("::").append(name);
|
||||
self.check_and_record(Variable,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, qualname, "", typ, 0));
|
||||
}
|
||||
|
||||
// value is the initialising expression of the static if it is not mut, otherwise "".
|
||||
pub fn static_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
qualname: &str,
|
||||
value: &str,
|
||||
typ: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Variable,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, qualname, value, typ, scope_id));
|
||||
}
|
||||
|
||||
pub fn field_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
qualname: &str,
|
||||
typ: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Variable,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, qualname, "", typ, scope_id));
|
||||
}
|
||||
|
||||
pub fn enum_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Enum,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, scope_id));
|
||||
}
|
||||
|
||||
pub fn tuple_variant_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
qualname: &str,
|
||||
val: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Variant,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, qualname, val, scope_id));
|
||||
}
|
||||
|
||||
pub fn struct_variant_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
ctor_id: NodeId,
|
||||
name: &str,
|
||||
val: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(VariantStruct,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, ctor_id, name, val, scope_id));
|
||||
}
|
||||
|
||||
pub fn fn_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Function,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, "", "", scope_id));
|
||||
}
|
||||
|
||||
pub fn method_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
decl_id: Option<DefId>,
|
||||
scope_id: NodeId) {
|
||||
let values = match decl_id {
|
||||
Some(decl_id) => svec!(id, name, decl_id.node, decl_id.krate, scope_id),
|
||||
None => svec!(id, name, "", "", scope_id)
|
||||
};
|
||||
self.check_and_record(Function,
|
||||
span,
|
||||
sub_span,
|
||||
values);
|
||||
}
|
||||
|
||||
pub fn method_decl_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(MethodDecl,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, scope_id));
|
||||
}
|
||||
|
||||
pub fn struct_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
ctor_id: NodeId,
|
||||
name: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Struct,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, ctor_id, name, scope_id));
|
||||
}
|
||||
|
||||
pub fn trait_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Trait,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, scope_id));
|
||||
}
|
||||
|
||||
pub fn impl_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
ref_id: DefId,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(Impl,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, ref_id.node, ref_id.krate, scope_id));
|
||||
}
|
||||
|
||||
pub fn mod_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
name: &str,
|
||||
parent: NodeId,
|
||||
filename: &str) {
|
||||
self.check_and_record(Module,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, parent, filename));
|
||||
}
|
||||
|
||||
pub fn use_alias_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
mod_id: Option<DefId>,
|
||||
name: &str,
|
||||
parent: NodeId) {
|
||||
let (mod_node, mod_crate) = match mod_id {
|
||||
Some(mod_id) => (mod_id.node, mod_id.krate),
|
||||
None => (0, 0)
|
||||
};
|
||||
self.check_and_record(UseAlias,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, mod_node, mod_crate, name, parent));
|
||||
}
|
||||
|
||||
pub fn extern_crate_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
cnum: ast::CrateNum,
|
||||
name: &str,
|
||||
loc: &str,
|
||||
parent: NodeId) {
|
||||
self.check_and_record(ExternCrate,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, name, loc, cnum, parent));
|
||||
}
|
||||
|
||||
pub fn inherit_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
base_id: DefId,
|
||||
deriv_id: NodeId) {
|
||||
self.check_and_record(Inheritance,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(base_id.node, base_id.krate, deriv_id, 0));
|
||||
}
|
||||
|
||||
pub fn fn_call_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: DefId,
|
||||
scope_id:NodeId) {
|
||||
self.check_and_record(FnCall,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id.node, id.krate, "", scope_id));
|
||||
}
|
||||
|
||||
pub fn meth_call_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
defid: Option<DefId>,
|
||||
declid: Option<DefId>,
|
||||
scope_id: NodeId) {
|
||||
let (dfn, dfk) = match defid {
|
||||
Some(defid) => (defid.node, defid.krate),
|
||||
None => (0, 0)
|
||||
};
|
||||
let (dcn, dck) = match declid {
|
||||
Some(declid) => (s!(declid.node), s!(declid.krate)),
|
||||
None => ("".to_owned(), "".to_owned())
|
||||
};
|
||||
self.check_and_record(MethodCall,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(dfn, dfk, dcn, dck, scope_id));
|
||||
}
|
||||
|
||||
pub fn sub_mod_ref_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Span,
|
||||
qualname: &str,
|
||||
parent:NodeId) {
|
||||
self.record_with_span(ModRef,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(0, 0, qualname, parent));
|
||||
}
|
||||
|
||||
pub fn typedef_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: NodeId,
|
||||
qualname: &str,
|
||||
value: &str) {
|
||||
self.check_and_record(Typedef,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id, qualname, value));
|
||||
}
|
||||
|
||||
pub fn crate_str(&mut self,
|
||||
span: Span,
|
||||
name: &str) {
|
||||
self.record_with_span(Crate,
|
||||
span,
|
||||
span,
|
||||
svec!(name));
|
||||
}
|
||||
|
||||
pub fn external_crate_str(&mut self,
|
||||
span: Span,
|
||||
name: &str,
|
||||
num: ast::CrateNum) {
|
||||
let lo_loc = self.span.sess.codemap().lookup_char_pos(span.lo);
|
||||
self.record_without_span(ExternalCrate,
|
||||
svec!(name, num, lo_loc.file.name),
|
||||
span);
|
||||
}
|
||||
|
||||
pub fn sub_type_ref_str(&mut self,
|
||||
span: Span,
|
||||
sub_span: Span,
|
||||
qualname: &str) {
|
||||
self.record_with_span(TypeRef,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(0, 0, qualname, 0));
|
||||
}
|
||||
|
||||
// A slightly generic function for a reference to an item of any kind.
|
||||
pub fn ref_str(&mut self,
|
||||
kind: Row,
|
||||
span: Span,
|
||||
sub_span: Option<Span>,
|
||||
id: DefId,
|
||||
scope_id: NodeId) {
|
||||
self.check_and_record(kind,
|
||||
span,
|
||||
sub_span,
|
||||
svec!(id.node, id.krate, "", scope_id));
|
||||
}
|
||||
}
|
381
src/librustc/middle/save/span_utils.rs
Normal file
381
src/librustc/middle/save/span_utils.rs
Normal file
|
@ -0,0 +1,381 @@
|
|||
// Copyright 2012-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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use driver::session::Session;
|
||||
|
||||
use middle::save::generated_code;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use syntax::ast;
|
||||
use syntax::codemap::*;
|
||||
use syntax::parse::lexer;
|
||||
use syntax::parse::lexer::{Reader,StringReader};
|
||||
use syntax::parse::token;
|
||||
use syntax::parse::token::{is_keyword,keywords,is_ident,Token};
|
||||
|
||||
pub struct SpanUtils<'a> {
|
||||
pub sess: &'a Session,
|
||||
pub err_count: Cell<int>,
|
||||
}
|
||||
|
||||
impl<'a> SpanUtils<'a> {
|
||||
// Standard string for extents/location.
|
||||
pub fn extent_str(&self, span: Span) -> String {
|
||||
let lo_loc = self.sess.codemap().lookup_char_pos(span.lo);
|
||||
let hi_loc = self.sess.codemap().lookup_char_pos(span.hi);
|
||||
let lo_pos = self.sess.codemap().lookup_byte_offset(span.lo).pos;
|
||||
let hi_pos = self.sess.codemap().lookup_byte_offset(span.hi).pos;
|
||||
|
||||
format!("file_name,{},file_line,{},file_col,{},extent_start,{},\
|
||||
file_line_end,{},file_col_end,{},extent_end,{}",
|
||||
lo_loc.file.name, lo_loc.line, lo_loc.col.to_uint(), lo_pos.to_uint(),
|
||||
hi_loc.line, hi_loc.col.to_uint(), hi_pos.to_uint())
|
||||
}
|
||||
|
||||
// sub_span starts at span.lo, so we need to adjust the positions etc.
|
||||
// If sub_span is None, we don't need to adjust.
|
||||
pub fn make_sub_span(&self, span: Span, sub_span: Option<Span>) -> Option<Span> {
|
||||
let loc = self.sess.codemap().lookup_char_pos(span.lo);
|
||||
assert!(!generated_code(span),
|
||||
"generated code; we should not be processing this `{}` in {}, line {}",
|
||||
self.snippet(span), loc.file.name, loc.line);
|
||||
|
||||
match sub_span {
|
||||
None => None,
|
||||
Some(sub) => {
|
||||
let FileMapAndBytePos {fm, pos} =
|
||||
self.sess.codemap().lookup_byte_offset(span.lo);
|
||||
let base = pos + fm.start_pos;
|
||||
Some(Span {
|
||||
lo: base + self.sess.codemap().lookup_byte_offset(sub.lo).pos,
|
||||
hi: base + self.sess.codemap().lookup_byte_offset(sub.hi).pos,
|
||||
expn_info: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snippet(&self, span: Span) -> String {
|
||||
match self.sess.codemap().span_to_snippet(span) {
|
||||
Some(s) => s,
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retokenise_span(&self, span: Span) -> StringReader<'a> {
|
||||
// sadness - we don't have spans for sub-expressions nor access to the tokens
|
||||
// so in order to get extents for the function name itself (which dxr expects)
|
||||
// we need to re-tokenise the fn definition
|
||||
|
||||
// Note: this is a bit awful - it adds the contents of span to the end of
|
||||
// the codemap as a new filemap. This is mostly OK, but means we should
|
||||
// not iterate over the codemap. Also, any spans over the new filemap
|
||||
// are incompatible with spans over other filemaps.
|
||||
let filemap = self.sess.codemap().new_filemap(String::from_str("<anon-dxr>"),
|
||||
self.snippet(span));
|
||||
let s = self.sess;
|
||||
lexer::StringReader::new(s.diagnostic(), filemap)
|
||||
}
|
||||
|
||||
// Re-parses a path and returns the span for the last identifier in the path
|
||||
pub fn span_for_last_ident(&self, span: Span) -> Option<Span> {
|
||||
let mut result = None;
|
||||
|
||||
let mut toks = self.retokenise_span(span);
|
||||
let mut bracket_count = 0;
|
||||
loop {
|
||||
let ts = toks.next_token();
|
||||
if ts.tok == token::EOF {
|
||||
return self.make_sub_span(span, result)
|
||||
}
|
||||
if bracket_count == 0 &&
|
||||
(is_ident(&ts.tok) || is_keyword(keywords::Self, &ts.tok)) {
|
||||
result = Some(ts.sp);
|
||||
}
|
||||
|
||||
bracket_count += match ts.tok {
|
||||
token::LT => 1,
|
||||
token::GT => -1,
|
||||
token::BINOP(token::SHR) => -2,
|
||||
_ => 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the span for the first identifier in the path.
|
||||
pub fn span_for_first_ident(&self, span: Span) -> Option<Span> {
|
||||
let mut toks = self.retokenise_span(span);
|
||||
let mut bracket_count = 0;
|
||||
loop {
|
||||
let ts = toks.next_token();
|
||||
if ts.tok == token::EOF {
|
||||
return None;
|
||||
}
|
||||
if bracket_count == 0 &&
|
||||
(is_ident(&ts.tok) || is_keyword(keywords::Self, &ts.tok)) {
|
||||
return self.make_sub_span(span, Some(ts.sp));
|
||||
}
|
||||
|
||||
bracket_count += match ts.tok {
|
||||
token::LT => 1,
|
||||
token::GT => -1,
|
||||
token::BINOP(token::SHR) => -2,
|
||||
_ => 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the span for the last ident before a `(` or `<` or '::<' and outside any
|
||||
// any brackets, or the last span.
|
||||
pub fn sub_span_for_meth_name(&self, span: Span) -> Option<Span> {
|
||||
let mut toks = self.retokenise_span(span);
|
||||
let mut prev = toks.next_token();
|
||||
let mut result = None;
|
||||
let mut bracket_count = 0;
|
||||
let mut last_span = None;
|
||||
while prev.tok != token::EOF {
|
||||
last_span = None;
|
||||
let mut next = toks.next_token();
|
||||
|
||||
if (next.tok == token::LPAREN ||
|
||||
next.tok == token::LT) &&
|
||||
bracket_count == 0 &&
|
||||
is_ident(&prev.tok) {
|
||||
result = Some(prev.sp);
|
||||
}
|
||||
|
||||
if bracket_count == 0 &&
|
||||
next.tok == token::MOD_SEP {
|
||||
let old = prev;
|
||||
prev = next;
|
||||
next = toks.next_token();
|
||||
if next.tok == token::LT &&
|
||||
is_ident(&old.tok) {
|
||||
result = Some(old.sp);
|
||||
}
|
||||
}
|
||||
|
||||
bracket_count += match prev.tok {
|
||||
token::LPAREN | token::LT => 1,
|
||||
token::RPAREN | token::GT => -1,
|
||||
token::BINOP(token::SHR) => -2,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if is_ident(&prev.tok) && bracket_count == 0 {
|
||||
last_span = Some(prev.sp);
|
||||
}
|
||||
prev = next;
|
||||
}
|
||||
if result.is_none() && last_span.is_some() {
|
||||
return self.make_sub_span(span, last_span);
|
||||
}
|
||||
return self.make_sub_span(span, result);
|
||||
}
|
||||
|
||||
// Return the span for the last ident before a `<` and outside any
|
||||
// brackets, or the last span.
|
||||
pub fn sub_span_for_type_name(&self, span: Span) -> Option<Span> {
|
||||
let mut toks = self.retokenise_span(span);
|
||||
let mut prev = toks.next_token();
|
||||
let mut result = None;
|
||||
let mut bracket_count = 0;
|
||||
loop {
|
||||
let next = toks.next_token();
|
||||
|
||||
if (next.tok == token::LT ||
|
||||
next.tok == token::COLON) &&
|
||||
bracket_count == 0 &&
|
||||
is_ident(&prev.tok) {
|
||||
result = Some(prev.sp);
|
||||
}
|
||||
|
||||
bracket_count += match prev.tok {
|
||||
token::LT => 1,
|
||||
token::GT => -1,
|
||||
token::BINOP(token::SHR) => -2,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if next.tok == token::EOF {
|
||||
break;
|
||||
}
|
||||
prev = next;
|
||||
}
|
||||
if bracket_count != 0 {
|
||||
let loc = self.sess.codemap().lookup_char_pos(span.lo);
|
||||
self.sess.span_bug(span,
|
||||
format!("Mis-counted brackets when breaking path? Parsing '{}' in {}, line {}",
|
||||
self.snippet(span), loc.file.name, loc.line).as_slice());
|
||||
}
|
||||
if result.is_none() && is_ident(&prev.tok) && bracket_count == 0 {
|
||||
return self.make_sub_span(span, Some(prev.sp));
|
||||
}
|
||||
self.make_sub_span(span, result)
|
||||
}
|
||||
|
||||
// Reparse span and return an owned vector of sub spans of the first limit
|
||||
// identifier tokens in the given nesting level.
|
||||
// example with Foo<Bar<T,V>, Bar<T,V>>
|
||||
// Nesting = 0: all idents outside of brackets: ~[Foo]
|
||||
// Nesting = 1: idents within one level of brackets: ~[Bar, Bar]
|
||||
pub fn spans_with_brackets(&self, span: Span, nesting: int, limit: int) -> Vec<Span> {
|
||||
let mut result: Vec<Span> = vec!();
|
||||
|
||||
let mut toks = self.retokenise_span(span);
|
||||
// We keep track of how many brackets we're nested in
|
||||
let mut bracket_count = 0;
|
||||
loop {
|
||||
let ts = toks.next_token();
|
||||
if ts.tok == token::EOF {
|
||||
if bracket_count != 0 {
|
||||
let loc = self.sess.codemap().lookup_char_pos(span.lo);
|
||||
self.sess.span_bug(span, format!(
|
||||
"Mis-counted brackets when breaking path? Parsing '{}' in {}, line {}",
|
||||
self.snippet(span), loc.file.name, loc.line).as_slice());
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (result.len() as int) == limit {
|
||||
return result;
|
||||
}
|
||||
bracket_count += match ts.tok {
|
||||
token::LT => 1,
|
||||
token::GT => -1,
|
||||
token::BINOP(token::SHL) => 2,
|
||||
token::BINOP(token::SHR) => -2,
|
||||
_ => 0
|
||||
};
|
||||
if is_ident(&ts.tok) &&
|
||||
bracket_count == nesting {
|
||||
result.push(self.make_sub_span(span, Some(ts.sp)).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub_span_before_token(&self, span: Span, tok: Token) -> Option<Span> {
|
||||
let mut toks = self.retokenise_span(span);
|
||||
let mut prev = toks.next_token();
|
||||
loop {
|
||||
if prev.tok == token::EOF {
|
||||
return None;
|
||||
}
|
||||
let next = toks.next_token();
|
||||
if next.tok == tok {
|
||||
return self.make_sub_span(span, Some(prev.sp));
|
||||
}
|
||||
prev = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Return an owned vector of the subspans of the tokens that come before tok2
|
||||
// which is before tok1. If there is no instance of tok2 before tok1, then that
|
||||
// place in the result is None.
|
||||
// Everything returned must be inside a set of (non-angle) brackets, but no
|
||||
// more deeply nested than that.
|
||||
pub fn sub_spans_before_tokens(&self,
|
||||
span: Span,
|
||||
tok1: Token,
|
||||
tok2: Token) -> Vec<Option<Span>> {
|
||||
let mut sub_spans : Vec<Option<Span>> = vec!();
|
||||
let mut toks = self.retokenise_span(span);
|
||||
let mut prev = toks.next_token();
|
||||
let mut next = toks.next_token();
|
||||
let mut stored_val = false;
|
||||
let mut found_val = false;
|
||||
let mut bracket_count = 0;
|
||||
while next.tok != token::EOF {
|
||||
if bracket_count == 1 {
|
||||
if next.tok == tok2 {
|
||||
sub_spans.push(self.make_sub_span(span, Some(prev.sp)));
|
||||
stored_val = true;
|
||||
found_val = false;
|
||||
}
|
||||
if next.tok == tok1 {
|
||||
if !stored_val {
|
||||
sub_spans.push(None);
|
||||
} else {
|
||||
stored_val = false;
|
||||
}
|
||||
found_val = false;
|
||||
}
|
||||
if !stored_val &&
|
||||
is_ident(&next.tok) {
|
||||
found_val = true;
|
||||
}
|
||||
}
|
||||
|
||||
bracket_count += match next.tok {
|
||||
token::LPAREN | token::LBRACE => 1,
|
||||
token::RPAREN | token::RBRACE => -1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
prev = next;
|
||||
next = toks.next_token();
|
||||
}
|
||||
if found_val {
|
||||
sub_spans.push(None);
|
||||
}
|
||||
return sub_spans;
|
||||
}
|
||||
|
||||
pub fn sub_span_after_keyword(&self,
|
||||
span: Span,
|
||||
keyword: keywords::Keyword) -> Option<Span> {
|
||||
let mut toks = self.retokenise_span(span);
|
||||
loop {
|
||||
let ts = toks.next_token();
|
||||
if ts.tok == token::EOF {
|
||||
return None;
|
||||
}
|
||||
if is_keyword(keyword, &ts.tok) {
|
||||
let ts = toks.next_token();
|
||||
if ts.tok == token::EOF {
|
||||
return None
|
||||
} else {
|
||||
return self.make_sub_span(span, Some(ts.sp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of the spans of idents in a patch.
|
||||
// E.g., For foo::bar<x,t>::baz, we return [foo, bar, baz] (well, their spans)
|
||||
pub fn spans_for_path_segments(&self, path: &ast::Path) -> Vec<Span> {
|
||||
if generated_code(path.span) {
|
||||
return vec!();
|
||||
}
|
||||
|
||||
self.spans_with_brackets(path.span, 0, -1)
|
||||
}
|
||||
|
||||
// Return an owned vector of the subspans of the param identifier
|
||||
// tokens found in span.
|
||||
pub fn spans_for_ty_params(&self, span: Span, number: int) -> Vec<Span> {
|
||||
if generated_code(span) {
|
||||
return vec!();
|
||||
}
|
||||
// Type params are nested within one level of brackets:
|
||||
// i.e. we want ~[A, B] from Foo<A, B<T,U>>
|
||||
self.spans_with_brackets(span, 1, number)
|
||||
}
|
||||
|
||||
pub fn report_span_err(&self, kind: &str, span: Span) {
|
||||
let loc = self.sess.codemap().lookup_char_pos(span.lo);
|
||||
info!("({}) Could not find sub_span in `{}` in {}, line {}",
|
||||
kind, self.snippet(span), loc.file.name, loc.line);
|
||||
self.err_count.set(self.err_count.get()+1);
|
||||
if self.err_count.get() > 1000 {
|
||||
self.sess.bug("span errors reached 1000, giving up");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4375,6 +4375,27 @@ pub fn trait_id_of_impl(tcx: &ctxt,
|
|||
}
|
||||
}
|
||||
|
||||
/// If the given def ID describes a method belonging to an impl, return the
|
||||
/// ID of the impl that the method belongs to. Otherwise, return `None`.
|
||||
pub fn impl_of_method(tcx: &ctxt, def_id: ast::DefId)
|
||||
-> Option<ast::DefId> {
|
||||
if def_id.krate != LOCAL_CRATE {
|
||||
return match csearch::get_method(tcx, def_id).container {
|
||||
TraitContainer(_) => None,
|
||||
ImplContainer(def_id) => Some(def_id),
|
||||
};
|
||||
}
|
||||
match tcx.methods.borrow().find_copy(&def_id) {
|
||||
Some(method) => {
|
||||
match method.container {
|
||||
TraitContainer(_) => None,
|
||||
ImplContainer(def_id) => Some(def_id),
|
||||
}
|
||||
}
|
||||
None => None
|
||||
}
|
||||
}
|
||||
|
||||
/// If the given def ID describes a method belonging to a trait (either a
|
||||
/// default method or an implementation of a trait method), return the ID of
|
||||
/// the trait that the method belongs to. Otherwise, return `None`.
|
||||
|
|
|
@ -421,6 +421,41 @@ impl CodeMap {
|
|||
fail!("asking for {} which we don't know about", filename);
|
||||
}
|
||||
|
||||
pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos {
|
||||
let idx = self.lookup_filemap_idx(bpos);
|
||||
let fm = self.files.borrow().get(idx).clone();
|
||||
let offset = bpos - fm.start_pos;
|
||||
FileMapAndBytePos {fm: fm, pos: offset}
|
||||
}
|
||||
|
||||
// Converts an absolute BytePos to a CharPos relative to the filemap and above.
|
||||
pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
|
||||
debug!("codemap: converting {:?} to char pos", bpos);
|
||||
let idx = self.lookup_filemap_idx(bpos);
|
||||
let files = self.files.borrow();
|
||||
let map = files.get(idx);
|
||||
|
||||
// The number of extra bytes due to multibyte chars in the FileMap
|
||||
let mut total_extra_bytes = 0;
|
||||
|
||||
for mbc in map.multibyte_chars.borrow().iter() {
|
||||
debug!("codemap: {:?}-byte char at {:?}", mbc.bytes, mbc.pos);
|
||||
if mbc.pos < bpos {
|
||||
// every character is at least one byte, so we only
|
||||
// count the actual extra bytes.
|
||||
total_extra_bytes += mbc.bytes - 1;
|
||||
// We should never see a byte position in the middle of a
|
||||
// character
|
||||
assert!(bpos.to_uint() >= mbc.pos.to_uint() + mbc.bytes);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(map.start_pos.to_uint() + total_extra_bytes <= bpos.to_uint());
|
||||
CharPos(bpos.to_uint() - map.start_pos.to_uint() - total_extra_bytes)
|
||||
}
|
||||
|
||||
fn lookup_filemap_idx(&self, pos: BytePos) -> uint {
|
||||
let files = self.files.borrow();
|
||||
let files = files;
|
||||
|
@ -491,41 +526,6 @@ impl CodeMap {
|
|||
col: chpos - linechpos
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos {
|
||||
let idx = self.lookup_filemap_idx(bpos);
|
||||
let fm = self.files.borrow().get(idx).clone();
|
||||
let offset = bpos - fm.start_pos;
|
||||
FileMapAndBytePos {fm: fm, pos: offset}
|
||||
}
|
||||
|
||||
// Converts an absolute BytePos to a CharPos relative to the filemap.
|
||||
fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
|
||||
debug!("codemap: converting {:?} to char pos", bpos);
|
||||
let idx = self.lookup_filemap_idx(bpos);
|
||||
let files = self.files.borrow();
|
||||
let map = files.get(idx);
|
||||
|
||||
// The number of extra bytes due to multibyte chars in the FileMap
|
||||
let mut total_extra_bytes = 0;
|
||||
|
||||
for mbc in map.multibyte_chars.borrow().iter() {
|
||||
debug!("codemap: {:?}-byte char at {:?}", mbc.bytes, mbc.pos);
|
||||
if mbc.pos < bpos {
|
||||
// every character is at least one byte, so we only
|
||||
// count the actual extra bytes.
|
||||
total_extra_bytes += mbc.bytes - 1;
|
||||
// We should never see a byte position in the middle of a
|
||||
// character
|
||||
assert!(bpos.to_uint() >= mbc.pos.to_uint() + mbc.bytes);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(map.start_pos.to_uint() + total_extra_bytes <= bpos.to_uint());
|
||||
CharPos(bpos.to_uint() - map.start_pos.to_uint() - total_extra_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
3
src/test/run-make/save-analysis/Makefile
Normal file
3
src/test/run-make/save-analysis/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
|||
-include ../tools.mk
|
||||
all:
|
||||
$(RUSTC) foo.rs -Zsave-analysis
|
58
src/test/run-make/save-analysis/foo.rs
Normal file
58
src/test/run-make/save-analysis/foo.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
// 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 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
struct Foo {
|
||||
f: int
|
||||
}
|
||||
|
||||
impl Foo {
|
||||
fn bar(&self) -> int {
|
||||
println!("f is {}", self.f);
|
||||
self.f
|
||||
}
|
||||
}
|
||||
|
||||
trait Tr {
|
||||
fn tar(&self, x: Box<Foo>) -> Foo;
|
||||
}
|
||||
|
||||
impl Tr for Foo {
|
||||
fn tar(&self, x: Box<Foo>) -> Foo {
|
||||
Foo{ f: self.f + x.f }
|
||||
}
|
||||
}
|
||||
|
||||
trait Tr2<X, Y: Tr> {
|
||||
fn squid(&self, y: &Y, z: Self) -> Box<X>;
|
||||
}
|
||||
|
||||
impl Tr2<Foo, Foo> for Foo {
|
||||
fn squid(&self, y: &Foo, z: Foo) -> Box<Foo> {
|
||||
box Foo { f: y.f + z.f + self.f }
|
||||
}
|
||||
}
|
||||
|
||||
enum En {
|
||||
Var1,
|
||||
Var2,
|
||||
Var3(int, int, Foo)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = Foo { f: 237 };
|
||||
let _f = x.bar();
|
||||
let en = Var2;
|
||||
|
||||
let _ = match en {
|
||||
Var1 => x.bar(),
|
||||
Var2 => 34,
|
||||
Var3(x, y, f) => f.bar()
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue