Skip to content

Commit 8a7319a

Browse files
committed
Fix clippy lints; return errors rather than panic
This cleans up clippy lints, both in the library itself, and in the generated code. As part of this change, it changes a few locations where we would have panicked on values passed in by the user to instead return an error. However, I left one place that unwraps in the generated code. After we have parsed all the arguments, we check that all of the required arguments. If not, we return an error. It's unfortunately a little difficult to prove to rust that we've handled all the arguments. I left the unwrap()s in place in case there's a logic error we have in handling this, and annotated the code with `#[allow(clippy::unwrap_in_result)]` to allow for this case. Closes #114
1 parent f1f85d2 commit 8a7319a

File tree

6 files changed

+90
-69
lines changed

6 files changed

+90
-69
lines changed

.github/workflows/rust.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ jobs:
1515
run: cargo build --verbose
1616
- name: Run tests
1717
run: cargo test --verbose
18+
- name: Run clippy
19+
run: cargo clippy -- -D warnings

argh/src/lib.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
//! pilot_nickname: Option<String>,
3232
//! }
3333
//!
34-
//! fn main() {
35-
//! let up: GoUp = argh::from_env();
36-
//! }
34+
//! let up: GoUp = argh::from_env();
3735
//! ```
3836
//!
3937
//! `./some_bin --help` will then output the following:
@@ -481,8 +479,8 @@ impl From<String> for EarlyExit {
481479
}
482480

