Skip to content

Commit d008168

Browse files
authored
fix: prevented to extend lifetime of references to Cmd's attributes (#5)
1 parent ccd2054 commit d008168

11 files changed

+137
-4
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ categories = ["command-line interface"]
1515
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1616

1717
[dependencies]
18+
19+
[dev-dependencies]
20+
trybuild = "1.0"

src/lib.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,19 @@ impl<'a> Cmd<'a> {
147147
}
148148
}
149149

150-
pub fn name(&self) -> &'a str {
150+
pub fn name(&'a self) -> &'a str {
151151
self.name
152152
}
153153

154-
pub fn args(&self) -> &[&'a str] {
154+
pub fn args(&'a self) -> &'a [&'a str] {
155155
&self.args
156156
}
157157

158158
pub fn has_opt(&self, name: &str) -> bool {
159159
self.opts.contains_key(name)
160160
}
161161

162-
pub fn opt_arg(&self, name: &str) -> Option<&str> {
162+
pub fn opt_arg(&'a self, name: &str) -> Option<&'a str> {
163163
if let Some(opt_vec) = self.opts.get(name) {
164164
if opt_vec.len() > 0 {
165165
return Some(opt_vec[0]);
@@ -168,7 +168,7 @@ impl<'a> Cmd<'a> {
168168
None
169169
}
170170

171-
pub fn opt_args(&self, name: &str) -> Option<&[&'a str]> {
171+
pub fn opt_args(&'a self, name: &str) -> Option<&'a [&'a str]> {
172172
match self.opts.get(name) {
173173
Some(vec) => Some(&vec),
174174
None => None,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use cliargs::Cmd;
2+
3+
fn main() {
4+
fn returns_one_of_cmd_args() -> &'static str {
5+
let mut cmd = cliargs::Cmd::with_strings(["/path/to/app".to_string(), "foo".to_string()]);
6+
cmd.parse().unwrap();
7+
8+
let args = cmd.args();
9+
let arg1 = args[0];
10+
println!("command args (within the scope = {arg1:?}");
11+
12+
arg1
13+
}
14+
15+
let arg1 = returns_one_of_cmd_args();
16+
println!("command args (out of the scope) = {arg1:?}");
17+
}
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
warning: unused import: `cliargs::Cmd`
2+
--> tests/compile_errors/lifetime_of_cmd_args.rs:1:5
3+
|
4+
1 | use cliargs::Cmd;
5+
| ^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unused_imports)]` on by default
8+
9+
error[E0515]: cannot return value referencing local variable `cmd`
10+
--> tests/compile_errors/lifetime_of_cmd_args.rs:12:9
11+
|
12+
8 | let args = cmd.args();
13+
| --- `cmd` is borrowed here
14+
...
15+
12 | arg1
16+
| ^^^^ returns a value referencing data owned by the current function
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use cliargs::Cmd;
2+
3+
fn main() {
4+
fn returns_cmd_name() -> &'static str {
5+
let mut cmd = Cmd::with_strings(["/path/to/app".to_string()]);
6+
cmd.parse().unwrap();
7+
8+
let name = cmd.name();
9+
println!("command name (within the scope = {name}");
10+
11+
name
12+
}
13+
14+
let name = returns_cmd_name();
15+
println!("command name (out of the scope) = {name}");
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error[E0515]: cannot return value referencing local variable `cmd`
2+
--> tests/compile_errors/lifetime_of_cmd_name.rs:11:9
3+
|
4+
8 | let name = cmd.name();
5+
| --- `cmd` is borrowed here
6+
...
7+
11 | name
8+
| ^^^^ returns a value referencing data owned by the current function
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use cliargs::Cmd;
2+
3+
fn main() {
4+
fn returns_opt_arg() -> &'static str {
5+
let mut cmd =
6+
cliargs::Cmd::with_strings(["/path/to/app".to_string(), "--foo=bar".to_string()]);
7+
cmd.parse().unwrap();
8+
9+
let opt_arg = cmd.opt_arg("foo").unwrap();
10+
println!("option arg (within the scope = {opt_arg:?}");
11+
12+
opt_arg
13+
}
14+
15+
let opt_arg = returns_opt_arg();
16+
println!("option arg (out of the scope) = {opt_arg:?}");
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
warning: unused import: `cliargs::Cmd`
2+
--> tests/compile_errors/lifetime_of_cmd_opt_arg.rs:1:5
3+
|
4+
1 | use cliargs::Cmd;
5+
| ^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unused_imports)]` on by default
8+
9+
error[E0515]: cannot return value referencing local variable `cmd`
10+
--> tests/compile_errors/lifetime_of_cmd_opt_arg.rs:12:9
11+
|
12+
9 | let opt_arg = cmd.opt_arg("foo").unwrap();
13+
| --- `cmd` is borrowed here
14+
...
15+
12 | opt_arg
16+
| ^^^^^^^ returns a value referencing data owned by the current function
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use cliargs::Cmd;
2+
3+
fn main() {
4+
fn returns_one_of_opt_args() -> &'static str {
5+
let mut cmd =
6+
cliargs::Cmd::with_strings(["/path/to/app".to_string(), "--foo=bar".to_string()]);
7+
cmd.parse().unwrap();
8+
9+
let opt_args = cmd.opt_args("foo").unwrap();
10+
println!("option arg (within the scope = {:?}", opt_args[0]);
11+
12+
opt_args[0]
13+
}
14+
15+
let opt_arg = returns_one_of_opt_args();
16+
println!("option arg (out of the scope) = {opt_arg:?}");
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
warning: unused import: `cliargs::Cmd`
2+
--> tests/compile_errors/lifetime_of_cmd_opt_args.rs:1:5
3+
|
4+
1 | use cliargs::Cmd;
5+
| ^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unused_imports)]` on by default
8+
9+
error[E0515]: cannot return value referencing local variable `cmd`
10+
--> tests/compile_errors/lifetime_of_cmd_opt_args.rs:12:9
11+
|
12+
9 | let opt_args = cmd.opt_args("foo").unwrap();
13+
| --- `cmd` is borrowed here
14+
...
15+
12 | opt_args[0]
16+
| ^^^^^^^^^^^ returns a value referencing data owned by the current function

tests/parse_test.rs

+6
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,9 @@ mod tests_of_errors {
108108
}
109109
}
110110
}
111+
112+
#[test]
113+
fn compile_error_check() {
114+
let t = trybuild::TestCases::new();
115+
t.compile_fail("tests/compile_errors/*.rs");
116+
}

0 commit comments

Comments
 (0)