-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change HashMap
to export mapped types
#339
Conversation
Interesting! How sure are we that
There definitely are some semantic differences. Here are a couple I have found: type A = { [key: string]: number };
let a: A = { "x": 42 };
let x: number = a["x"]; // ok
type B = { [key in string]?: number };
let b: B = { "y": 42 };
let y: number = b["y"]; // error type A = { [key: string]: number };
let a: A = { "x": undefined }; // error
type B = { [key in string]?: number };
let b: B = { "y": undefined }; // ok For just That does mean that this change is breaking, correct? Do we want to treat it as such, or is this more of a bug fix? |
I think this behavior is better because a
This is annoying, and I don't know if there's a way to implement the feature without this behavior.
Yeah, this does look like a breaking change, it'll make TypeScript demand a whole lot of null checks when dealing with |
This breaks iterating over object values: //before
type A = {[key: string]: number};
let a: A = {"a": 1, "b": 2, "c": 3};
//el is a `number` below
let sum = Object.values(a).reduce((acc, el) => acc + el, 0);
//after
type A = {[key: string]?: number};
let a: A = {"a": 1, "b": 2, "c": 3};
//el is a `number|undefined` below
let sum = Object.values(a).reduce((acc, el) => acc + el, 0); This does make sense that record types will return undefined on keys that aren't in them, but I think it's better handled by setting |
Interesting! I do still think that the new representation is better overall, though I'd love to be convinced otherwise. For one, I think we should chose the most sensible bindings with the default tsc config. I'd also err on the side of having false-positive tsc errors instead of false-negatives. |
You can give type A = { [key in string]?: number }
const a: A = { a: 1, b: 2, c: 3 }
Object.values(a).reduce<number>((acc, el = 0) => acc + el, 0) |
Yeah, this trivial case is somewhat easy to work around, but if Is it possible to detect that the key type is enumerable and only output the enum Keys {
A,
B,
C,
}
struct A<T> {
foo: HashMap<Keys, T>
} type A<T> = {
foo: {
"A"?: T,
"B"?: T,
"C"?: T,
}
} This is essentially doing the type mapping by hand. I think this TS bug is related, and if fixed setting |
I honestly don't think that emitting Since let foo: {[key: string]: number} = {};
let bar: number = foo["bar"]; works, I believe this to be an issue much more with how TypeScript doesn't handle the possibility of missing keys (which is only one of many type safety issues in TypeScript), instead of an issue with the type that I understand the rationale (as much as I personally don't agree with trying to fix safety issues in TypeScripts type system in this crate), but I do believe that especially where Rust is expecting inputs from TypeScript (as As is, it is impossible (without implementing the |
Thanks, this is valuable feedback. Right now, the most ergonomic way to alter the representation of #[derive(TS)]
#[ts(export)]
struct A {
#[ts(as = "TsHashMap<_>")]
a: HashMap<String, B>,
} That works, even with imports, but is not great if a codebase is littered with The `TsHashMap` helperstruct TsHashMap<M>(M);
impl<K: TS, V: TS, H> TS for TsHashMap<HashMap<K, V, H>> {
type WithoutGenerics = TsHashMap<HashMap<ts_rs::Dummy, ts_rs::Dummy, H>>;
fn ident() -> String { panic!() }
fn name() -> String { format!("{{ [key: {}]: {} }}", K::name(), V::name()) }
fn inline() -> String { format!("{{ [key: {}]: {} }}", K::inline(), V::inline()) }
fn inline_flattened() -> String { panic!("{} cannot be flattened", Self::name()) }
fn decl() -> String { panic!("{} cannot be declared", Self::name()) }
fn decl_concrete() -> String { panic!("{} cannot be declared", Self::name()) }
fn visit_dependencies(v: &mut impl ts_rs::TypeVisitor) where Self: 'static {
<HashMap<K, V, H>>::visit_dependencies(v);
}
fn visit_generics(v: &mut impl ts_rs::TypeVisitor) where Self: 'static {
<HashMap<K, V, H>>::visit_generics(v);
}
} |
The main reason to have the optional key is for cases where the key is an enum (aka a union of strings), which would make every entry mandatory in TS, causing a lot of problems. I also personally think this nicely reflects Rust's
You are correct in that they are different, so to prevent code that has
This error occurs because this is not valid JSON. JSON doesn't support |
Goal
Allow enums to be used as
HashMap
keysCloses #338
Changes
Change
HashMap
to export{ [key in K]?: V }
instead of{ [key: K]: V }
Checklist