Skip to content
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

feat: Union the type of an optional property of a mapped type with undefined #1073

Merged
merged 2 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 74 additions & 63 deletions crates/stc_ts_file_analyzer/src/analyzer/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3129,81 +3129,92 @@ impl Analyzer<'_, '_> {
// If type of prop is equal to the type of index signature, it's
// index access.

match constraint.as_ref().map(Type::normalize) {
Some(Type::Index(Index {
ty: box Type::Array(..), ..
})) => {
if let Ok(obj) = self.env.get_global_type(span, &js_word!("Array")) {
return self.access_property(span, &obj, prop, type_mode, id_ctx, opts);
let result = (|obj| {
match constraint.as_ref().map(Type::normalize) {
Some(Type::Index(Index {
ty: box Type::Array(..), ..
})) => {
if let Ok(obj) = self.env.get_global_type(span, &js_word!("Array")) {
return self.access_property(span, &obj, prop, type_mode, id_ctx, opts);
}
}
}

Some(index @ Type::Keyword(..)) | Some(index @ Type::Param(..)) => {
// {
// [P in string]: number;
// };
if let Ok(()) = self.assign(span, &mut Default::default(), index, &prop.ty()) {
// We handle `Partial<string>` at here.
let ty = m.ty.clone().map(|v| *v).unwrap_or_else(|| Type::any(span, Default::default()));

let ty = match m.optional {
Some(TruePlusMinus::Plus) | Some(TruePlusMinus::True) => ty.union_with_undefined(span),
Some(TruePlusMinus::Minus) => self.apply_type_facts_to_type(TypeFacts::NEUndefined | TypeFacts::NENull, ty),
_ => ty,
};
return Ok(ty);
Some(index @ Type::Keyword(..)) | Some(index @ Type::Param(..)) => {
// {
// [P in string]: number;
// };
if let Ok(()) = self.assign(span, &mut Default::default(), index, &prop.ty()) {
// We handle `Partial<string>` at here.
let ty = m.ty.clone().map(|v| *v).unwrap_or_else(|| Type::any(span, Default::default()));

let ty = match m.optional {
Some(TruePlusMinus::Plus) | Some(TruePlusMinus::True) => ty.union_with_undefined(span),
Some(TruePlusMinus::Minus) => {
self.apply_type_facts_to_type(TypeFacts::NEUndefined | TypeFacts::NENull, ty)
}
_ => ty,
};
return Ok(ty);
}
}
}

_ => {}
}

let expanded = self
.expand_mapped(span, m)
.context("tried to expand a mapped type to access property")?;

if let Some(obj) = &expanded {
return self.access_property(span, obj, prop, type_mode, id_ctx, opts);
}
_ => {}
}

if let Some(Type::Index(Index { ty, .. })) = constraint.as_ref().map(Type::normalize) {
// Check if we can index the object with given key.
if let Ok(index_type) = self.keyof(span, ty) {
if let Ok(()) = self.assign_with_opts(
&mut Default::default(),
&index_type,
&prop.ty(),
AssignOpts {
span,
..Default::default()
},
) {
let mut result_ty = m.ty.clone().map(|v| *v).unwrap_or_else(|| Type::any(span, Default::default()));
if let Some(obj) = self
.expand_mapped(span, m)
.context("tried to expand a mapped type to access property")?
{
return self.access_property(span, &obj, prop, type_mode, id_ctx, opts);
}

replace_type(
&mut result_ty,
|ty| match ty.normalize() {
Type::Param(ty) => ty.name == m.type_param.name,
_ => false,
if let Some(Type::Index(Index { ty, .. })) = constraint.as_ref().map(Type::normalize) {
// Check if we can index the object with given key.
if let Ok(index_type) = self.keyof(span, ty) {
if let Ok(()) = self.assign_with_opts(
&mut Default::default(),
&index_type,
&prop.ty(),
AssignOpts {
span,
..Default::default()
},
|_| Some(index_type.clone()),
);
) {
let mut result_ty = m.ty.clone().map(|v| *v).unwrap_or_else(|| Type::any(span, Default::default()));

replace_type(
&mut result_ty,
|ty| match ty.normalize() {
Type::Param(ty) => ty.name == m.type_param.name,
_ => false,
},
|_| Some(index_type.clone()),
);

return Ok(result_ty);
return Ok(result_ty);
}
}
}
}

warn!("Creating an indexed access type with mapped type as the object");
warn!("Creating an indexed access type with mapped type as the object");

return Ok(Type::IndexedAccessType(IndexedAccessType {
span,
readonly: false,
obj_type: Box::new(obj),
index_type: Box::new(prop.ty().into_owned()),
metadata: Default::default(),
tracker: Default::default(),
}));
return Ok(Type::IndexedAccessType(IndexedAccessType {
span,
readonly: false,
obj_type: Box::new(obj),
index_type: Box::new(prop.ty().into_owned()),
metadata: Default::default(),
tracker: Default::default(),
}));
})(obj.clone())
.map(|v| {
if let Some(TruePlusMinus::True) = m.optional {
Type::new_union(span, vec![v, Type::undefined(span, Default::default())])
} else {
v
}
});
return result;
}

Type::Ref(r) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
{
"required_errors": {
"TS2322": 13,
"TS2322": 12,
"TS2536": 4,
"TS2542": 4
},
"required_error_lines": {
"TS2322": [
14,
19,
33,
75,
81,
91,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Stats {
required_error: 21,
matched_error: 7,
required_error: 20,
matched_error: 8,
extra_error: 5,
panic: 0,
}
4 changes: 2 additions & 2 deletions crates/stc_ts_type_checker/tests/tsc-stats.rust-debug
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Stats {
required_error: 3501,
matched_error: 6534,
required_error: 3500,
matched_error: 6535,
extra_error: 763,
panic: 73,
}
Loading