Repository: facet-rs/facet (Rust)

Issue:
facet-args: using named argument short-code causes panic
## Problem

Using short code in `facet-args` causes panic, reproducible by updating the existing test to use the short code instead of the full.

```diff
diff --git i/facet-args/tests/simple.rs w/facet-args/tests/simple.rs
index aace696..54844fb 100644
--- i/facet-args/tests/simple.rs
+++ w/facet-args/tests/simple.rs
@@ -1,5 +1,4 @@
 use facet::Facet;
-use facet_pretty::FacetPretty;

 #[test]
 fn test_arg_parse() {
@@ -17,6 +16,8 @@ fn test_arg_parse() {
         concurrency: usize,
     }

-    let args: Args = facet_args::from_slice(&["--verbose", "--concurrency", "14", "example.rs"]);
-    eprintln!("args: {}", args.pretty());
+    let args: Args = facet_args::from_slice(&["--verbose", "-j", "14", "example.rs"]);
+    assert!(args.verbose);
+    assert_eq!(args.path, "example.rs");
+    assert_eq!(args.concurrency, 14);
 }
```

## Error

```rust
Message:  called `Result::unwrap()` on an `Err` value: 
OperationFailed { shape: Shape { id: TypeId(0xacd3f9ff7a59392a1cacd4ab2322738a), 
layout: Sized(Layout { size: 24, align: 8 (1 << 3) }), vtable: ValueVTable { type_name: 0x102237a30, 
marker_traits: MarkerTraits(EQ | SEND | SYNC | UNPIN), 
drop_in_place: Some(0x102237cf0), invariants: None, display: Some(0x1022376d0), 
debug: Some(0x102237ca0), 
default_in_place: Some(0x102237800), 
clone_into: Some(0x102237760), eq: Some(0x102237a80), 
partial_ord: Some(0x102237930), ord: Some(0x102237b70), 
hash: Some(0x102237980), parse: Some(0x102237eb0), 
try_from: None, try_into_inner: None, try_borrow_inner: None }, 
def: Scalar(ScalarDef { affinity: String(StringAffinity { max_inline_length: Some(0) }) }), type_params: [], doc: [], attributes: [], inner: None }, 
operation: "was supposed to mark a field as initialized, but the parent frame already had it marked as initialized" }
```


## Notes

Seems like the short codes are not parsed in the current code only `--` and positional:
https://github.com/facet-rs/facet/blob/main/facet-args/src/lib.rs#L50-L67
Doing a basic copy of the `--` parsing code to parse `-` results in an error because the lookup code can't find a field by the short name.
I can't find anything in `facet-reflect` about short specifically, so I assume it's actually a `FieldAttribute::Arbitrary`, so when finding a short name argument it needs a different way to search for the relevant field by checking for existence of a arbitrary field named `short` with value of the short name we need to find.


Discussion:

Patch:
diff --git a/facet-args/README.md b/facet-args/README.md
index 003feb990..870126ad2 100644
--- a/facet-args/README.md
+++ b/facet-args/README.md
@@ -63,7 +63,7 @@ struct Args {
 }
 
 # fn main() {
-let args: Args = facet_args::from_slice(&["--verbose", "--concurrency", "14", "example.rs"]);
+let args: Args = facet_args::from_slice(&["--verbose", "-j", "14", "example.rs"]);
 eprintln!("args: {}", args.pretty());
 # }
 ```
diff --git a/facet-args/README.md.in b/facet-args/README.md.in
index 1cb1bcdc7..096dc70ad 100644
--- a/facet-args/README.md.in
+++ b/facet-args/README.md.in
@@ -17,7 +17,7 @@ struct Args {
 }
 
 # fn main() {
-let args: Args = facet_args::from_slice(&["--verbose", "--concurrency", "14", "example.rs"]);
+let args: Args = facet_args::from_slice(&["--verbose", "-j", "14", "example.rs"]);
 eprintln!("args: {}", args.pretty());
 # }
 ```
diff --git a/facet-args/src/lib.rs b/facet-args/src/lib.rs
index 7a5d94a9e..67e06642a 100644
--- a/facet-args/src/lib.rs
+++ b/facet-args/src/lib.rs
@@ -63,6 +63,30 @@ where
                 s = &s[1..];
                 wip = parse_field(field, value).unwrap();
             }
+        } else if let Some(key) = token.strip_prefix("-") {
+            log::trace!("Found short named argument: {}", key);
+            let Def::Struct(sd) = wip.shape().def else {
+                panic!("Expected struct definition");
+            };
+            for (field_index, f) in sd.fields.iter().enumerate() {
+                if f.attributes
+                    .iter()
+                    .any(|a| matches!(a, FieldAttribute::Arbitrary(a) if a.contains("short") && a.contains(key))
+                   )
+                {
+                    log::trace!("Found field matching short_code: {} for field {}", key, f.name);
+                    let field = wip.field(field_index).unwrap();
+                    if field.shape().is_type::<bool>() {
+                        wip = parse_field(field, "true").unwrap();
+                    } else {
+                        let value = s.first().expect("expected value after argument");
+                        log::trace!("Field value: {}", value);
+                        s = &s[1..];
+                        wip = parse_field(field, value).unwrap();
+                    }
+                    break;
+                }
+            }
         } else {
             log::trace!("Encountered positional argument: {}", token);
             let Def::Struct(sd) = wip.shape().def else {


Test patch:
diff --git a/facet-args/tests/simple.rs b/facet-args/tests/simple.rs
index aace6961e..54844fb87 100644
--- a/facet-args/tests/simple.rs
+++ b/facet-args/tests/simple.rs
@@ -1,5 +1,4 @@
 use facet::Facet;
-use facet_pretty::FacetPretty;
 
 #[test]
 fn test_arg_parse() {
@@ -17,6 +16,8 @@ fn test_arg_parse() {
         concurrency: usize,
     }
 
-    let args: Args = facet_args::from_slice(&["--verbose", "--concurrency", "14", "example.rs"]);
-    eprintln!("args: {}", args.pretty());
+    let args: Args = facet_args::from_slice(&["--verbose", "-j", "14", "example.rs"]);
+    assert!(args.verbose);
+    assert_eq!(args.path, "example.rs");
+    assert_eq!(args.concurrency, 14);
 }