use super::hir_id_from_path;
use crate::errors::ErrorWithSpan;
use crate::rule_finder::StartMatch;
use crate::rules::Rule;
use rustc::hir::{self, intravisit, HirId};
use rustc::ty::TyCtxt;
use std::collections::HashSet;
use syntax_pos::Span;
struct ValidatorState<'tcx> {
tcx: TyCtxt<'tcx>,
errors: Vec<ErrorWithSpan>,
placeholders: HashSet<HirId>,
bound_placeholders: HashSet<HirId>,
}
impl<'tcx> ValidatorState<'tcx> {
fn add_error<T: Into<String>>(&mut self, message: T, span: Span) {
self.errors.push(ErrorWithSpan::new(message, span));
}
}
impl<'tcx, T: StartMatch + 'tcx> Rule<'tcx, T> {
pub(crate) fn validate<'a>(
&self,
tcx: TyCtxt<'tcx>,
) -> Result<(), Vec<ErrorWithSpan>> {
let rule_body = tcx.hir().body(self.body_id);
let mut search_validator = SearchValidator {
state: ValidatorState {
tcx,
errors: Vec::new(),
placeholders: rule_body
.arguments
.iter()
.map(|arg| arg.pat.hir_id)
.collect(),
bound_placeholders: HashSet::new(),
},
};
StartMatch::walk(&mut search_validator, self.search);
let mut replacement_validator = ReplacementValidator {
state: search_validator.state,
};
StartMatch::walk(&mut replacement_validator, self.replace);
if !replacement_validator.state.errors.is_empty() {
return Err(replacement_validator.state.errors);
}
Ok(())
}
}
struct SearchValidator<'tcx> {
state: ValidatorState<'tcx>,
}
impl<'tcx> intravisit::Visitor<'tcx> for SearchValidator<'tcx> {
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
intravisit::NestedVisitorMap::All(&self.state.tcx.hir())
}
fn visit_qpath(&mut self, qpath: &'tcx hir::QPath, id: hir::HirId, span: Span) {
if let Some(hir_id) = hir_id_from_path(qpath) {
if self.state.placeholders.contains(&hir_id)
&& !self.state.bound_placeholders.insert(hir_id)
{
self.state.add_error(
"Placeholder is bound multiple times. This is not currently permitted.",
span,
);
}
}
intravisit::walk_qpath(self, qpath, id, span);
}
}
struct ReplacementValidator<'tcx> {
state: ValidatorState<'tcx>,
}
impl<'tcx> intravisit::Visitor<'tcx> for ReplacementValidator<'tcx> {
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
intravisit::NestedVisitorMap::All(&self.state.tcx.hir())
}
fn visit_qpath(&mut self, qpath: &'tcx hir::QPath, id: hir::HirId, span: Span) {
if let Some(hir_id) = hir_id_from_path(qpath) {
if self.state.placeholders.contains(&hir_id)
&& !self.state.bound_placeholders.contains(&hir_id)
{
self.state.add_error(
"Placeholder used in replacement pattern, but never bound.",
span,
);
}
}
intravisit::walk_qpath(self, qpath, id, span);
}
}