// Copyright (c) 2016-2020 Fabian Schuiki //! A pairing of call arguments and the arguments of the called function. //! //! This module takes a list of arguments passed to a function or task call, and //! matches them against the argument list of the function or task declaration. use crate::crate_prelude::*; use crate::func_args::{FuncArg, FuncArgList}; use std::{collections::HashMap, sync::Arc}; /// A mapping of call arguments to function/task declaration arguments. #[derive(Debug)] pub struct CallMapping<'a> { /// The span of the call. pub span: Span, /// The mapping from declaration arguments to call arguments. pub args: Vec>, /// Set if the mapping is incomplete due to an error. error: bool, } impl<'a> CallMapping<'a> { /// Create a new tombstone mapping. pub fn error(span: Span) -> Self { Self { span, args: Default::default(), error: true, } } /// Is this a tombstone? pub fn is_error(&self) -> bool { self.error } } /// A mapping of a single call argument to a function/task declaration argument. #[derive(Debug, Clone, Copy)] pub struct CallArgMapping<'a> { /// A representative span of this argument. Either at the call site, or if /// omitted, the default at the declaration site. pub span: Span, /// The argument in the function/task declaration. pub decl: &'a FuncArg<'a>, /// The source of the argument in the call. pub call: CallArgSource<'a>, /// The expression ultimately assigned. This is either the expression in the /// call argument, or the default expression in the declaration. pub expr: &'a ast::Expr<'a>, /// The parameter environment to evaluate the call argument. pub env: ParamEnv, } /// Whether an argument is explicitly provided by the call, or uses the default. #[derive(Debug, Clone, Copy)] pub enum CallArgSource<'a> { /// Argument explicitly provided in the call. Call(&'a ast::CallArg<'a>), /// Argument implicitly set to the default provided in the declaration. Default(&'a ast::Expr<'a>), } impl Eq for CallArgSource<'_> {} impl PartialEq for CallArgSource<'_> { fn eq(&self, other: &Self) -> bool { match (*self, *other) { (Self::Call(a), Self::Call(b)) => std::ptr::eq(a, b), (Self::Default(a), Self::Default(b)) => std::ptr::eq(a, b), _ => false, } } } impl std::hash::Hash for CallArgSource<'_> { fn hash(&self, h: &mut H) { match *self { Self::Call(x) => std::ptr::hash(x, h), Self::Default(x) => std::ptr::hash(x, h), } } } /// Map the arguments of a call to the arguments of the callee declaration. #[moore_derive::query] pub(crate) fn call_mapping<'a>( cx: &impl Context<'a>, Ref(decl_args): Ref<'a, FuncArgList<'a>>, Ref(call_args): Ref<'a, [ast::CallArg<'a>]>, call_span: Span, ) -> Arc> { debug!("Computing argument mapping"); // Ensure that the call does not have more arguments than the declaration. if call_args.len() > decl_args.args.len() { cx.emit( DiagBuilder2::error(format!( "argument mismatch: {} only has {} arguments, but {} provided", decl_args.func, decl_args.args.len(), call_args.len(), )) .span(call_span), ); return Arc::new(CallMapping::error(call_span)); } // Build a lookup table of declaration argument names. let decl_args_by_name: HashMap<_, _> = decl_args .args .iter() .flat_map(|a| a.name.map(|n| (n.value, a))) .collect(); trace!("Declaration args lookup: {:?}", decl_args_by_name); // Process call arguments and match them. let mut seen_named = false; let mut failed = false; let mut partial_mapping = HashMap::, &ast::CallArg>::new(); for (decl_arg, call_arg) in decl_args.args.iter().zip(call_args.iter()) { trace!("Matching up `{}`", call_arg.span().extract()); // Match let matched_decl_arg = match call_arg.name { Some(call_name) => { seen_named = true; if let Some(decl_arg) = decl_args_by_name.get(&call_name.value).copied() { decl_arg } else { cx.emit( DiagBuilder2::error(format!("unknown argument: `{}`", call_name)) .span(call_name.span) .add_note(format!( "Subroutine `{}` was declared here:", decl_args.func.prototype.name )) .span(decl_args.func.span()), ); failed = true; continue; } } None if seen_named => { cx.emit( DiagBuilder2::error("positional argument after named") .span(call_arg.span()) .add_note( "IEEE 1800-2017 requires all positional arguments to appear before \ named arguments.", ), ); failed = true; continue; } None => decl_arg, }; if let Some(previous) = partial_mapping.get(&Ref(matched_decl_arg)) { cx.emit( DiagBuilder2::error(format!( "argument assigned multiple times: `{}`", matched_decl_arg .name .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()) )) .span(call_arg.span()) .add_note("Previous assignment was here:") .span(previous.span()), ); continue; } else { partial_mapping.insert(Ref(matched_decl_arg), call_arg); } } trace!("Mapping before assigning defaults: {:#?}", partial_mapping); // Abort here if we had any failures thus far. if failed { return Arc::new(CallMapping::error(call_span)); } // Make a canonical with one entry for each declaration argument, and // populate default values. let mut mapping = vec![]; for decl_arg in &decl_args.args { // Establish the assigned value, either as explicitly provided in the // call, or from the default value in the declaration. let src = match partial_mapping.get(&Ref(decl_arg)) { Some(call_arg) => match &call_arg.expr { Some(expr) => Some((expr, call_arg)), None => None, }, None => None, }; let span = match src { Some((_, call_arg)) => call_arg.span(), None => decl_arg.span, }; let (expr, src) = match src { Some((expr, call_arg)) => (expr, CallArgSource::Call(call_arg)), None => match decl_arg.default { Some(default) => (default, CallArgSource::Default(default)), None => { cx.emit( DiagBuilder2::error(format!( "argument without default: `{}` must be passed a value", decl_arg .name .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()) )) .span(call_span) .add_note("Argument was declared here:") .span(decl_arg.span), ); failed = true; continue; } }, }; // Assemble the data struct. let arg = CallArgMapping { span, decl: decl_arg, call: src, expr, env: cx.default_param_env(), // TODO(fschuiki): this needs to change }; mapping.push(arg); } // Assemble the final struct. Arc::new(CallMapping { span: call_span, args: mapping, error: failed, }) } /// A visitor that emits diagnostics for the mapping of a call's arguments to /// the callee's argument list. pub struct CallMappingVerbosityVisitor<'cx, C> { cx: &'cx C, } impl<'cx, C> CallMappingVerbosityVisitor<'cx, C> { /// Create a new visitor. pub fn new(cx: &'cx C) -> Self { CallMappingVerbosityVisitor { cx } } } impl<'a, 'cx, C> ast::Visitor<'a> for CallMappingVerbosityVisitor<'cx, C> where C: Context<'a>, 'a: 'cx, { fn pre_visit_expr(&mut self, node: &'a ast::Expr<'a>) -> bool { // We're only interested in function calls. Get the call target and // arguments. let (target, args) = match self.cx.hir_of_expr(Ref(node)) { Ok(hir::Expr { kind: hir::ExprKind::FunctionCall(target, args), .. }) => (target, args), _ => return true, }; // Canonicalize the target's function arguments and establish the // mapping to the call arguments. let decl_args = self.cx.canonicalize_func_args(Ref(target)); let mapping = self.cx.call_mapping(Ref(decl_args), Ref(args), node.span()); if mapping.is_error() { return true; } let mut d = DiagBuilder2::note("call argument mapping") .span(node.span()) .add_note(format!( "Call to subroutine `{}` has the following argument mapping:", target.prototype.name )); for m in &mapping.args { let name = match m.decl.name { Some(name) => format!(" {}", name), None => format!(""), }; let ty = self.cx.unpacked_type_from_ast( Ref(m.decl.ty), Ref(m.decl.unpacked_dims), m.env, None, ); let value = format!( "{} {}", m.expr.span().extract(), match m.call { CallArgSource::Call(..) => "(call)", CallArgSource::Default(..) => "(default)", } ); d = d.add_note(format!("{} {}{} = {}", m.decl.dir, ty, name, value)); } self.cx.emit(d); true } }