483481
/// Extract the base cmd from a path
484-
fn cmd<'a>(default: &'a String, path: &'a String) -> &'a str {
485-
std::path::Path::new(path).file_name().map(|s| s.to_str()).flatten().unwrap_or(default.as_str())
482+
fn cmd<'a>(default: &'a str, path: &'a str) -> &'a str {
483+
std::path::Path::new(path).file_name().map(|s| s.to_str()).flatten().unwrap_or(default)
486484
}
487485

488486
/// Create a `FromArgs` type from the current process's `env::args`.
@@ -690,7 +688,7 @@ pub fn parse_struct_args(
690688
continue;
691689
}
692690

693-
if next_arg.starts_with("-") && !options_ended {
691+
if next_arg.starts_with('-') && !options_ended {
694692
if next_arg == "--" {
695693
options_ended = true;
696694
continue;
@@ -877,7 +875,7 @@ impl<'a> ParseStructSubCommand<'a> {
877875
}
878876
}
879877

880-
return Ok(false);
878+
Ok(false)
881879
}
882880
}
883881

@@ -951,7 +949,7 @@ impl MissingRequirements {
951949

952950
if !self.options.is_empty() {
953951
if !self.positional_args.is_empty() {
954-
output.push_str("\n");
952+
output.push('\n');
955953
}
956954
output.push_str("Required options not provided:");
957955
for option in &self.options {
@@ -962,7 +960,7 @@ impl MissingRequirements {
962960

963961
if let Some(missing_subcommands) = self.subcommands {
964962
if !self.options.is_empty() {
965-
output.push_str("\n");
963+
output.push('\n');
966964
}
967965
output.push_str("One of the following subcommands must be present:");
968966
output.push_str(NEWLINE_INDENT);

argh/tests/lib.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
#![cfg(test)]
21
// Copyright (c) 2020 Google LLC All rights reserved.
32
// Use of this source code is governed by a BSD-style
43
// license that can be found in the LICENSE file.
54

5+
// Deny a bunch of uncommon clippy lints to make sure the generated code won't trigger a warning.
6+
#![deny(
7+
clippy::indexing_slicing,
8+
clippy::panic_in_result_fn,
9+
clippy::str_to_string,
10+
clippy::unreachable,
11+
clippy::unwrap_in_result
12+
)]
13+
614
use {argh::FromArgs, std::fmt::Debug};
715

816
#[test]
@@ -154,7 +162,7 @@ fn default_number() {
154162
fn default_function() {
155163
const MSG: &str = "hey I just met you";
156164
fn call_me_maybe() -> String {
157-
MSG.to_string()
165+
MSG.to_owned()
158166
}
159167

160168
#[derive(FromArgs)]
@@ -821,7 +829,7 @@ Options:
821829
help_example,
822830
HelpExample {
823831
force: true,
824-
scribble: "fooey".to_string(),
832+
scribble: "fooey".to_owned(),
825833
really_really_really_long_name_for_pat: false,
826834
verbose: false,
827835
command: HelpExampleSubCommands::BlowUp(BlowUp { safely: true }),
@@ -1264,7 +1272,7 @@ Options:
12641272
-n, --n fooey
12651273
--help display usage information
12661274
"###
1267-
.to_string(),
1275+
.to_owned(),
12681276
status: Ok(()),
12691277
}),
12701278
);
@@ -1283,7 +1291,7 @@ fn redact_arg_values_produces_errors_with_bad_arguments() {
12831291
assert_eq!(
12841292
Cmd::redact_arg_values(&["program-name"], &["--n"]),
12851293
Err(argh::EarlyExit {
1286-
output: "No value provided for option '--n'.\n".to_string(),
1294+
output: "No value provided for option '--n'.\n".to_owned(),
12871295
status: Err(()),
12881296
}),
12891297
);

argh_derive/src/help.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub(crate) fn help(
9696

9797
lits_section(&mut format_lit, "Notes:", &ty_attrs.notes);
9898

99-
if ty_attrs.error_codes.len() != 0 {
99+
if !ty_attrs.error_codes.is_empty() {
100100
format_lit.push_str(SECTION_SEPARATOR);
101101
format_lit.push_str("Error codes:");
102102
for (code, text) in &ty_attrs.error_codes {
@@ -106,7 +106,7 @@ pub(crate) fn help(
106106
}
107107
}
108108

109-
format_lit.push_str("\n");
109+
format_lit.push('\n');
110110

111111
quote! { {
112112
#subcommand_calculation
@@ -116,7 +116,7 @@ pub(crate) fn help(
116116

117117
/// A section composed of exactly just the literals provided to the program.
118118
fn lits_section(out: &mut String, heading: &str, lits: &[syn::LitStr]) {
119-
if lits.len() != 0 {
119+
if !lits.is_empty() {
120120
out.push_str(SECTION_SEPARATOR);
121121
out.push_str(heading);
122122
for lit in lits {

argh_derive/src/lib.rs

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub fn argh_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
3535
/// as well as all errors that occurred.
3636
fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {
3737
let errors = &Errors::default();
38-
if input.generics.params.len() != 0 {
38+
if !input.generics.params.is_empty() {
3939
errors.err(
4040
&input.generics,
4141
"`#![derive(FromArgs)]` cannot be applied to types with generic parameters",
@@ -65,22 +65,15 @@ enum Optionality {
6565
impl PartialEq<Optionality> for Optionality {
6666
fn eq(&self, other: &Optionality) -> bool {
6767
use Optionality::*;
68-
match (self, other) {
69-
(None, None) | (Optional, Optional) | (Repeating, Repeating) => true,
70-
// NB: (Defaulted, Defaulted) can't contain the same token streams
71-
_ => false,
72-
}
68+
// NB: (Defaulted, Defaulted) can't contain the same token streams
69+
matches!((self, other), (Optional, Optional) | (Repeating, Repeating))
7370
}
7471
}
7572

7673
impl Optionality {
7774
/// Whether or not this is `Optionality::None`
7875
fn is_required(&self) -> bool {
79-
if let Optionality::None = self {
80-
true
81-
} else {
82-
false
83-
}
76+
matches!(self, Optionality::None)
8477
}
8578
}
8679

@@ -252,6 +245,7 @@ fn impl_from_args_struct(
252245
let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs);
253246

254247
let trait_impl = quote_spanned! { impl_span =>
248+
#[automatically_derived]
255249
impl argh::FromArgs for #name {
256250
#from_args_method
257251

@@ -269,8 +263,8 @@ fn impl_from_args_struct_from_args<'a>(
269263
type_attrs: &TypeAttrs,
270264
fields: &'a [StructField<'a>],
271265
) -> TokenStream {
272-
let init_fields = declare_local_storage_for_from_args_fields(&fields);
273-
let unwrap_fields = unwrap_from_args_fields(&fields);
266+
let init_fields = declare_local_storage_for_from_args_fields(fields);
267+
let unwrap_fields = unwrap_from_args_fields(fields);
274268
let positional_fields: Vec<&StructField<'_>> =
275269
fields.iter().filter(|field| field.kind == FieldKind::Positional).collect();
276270
let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident);
@@ -289,13 +283,13 @@ fn impl_from_args_struct_from_args<'a>(
289283
}
290284
});
291285

292-
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(&fields);
286+
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
293287

294288
let mut subcommands_iter =
295289
fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse();
296290

297291
let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
298-
while let Some(dup_subcommand) = subcommands_iter.next() {
292+
for dup_subcommand in subcommands_iter {
299293
errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field);
300294
}
301295

@@ -304,7 +298,7 @@ fn impl_from_args_struct_from_args<'a>(
304298
let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
305299

306300
let append_missing_requirements =
307-
append_missing_requirements(&missing_requirements_ident, &fields);
301+
append_missing_requirements(&missing_requirements_ident, fields);
308302

309303
let parse_subcommands = if let Some(subcommand) = subcommand {
310304
let name = subcommand.name;
@@ -324,12 +318,14 @@ fn impl_from_args_struct_from_args<'a>(
324318

325319
// Identifier referring to a value containing the name of the current command as an `&[&str]`.
326320
let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
327-
let help = help::help(errors, cmd_name_str_array_ident, type_attrs, &fields, subcommand);
321+
let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand);
328322

329323
let method_impl = quote_spanned! { impl_span =>
330324
fn from_args(__cmd_name: &[&str], __args: &[&str])
331325
-> std::result::Result<Self, argh::EarlyExit>
332326
{
327+
#![allow(clippy::unwrap_in_result)]
328+
333329
#( #init_fields )*
334330

335331
argh::parse_struct_args(
@@ -374,8 +370,8 @@ fn impl_from_args_struct_redact_arg_values<'a>(
374370
type_attrs: &TypeAttrs,
375371
fields: &'a [StructField<'a>],
376372
) -> TokenStream {
377-
let init_fields = declare_local_storage_for_redacted_fields(&fields);
378-
let unwrap_fields = unwrap_redacted_fields(&fields);
373+
let init_fields = declare_local_storage_for_redacted_fields(fields);
374+
let unwrap_fields = unwrap_redacted_fields(fields);
379375

380376
let positional_fields: Vec<&StructField<'_>> =
381377
fields.iter().filter(|field| field.kind == FieldKind::Positional).collect();
@@ -395,13 +391,13 @@ fn impl_from_args_struct_redact_arg_values<'a>(
395391
}
396392
});
397393

398-
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(&fields);
394+
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
399395

400396
let mut subcommands_iter =
401397
fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse();
402398

403399
let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
404-
while let Some(dup_subcommand) = subcommands_iter.next() {
400+
for dup_subcommand in subcommands_iter {
405401
errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field);
406402
}
407403

@@ -410,7 +406,7 @@ fn impl_from_args_struct_redact_arg_values<'a>(
410406
let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
411407

412408
let append_missing_requirements =
413-
append_missing_requirements(&missing_requirements_ident, &fields);
409+
append_missing_requirements(&missing_requirements_ident, fields);
414410

415411
let redact_subcommands = if let Some(subcommand) = subcommand {
416412
let name = subcommand.name;
@@ -428,15 +424,15 @@ fn impl_from_args_struct_redact_arg_values<'a>(
428424
quote_spanned! { impl_span => None }
429425
};
430426

431-
let cmd_name = if type_attrs.is_subcommand.is_none() {
432-
quote! { __cmd_name.last().expect("no command name").to_string() }
427+
let unwrap_cmd_name_err_string = if type_attrs.is_subcommand.is_none() {
428+
quote! { "no command name" }
433429
} else {
434-
quote! { __cmd_name.last().expect("no subcommand name").to_string() }
430+
quote! { "no subcommand name" }
435431
};
436432

437433
// Identifier referring to a value containing the name of the current command as an `&[&str]`.
438434
let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
439-
let help = help::help(errors, cmd_name_str_array_ident, type_attrs, &fields, subcommand);
435+
let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand);
440436

441437
let method_impl = quote_spanned! { impl_span =>
442438
fn redact_arg_values(__cmd_name: &[&str], __args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
@@ -471,7 +467,11 @@ fn impl_from_args_struct_redact_arg_values<'a>(
471467
#missing_requirements_ident.err_on_any()?;
472468

473469
let mut __redacted = vec![
474-
#cmd_name,
470+
if let Some(cmd_name) = __cmd_name.last() {
471+
(*cmd_name).to_owned()
472+
} else {
473+
return Err(argh::EarlyExit::from(#unwrap_cmd_name_err_string.to_owned()));
474+
}
475475
];
476476

477477
#( #unwrap_fields )*
@@ -510,6 +510,7 @@ fn top_or_sub_cmd_impl(errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttr
510510
if type_attrs.is_subcommand.is_none() {
511511
// Not a subcommand
512512
quote! {
513+
#[automatically_derived]
513514
impl argh::TopLevelCommand for #name {}
514515
}
515516
} else {
@@ -519,6 +520,7 @@ fn top_or_sub_cmd_impl(errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttr
519520
&empty_str
520521
});
521522
quote! {
523+
#[automatically_derived]
522524
impl argh::SubCommand for #name {
523525
const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo {
524526
name: #subcommand_name,
@@ -586,7 +588,9 @@ fn unwrap_from_args_fields<'a>(
586588
let field_name = field.name;
587589
match field.kind {
588590
FieldKind::Option | FieldKind::Positional => match &field.optionality {
589-
Optionality::None => quote! { #field_name: #field_name.slot.unwrap() },
591+
Optionality::None => quote! {
592+
#field_name: #field_name.slot.unwrap()
593+
},
590594
Optionality::Optional | Optionality::Repeating => {
591595
quote! { #field_name: #field_name.slot }
592596
}
@@ -639,7 +643,7 @@ fn declare_local_storage_for_redacted_fields<'a>(
639643
let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> =
640644
argh::ParseValueSlotTy {
641645
slot: std::default::Default::default(),
642-
parse_func: |arg, _| { Ok(arg.to_string()) },
646+
parse_func: |arg, _| { Ok(arg.to_owned()) },
643647
};
644648
}
645649
}
@@ -658,7 +662,7 @@ fn declare_local_storage_for_redacted_fields<'a>(
658662
let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> =
659663
argh::ParseValueSlotTy {
660664
slot: std::default::Default::default(),
661-
parse_func: |_, _| { Ok(#arg_name.to_string()) },
665+
parse_func: |_, _| { Ok(#arg_name.to_owned()) },
662666
};
663667
}
664668
}
@@ -861,25 +865,37 @@ fn impl_from_args_enum(
861865
fn from_args(command_name: &[&str], args: &[&str])
862866
-> std::result::Result<Self, argh::EarlyExit>
863867
{
864-
let subcommand_name = *command_name.last().expect("no subcommand name");
868+
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
869+
*subcommand_name
870+
} else {
871+
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
872+
};
873+
865874
#(
866875
if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
867876
return Ok(#name_repeating::#variant_names(
868877
<#variant_ty as argh::FromArgs>::from_args(command_name, args)?
869878
));
870879
}
871880
)*
872-
unreachable!("no subcommand matched")
881+
882+
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
873883
}
874884

875885
fn redact_arg_values(command_name: &[&str], args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
876-
let subcommand_name = *command_name.last().expect("no subcommand name");
886+
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
887+
*subcommand_name
888+
} else {
889+
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
890+
};
891+
877892
#(
878893
if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
879894
return <#variant_ty as argh::FromArgs>::redact_arg_values(command_name, args);
880895
}
881896
)*
882-
unreachable!("no subcommand matched")
897+
898+
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
883899
}
884900
}
885901

0 commit comments

Comments
 (0)