diff --git a/crates/stc_ts_file_analyzer/src/analyzer/assign/function.rs b/crates/stc_ts_file_analyzer/src/analyzer/assign/function.rs index fe0285e131..cb356b3b28 100644 --- a/crates/stc_ts_file_analyzer/src/analyzer/assign/function.rs +++ b/crates/stc_ts_file_analyzer/src/analyzer/assign/function.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, cmp::max}; +use std::{borrow::Cow, cmp::max, iter::Peekable}; use fxhash::FxHashMap; use itertools::Itertools; @@ -7,10 +7,10 @@ use stc_ts_errors::{ debug::{dump_type_map, force_dump_type_as_string}, DebugExt, ErrorKind, }; -use stc_ts_types::{Constructor, FnParam, Function, IdCtx, Key, KeywordType, LitType, Type, TypeElement, TypeParamDecl}; +use stc_ts_types::{Constructor, FnParam, Function, IdCtx, Key, KeywordType, LitType, SpreadLike, Type, TypeElement, TypeParamDecl}; use stc_utils::{cache::Freeze, dev_span, stack}; use swc_atoms::js_word; -use swc_common::{Spanned, SyntaxContext, TypeEq}; +use swc_common::{Span, Spanned, SyntaxContext, TypeEq}; use swc_ecma_ast::TsKeywordTypeKind; use tracing::debug; @@ -816,6 +816,125 @@ impl Analyzer<'_, '_> { Ok(()) } + fn relate_spread_likes<'a, 'l, 'r, T, LI, RI>( + &'a mut self, + span: Span, + li: &mut Peekable
  • , + ri: &mut Peekable, + relate: &mut impl FnMut(&mut Self, &Type, &Type) -> VResult<()>, + ) -> VResult<()> + where + T: SpreadLike, + LI: Iterator + Clone, + RI: Iterator + Clone, + { + let _tracing = dev_span!("relate_spread_likes"); + let li_count = li.clone().count(); + let ri_count = ri.clone().count(); + + while let (Some(..), Some(..)) = (li.peek(), ri.peek()) { + let l = li.peek().copied().cloned().unwrap(); + let r = ri.peek().copied().cloned().unwrap(); + + match (l.is_spread(), r.is_spread()) { + (true, true) => { + li.next(); + ri.next(); + + relate(self, &l.ty(), &r.ty()).context("failed to relate a spread item to another one")?; + } + (true, false) => { + li.next(); + + for (idx, r) in ri.into_iter().enumerate() { + let le = self + .access_property( + span, + &l.ty(), + &Key::Num(RNumber { + span: l.span(), + value: idx as f64, + raw: None, + }), + TypeOfMode::RValue, + IdCtx::Var, + AccessPropertyOpts { + disallow_indexing_array_with_string: true, + disallow_creating_indexed_type_from_ty_els: true, + disallow_indexing_class_with_computed: true, + disallow_inexact: true, + use_last_element_for_tuple_on_out_of_bound: true, + disallow_creating_indexed_type_for_type_params: true, + ..Default::default() + }, + ) + .unwrap_or_else(|_| l.ty().clone()); + + relate(self, &le, r.ty()).with_context(|| { + format!( + "l: spread + {} (from {}); r: non-spread + {}; idx = {}", + force_dump_type_as_string(&le), + force_dump_type_as_string(&l.ty()), + force_dump_type_as_string(r.ty()), + idx + ) + })?; + } + + return Ok(()); + } + (false, true) => { + ri.next(); + + for (idx, l) in li.into_iter().enumerate() { + let re = self + .access_property( + span, + &r.ty(), + &Key::Num(RNumber { + span: r.span(), + value: idx as f64, + raw: None, + }), + TypeOfMode::RValue, + IdCtx::Var, + AccessPropertyOpts { + disallow_indexing_array_with_string: true, + disallow_creating_indexed_type_from_ty_els: true, + disallow_indexing_class_with_computed: true, + disallow_inexact: true, + use_last_element_for_tuple_on_out_of_bound: true, + disallow_creating_indexed_type_for_type_params: true, + ..Default::default() + }, + ) + .unwrap_or_else(|_| r.ty().clone()); + + relate(self, l.ty(), &re).with_context(|| { + format!( + "l: non-spread + {}; r: spread + {} (from {}); idx = {}", + force_dump_type_as_string(&l.ty()), + force_dump_type_as_string(&re), + force_dump_type_as_string(&r.ty()), + idx + ) + })?; + } + + return Ok(()); + } + (false, false) => { + li.next(); + ri.next(); + + relate(self, &l.ty(), &r.ty()).context("failed to relate a non-spread item")?; + } + } + } + + Ok(()) + } + /// # Validation of parameter count /// /// A parameter named `this` is excluded. @@ -837,24 +956,33 @@ impl Analyzer<'_, '_> { let span = opts.span; - let mut li = l.iter().filter(|p| { - !matches!( - p.pat, - RPat::Ident(RBindingIdent { - id: RIdent { sym: js_word!("this"), .. }, - .. - }) - ) - }); - let mut ri = r.iter().filter(|p| { - !matches!( - p.pat, - RPat::Ident(RBindingIdent { - id: RIdent { sym: js_word!("this"), .. }, - .. - }) - ) - }); + let mut li = l + .iter() + .filter(|p| { + !matches!( + p.pat, + RPat::Ident(RBindingIdent { + id: RIdent { sym: js_word!("this"), .. }, + .. + }) + ) + }) + .peekable(); + let mut ri = r + .iter() + .filter(|p| { + !matches!( + p.pat, + RPat::Ident(RBindingIdent { + id: RIdent { sym: js_word!("this"), .. }, + .. + }) + ) + }) + .peekable(); + + let li_count = li.clone().count(); + let ri_count = ri.clone().count(); let l_has_rest = l.iter().any(|p| matches!(p.pat, RPat::Rest(..))); @@ -899,124 +1027,19 @@ impl Analyzer<'_, '_> { } } - loop { - let l = li.next(); - let r = ri.next(); - - let (Some(l), Some(r)) = (l, r) else { - break - }; - - // TODO(kdy1): What should we do? - if opts.allow_assignment_to_param { - if let Ok(()) = self.assign_param( - data, - r, - l, - AssignOpts { - allow_unknown_type: true, - allow_assignment_to_param: false, - ..opts - }, - ) { - continue; - } - } - - // A rest pattern is always the last - match (&l.pat, &r.pat) { - (RPat::Rest(..), RPat::Rest(..)) => { - self.assign_param(data, l, r, opts) - .with_context(|| "tried to assign a rest parameter to another rest parameter".to_string())?; - break; - } - - (RPat::Rest(..), _) => { - // TODO(kdy1): Implement correct logic - - return Ok(()); - } - - (_, RPat::Rest(..)) => { - // If r is an iterator, we should assign each element to l. - if let Ok(r_iter) = self.get_iterator(span, Cow::Borrowed(&r.ty), Default::default()) { - if let Ok(l_iter) = self.get_iterator(span, Cow::Borrowed(&l.ty), Default::default()) { - for idx in 0..max(li.clone().count(), ri.clone().count()) { - let le = self.access_property( - span, - &l_iter, - &Key::Num(RNumber { - span: l.span, - value: idx as f64, - raw: None, - }), - TypeOfMode::RValue, - IdCtx::Var, - AccessPropertyOpts { - disallow_indexing_array_with_string: true, - disallow_creating_indexed_type_from_ty_els: true, - disallow_indexing_class_with_computed: true, - disallow_inexact: true, - ..Default::default() - }, - )?; - - let re = self.access_property( - span, - &r_iter, - &Key::Num(RNumber { - span: r.span, - value: idx as f64, - raw: None, - }), - TypeOfMode::RValue, - IdCtx::Var, - AccessPropertyOpts { - disallow_indexing_array_with_string: true, - disallow_creating_indexed_type_from_ty_els: true, - disallow_indexing_class_with_computed: true, - disallow_inexact: true, - use_last_element_for_tuple_on_out_of_bound: true, - ..Default::default() - }, - )?; - - self.assign_param_type(data, &le, &re, opts).with_context(|| { - format!( - "tried to assign a rest parameter to parameters; r_ty = {}", - force_dump_type_as_string(&r.ty) - ) - })?; - } - } - - return Ok(()); - } - - self.assign_param(data, l, r, opts) - .context("tried to assign a rest parameter to parameters where r-ty is not a tuple")?; - - for l in li { - self.assign_param(data, l, r, opts) - .context("tried to assign a rest parameter to parameters where r-ty is not a tuple (iter)")?; - } - - return Ok(()); - } - - _ => { - self.assign_param( - data, - l, - r, - AssignOpts { - allow_unknown_type: true, - ..opts - }, - )?; - } - } - } + self.relate_spread_likes(span, &mut li, &mut ri, &mut |this, l, r| { + // + this.assign_param_type( + data, + l, + r, + AssignOpts { + allow_assignment_to_void: true, + ..opts + }, + ) + }) + .context("failed to relate parameters")?; Ok(()) } diff --git a/crates/stc_ts_file_analyzer/src/analyzer/assign/type_el.rs b/crates/stc_ts_file_analyzer/src/analyzer/assign/type_el.rs index f9c29af448..bccc823dd1 100644 --- a/crates/stc_ts_file_analyzer/src/analyzer/assign/type_el.rs +++ b/crates/stc_ts_file_analyzer/src/analyzer/assign/type_el.rs @@ -256,7 +256,12 @@ impl Analyzer<'_, '_> { ..opts }, ) - .context("tried to assign to type elements by converting rhs to a type literal"); + .with_context(|| { + format!( + "tried to assign to type elements by converting rhs to a type literal: {}", + force_dump_type_as_string(&rty) + ) + }); } return Err(ErrorKind::SimpleAssignFailed { span, cause: None }.into()); diff --git a/crates/stc_ts_file_analyzer/src/analyzer/expr/mod.rs b/crates/stc_ts_file_analyzer/src/analyzer/expr/mod.rs index e61b3fbce4..045d50bdfc 100644 --- a/crates/stc_ts_file_analyzer/src/analyzer/expr/mod.rs +++ b/crates/stc_ts_file_analyzer/src/analyzer/expr/mod.rs @@ -656,6 +656,7 @@ pub(crate) struct AccessPropertyOpts { /// obj11.foo; // Error TS2339 /// ``` pub disallow_creating_indexed_type_from_ty_els: bool, + pub disallow_creating_indexed_type_for_type_params: bool, pub disallow_indexing_class_with_computed: bool, @@ -2187,6 +2188,15 @@ impl Analyzer<'_, '_> { } } + if opts.disallow_creating_indexed_type_for_type_params { + return Err(ErrorKind::NoSuchProperty { + span, + obj: Some(Box::new(obj.clone())), + prop: Some(Box::new(prop.clone())), + } + .context("disallow_creating_indexed_type_for_type_params = true")); + } + let mut prop_ty = match prop { Key::Computed(key) => key.ty.clone(), Key::Normal { span, sym } => Box::new(Type::Lit(LitType { @@ -2756,6 +2766,9 @@ impl Analyzer<'_, '_> { })); } if opts.use_last_element_for_tuple_on_out_of_bound { + if elems.is_empty() { + return Ok(Type::any(span, Default::default())); + } return Ok(*elems.last().unwrap().ty.clone()); } @@ -2777,7 +2790,7 @@ impl Analyzer<'_, '_> { } return Err(ErrorKind::TupleIndexError { - span: n.span(), + span, index: v, len: elems.len() as u64, } diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.ts b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.ts new file mode 100644 index 0000000000..f22b8ced73 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.ts @@ -0,0 +1,12 @@ +// call signatures in derived types must have the same or fewer optional parameters as the base type + +interface Base { + a: (...args: number[]) => number; + a2: (x: number, ...z: number[]) => number; + a3: (x: number, y?: string, ...z: number[]) => number; + a4: (x?: number, y?: string, ...z: number[]) => number; +} + +export interface I3B extends Base { + a: (x?: string) => number; // error, incompatible type +} diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.tsc-errors.json b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.tsc-errors.json new file mode 100644 index 0000000000..c45e3b41e4 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.tsc-errors.json @@ -0,0 +1,8 @@ +[ + { + "file": "tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/1.ts", + "line": 10, + "col": 18, + "code": 2430 + } +] \ No newline at end of file diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.ts b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.ts new file mode 100644 index 0000000000..071ac01f45 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.ts @@ -0,0 +1,46 @@ +// call signatures in derived types must have the same or fewer optional parameters as the base type + +interface Base { + a: (...args: number[]) => number; + a2: (x: number, ...z: number[]) => number; + a3: (x: number, y?: string, ...z: number[]) => number; + a4: (x?: number, y?: string, ...z: number[]) => number; +} + + + + + + + + + + + + + + + + + + + + + + + + + +export interface I10C extends Base { + a3: (x: number, ...z: number[]) => number; // error +} + + + + + + + + + + diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.tsc-errors.json b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.tsc-errors.json new file mode 100644 index 0000000000..82d78c58c8 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.tsc-errors.json @@ -0,0 +1,8 @@ +[ + { + "file": "tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/2.ts", + "line": 34, + "col": 18, + "code": 2430 + } +] \ No newline at end of file diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.ts b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.ts new file mode 100644 index 0000000000..722c610acc --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.ts @@ -0,0 +1,46 @@ +// call signatures in derived types must have the same or fewer optional parameters as the base type + +interface Base { + a: (...args: number[]) => number; + a2: (x: number, ...z: number[]) => number; + a3: (x: number, y?: string, ...z: number[]) => number; + a4: (x?: number, y?: string, ...z: number[]) => number; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + +export interface I10E extends Base { + a3: (x: number, ...z: string[]) => number; // error +} + + + + + + + + diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.tsc-errors.json b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.tsc-errors.json new file mode 100644 index 0000000000..4afda86b13 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.tsc-errors.json @@ -0,0 +1,8 @@ +[ + { + "file": "tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/3.ts", + "line": 36, + "col": 18, + "code": 2430 + } +] \ No newline at end of file diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.ts b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.ts new file mode 100644 index 0000000000..d941fa50f1 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.ts @@ -0,0 +1,46 @@ +// call signatures in derived types must have the same or fewer optional parameters as the base type + +interface Base { + a: (...args: number[]) => number; + a2: (x: number, ...z: number[]) => number; + a3: (x: number, y?: string, ...z: number[]) => number; + a4: (x?: number, y?: string, ...z: number[]) => number; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +export interface I16 extends Base { + a4: (x: number, ...args: string[]) => number; // error, rest param has type mismatch +} + + diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.tsc-errors.json b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.tsc-errors.json new file mode 100644 index 0000000000..4420d1afdf --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.tsc-errors.json @@ -0,0 +1,8 @@ +[ + { + "file": "tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/4.ts", + "line": 42, + "col": 18, + "code": 2430 + } +] \ No newline at end of file diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.ts b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.ts new file mode 100644 index 0000000000..d4926d0687 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.ts @@ -0,0 +1,45 @@ +// call signatures in derived types must have the same or fewer optional parameters as the base type + +interface Base { + a: (...args: number[]) => number; + a2: (x: number, ...z: number[]) => number; + a3: (x: number, y?: string, ...z: number[]) => number; + a4: (x?: number, y?: string, ...z: number[]) => number; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +export interface I17 extends Base { + a4: (...args: number[]) => number; // error +} diff --git a/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.tsc-errors.json b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.tsc-errors.json new file mode 100644 index 0000000000..d103d8c6c8 --- /dev/null +++ b/crates/stc_ts_file_analyzer/tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.tsc-errors.json @@ -0,0 +1,8 @@ +[ + { + "file": "tests/tsc/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithCallSignaturesWithRestParameters/5.ts", + "line": 43, + "col": 18, + "code": 2430 + } +] \ No newline at end of file diff --git a/crates/stc_ts_types/src/lib.rs b/crates/stc_ts_types/src/lib.rs index 9a4f8bf369..5b9137c832 100644 --- a/crates/stc_ts_types/src/lib.rs +++ b/crates/stc_ts_types/src/lib.rs @@ -3288,3 +3288,28 @@ impl_freeze!(Key); impl_freeze!(Enum); impl_freeze!(ClassDef); impl_freeze!(Mapped); + +pub trait SpreadLike: 'static + Spanned + Clone { + fn is_spread(&self) -> bool; + fn ty(&self) -> &Type; +} + +impl SpreadLike for FnParam { + fn is_spread(&self) -> bool { + matches!(&self.pat, RPat::Rest(..)) + } + + fn ty(&self) -> &Type { + &self.ty + } +} + +impl SpreadLike for TypeOrSpread { + fn is_spread(&self) -> bool { + self.spread.is_some() + } + + fn ty(&self) -> &Type { + &self.ty + } +} diff --git a/cspell.json b/cspell.json index 0b9c4d6769..1fc6ce127e 100644 --- a/cspell.json +++ b/cspell.json @@ -57,6 +57,7 @@ "osascript", "overriden", "partialeq", + "Peekable", "petgraph", "pmutil", "Postprocess", @@ -125,4 +126,4 @@ "scripts/npm", "**/stc_ts_testing/src/conformance.rs" ] -} \ No newline at end of file +}