api-macro: lower a whole bunch of errors

to be non fatal for better error messages, this way the user
will see the compile error, but we still generate all the
code & schema variables so that one error isn't accompanied
by all the ones resulting from not having the generated code
there at all.

Eg.

    error: description not allowed on external type
       --> src/api2/access/user.rs:472:22
        |
    472 |         description: "Get API token metadata (with config digest).",
        |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Was previously also accompanied by

    error[E0425]: cannot find value `API_METHOD_READ_TOKEN` in this scope
       --> src/api2/access/user.rs:774:11
        |
    699 | pub fn delete_token(
        | ------ similarly named constant `API_METHOD_DELETE_TOKEN` defined here
    ...
    774 |     .get(&API_METHOD_READ_TOKEN)
        |           ^^^^^^^^^^^^^^^^^^^^^ help: a constant with a similar name exists: `API_METHOD_DELETE_TOKEN`

The second error was "wrong" and came much later, needlessly
filling the screen if this happened on multiple functions.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-12-07 15:33:34 +01:00
parent 273ce60242
commit 0ad74b431e
4 changed files with 41 additions and 21 deletions

View File

@ -21,8 +21,8 @@ pub fn handle_enum(
);
}
if let Some(fmt) = attribs.get("format") {
bail!(fmt.span(), "illegal key 'format', will be autogenerated");
if let Some(fmt) = attribs.remove("format") {
error!(fmt.span(), "illegal key 'format', will be autogenerated");
}
let schema = {
@ -47,9 +47,10 @@ pub fn handle_enum(
_ => bail!(variant => "api macro does not support enums with fields"),
}
let (comment, _doc_span) = util::get_doc_comments(&variant.attrs)?;
let (mut comment, _doc_span) = util::get_doc_comments(&variant.attrs)?;
if comment.is_empty() {
bail!(variant => "enum variant needs a description");
error!(&variant => "enum variant needs a description");
comment = "<missing description>".to_string();
}
let attrs = serde::SerdeAttrib::try_from(&variant.attrs[..])?;

View File

@ -68,7 +68,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
.unwrap_or(false);
if !attribs.is_empty() {
bail!(
error!(
attribs.span(),
"unexpected api elements: {}",
util::join_debug(", ", attribs.elements.keys()),
@ -171,7 +171,7 @@ enum ParameterType<'a> {
Other(&'a syn::Type, bool, &'a Schema),
}
fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent), Error> {
fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent), syn::Error> {
// `self` types are not supported:
let pat_type = match input {
syn::FnArg::Receiver(r) => bail!(r => "methods taking a 'self' are not supported"),
@ -204,7 +204,13 @@ fn handle_function_signature(
let mut param_list = Vec::<(FieldName, ParameterType)>::new();
for input in sig.inputs.iter() {
let (pat_type, pat) = check_input_type(input)?;
let (pat_type, pat) = match check_input_type(input) {
Ok(input) => input,
Err(err) => {
crate::add_error(err);
continue;
}
};
// For any named type which exists on the function signature...
if let Some((_ident, optional, ref mut schema)) =
@ -214,10 +220,10 @@ fn handle_function_signature(
let is_option = util::infer_type(schema, &*pat_type.ty)?;
let has_default = schema.find_schema_property("default").is_some();
if !is_option && *optional && !has_default {
bail!(pat_type => "optional types need a default or be an Option<T>");
error!(pat_type => "optional types need a default or be an Option<T>");
}
if has_default && !*optional {
bail!(pat_type => "non-optional parameter cannot have a default");
error!(pat_type => "non-optional parameter cannot have a default");
}
} else {
continue;
@ -225,7 +231,10 @@ fn handle_function_signature(
}
for input in sig.inputs.iter() {
let (pat_type, pat) = check_input_type(input)?;
let (pat_type, pat) = match check_input_type(input) {
Ok(input) => input,
Err(_err) => continue, // we already produced errors above,
};
// Here's the deal: we need to distinguish between parameters we need to extract before
// calling the function, a general "Value" parameter covering all the remaining json
@ -265,24 +274,28 @@ fn handle_function_signature(
ParameterType::Other(&pat_type.ty, *optional, schema)
} else if is_api_method_type(&pat_type.ty) {
if api_method_param.is_some() {
bail!(pat_type => "multiple ApiMethod parameters found");
error!(pat_type => "multiple ApiMethod parameters found");
continue;
}
api_method_param = Some(param_list.len());
ParameterType::ApiMethod
} else if is_rpc_env_type(&pat_type.ty) {
if rpc_env_param.is_some() {
bail!(pat_type => "multiple RpcEnvironment parameters found");
error!(pat_type => "multiple RpcEnvironment parameters found");
continue;
}
rpc_env_param = Some(param_list.len());
ParameterType::RpcEnv
} else if is_value_type(&pat_type.ty) {
if value_param.is_some() {
bail!(pat_type => "multiple additional Value parameters found");
error!(pat_type => "multiple additional Value parameters found");
continue;
}
value_param = Some(param_list.len());
ParameterType::Value
} else {
bail!(&pat_ident => "unexpected parameter {:?}", pat_ident.to_string());
error!(&pat_ident => "unexpected parameter {:?}", pat_ident.to_string());
continue;
};
param_list.push((param_name, param_type));
@ -437,7 +450,13 @@ fn create_wrapper_function(
});
}
} else if optional && no_option_type {
bail!(ty => "Optional parameter without Option<T> requires a default");
// FIXME: we should not be able to reach this without having produced another
// error above already anyway?
error!(ty => "Optional parameter without Option<T> requires a default");
// we produced an error so just write something that will compile
body.extend(quote_spanned! { span =>
.unwrap_or_else(|| unreachable!())
});
}
body.extend(quote_spanned! { span => ; });
args.extend(quote_spanned! { span => #arg_name, });

View File

@ -135,7 +135,7 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<T
schema_fields.insert(field.0.as_str().to_string(), field);
}
} else {
bail!(schema.span, "structs need an object schema");
error!(schema.span, "structs need an object schema");
}
let mut new_fields: Vec<(FieldName, bool, Schema)> = Vec::new();
@ -182,7 +182,7 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<T
// now error out about all the fields not found in the struct:
if !schema_fields.is_empty() {
let bad_fields = util::join(", ", schema_fields.keys());
bail!(
error!(
schema.span,
"struct does not contain the following fields: {}",
bad_fields
@ -220,7 +220,7 @@ fn handle_regular_field(
if derived {
field_def.1 = true;
} else if !field_def.1 {
bail!(&field.ty => "non-optional Option type?");
error!(&field.ty => "non-optional Option type?");
}
}

View File

@ -143,7 +143,7 @@ impl TryFrom<&[syn::Attribute]> for ContainerAttrib {
if var.path.is_ident("rename_all") {
let rename_all = RenameAll::try_from(&var.lit)?;
if this.rename_all.is_some() && this.rename_all != Some(rename_all) {
bail!(var.lit => "multiple conflicting 'rename_all' attributes");
error!(var.lit => "multiple conflicting 'rename_all' attributes");
}
this.rename_all = Some(rename_all);
}
@ -180,11 +180,11 @@ impl TryFrom<&[syn::Attribute]> for SerdeAttrib {
syn::Lit::Str(lit) => {
let rename = FieldName::from(&lit);
if this.rename.is_some() && this.rename.as_ref() != Some(&rename) {
bail!(lit => "multiple conflicting 'rename' attributes");
error!(lit => "multiple conflicting 'rename' attributes");
}
this.rename = Some(rename);
}
_ => bail!(var.lit => "'rename' value must be a string literal"),
_ => error!(var.lit => "'rename' value must be a string literal"),
}
}
}