From 91f14e26c0cc66a05e399fbf4487e51c49e65469 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 23 May 2024 14:03:00 +0200 Subject: [PATCH 1/4] feat(symbols): collect symbols for params This includes type params, function params, and params declared through `infer X` in type annotations. --- src/fast_check/swc_helpers.rs | 97 --- src/fast_check/transform.rs | 16 +- src/lib.rs | 1 + src/swc_helpers.rs | 112 +++ src/symbols/analyzer.rs | 280 +++++++- src/symbols/dep_analyzer.rs | 1 + tests/helpers/mod.rs | 56 +- tests/specs/symbols/Classes01.txt | 640 +++++++++++++++--- tests/specs/symbols/Functions01.txt | 288 ++++++-- .../class_private_ctor_param_props.txt | 44 +- tests/specs/symbols/declaration_merging01.txt | 68 +- tests/specs/symbols/function_overloads01.txt | 232 ++++++- tests/specs/symbols/ts_namespace_export01.txt | 34 + 13 files changed, 1531 insertions(+), 338 deletions(-) create mode 100644 src/swc_helpers.rs diff --git a/src/fast_check/swc_helpers.rs b/src/fast_check/swc_helpers.rs index e2fe1fead..5b501eb65 100644 --- a/src/fast_check/swc_helpers.rs +++ b/src/fast_check/swc_helpers.rs @@ -1,7 +1,5 @@ // Copyright 2018-2024 the Deno authors. MIT license. -use std::ops::ControlFlow; - use deno_ast::swc::ast::*; use deno_ast::swc::common::DUMMY_SP; @@ -20,101 +18,6 @@ pub fn ts_keyword_type(kind: TsKeywordTypeKind) -> TsType { }) } -#[derive(Debug)] -pub enum ReturnStatementAnalysis { - /// There are no return statements in the function body. - None, - /// There are only return statements without arguments in the function body, - /// or if the function body is empty. - Void, - /// There is only a single return statement in the function body, and it has - /// an argument. - Single(ReturnStmt), - /// There are multiple return statements in the function body, and at least - /// one of them has an argument. - Multiple, -} - -pub fn analyze_return_stmts_in_function_body( - body: &deno_ast::swc::ast::BlockStmt, -) -> ReturnStatementAnalysis { - if body.stmts.is_empty() { - ReturnStatementAnalysis::Void - } else { - let mut analysis = ReturnStatementAnalysis::None; - analyze_return_stmts_from_stmts(&body.stmts, &mut analysis); - analysis - } -} - -fn analyze_return_stmts_from_stmts( - stmts: &[Stmt], - analysis: &mut ReturnStatementAnalysis, -) -> ControlFlow<(), ()> { - for stmt in stmts { - analyze_return_stmts_from_stmt(stmt, analysis)?; - } - ControlFlow::Continue(()) -} - -fn analyze_return_stmts_from_stmt( - stmt: &Stmt, - analysis: &mut ReturnStatementAnalysis, -) -> ControlFlow<(), ()> { - match stmt { - Stmt::Block(n) => analyze_return_stmts_from_stmts(&n.stmts, analysis), - Stmt::With(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::Return(n) => { - match (&n.arg, &*analysis) { - (None, ReturnStatementAnalysis::None) => { - *analysis = ReturnStatementAnalysis::Void; - } - (None, ReturnStatementAnalysis::Void) => {} - (Some(_), ReturnStatementAnalysis::None) - | (Some(_), ReturnStatementAnalysis::Void) => { - *analysis = ReturnStatementAnalysis::Single(n.clone()); - } - (_, ReturnStatementAnalysis::Single(_)) => { - *analysis = ReturnStatementAnalysis::Multiple; - return ControlFlow::Break(()); - } - (_, ReturnStatementAnalysis::Multiple) => unreachable!(), // we break early when analysis is Multiple - } - ControlFlow::Continue(()) - } - Stmt::Labeled(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::If(n) => analyze_return_stmts_from_stmt(&n.cons, analysis), - Stmt::Switch(n) => { - for case in &n.cases { - analyze_return_stmts_from_stmts(&case.cons, analysis)?; - } - ControlFlow::Continue(()) - } - Stmt::Try(n) => { - analyze_return_stmts_from_stmts(&n.block.stmts, analysis)?; - if let Some(n) = &n.handler { - analyze_return_stmts_from_stmts(&n.body.stmts, analysis)?; - } - if let Some(n) = &n.finalizer { - analyze_return_stmts_from_stmts(&n.stmts, analysis)?; - } - ControlFlow::Continue(()) - } - Stmt::While(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::DoWhile(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::For(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::ForIn(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::ForOf(n) => analyze_return_stmts_from_stmt(&n.body, analysis), - Stmt::Break(_) - | Stmt::Continue(_) - | Stmt::Throw(_) - | Stmt::Debugger(_) - | Stmt::Decl(_) - | Stmt::Expr(_) - | Stmt::Empty(_) => ControlFlow::Continue(()), - } -} - pub fn is_void_type(return_type: &TsType) -> bool { is_keyword_type(return_type, TsKeywordTypeKind::TsVoidKeyword) } diff --git a/src/fast_check/transform.rs b/src/fast_check/transform.rs index 12446970d..6a9bc6323 100644 --- a/src/fast_check/transform.rs +++ b/src/fast_check/transform.rs @@ -25,6 +25,9 @@ use deno_ast::SourceRange; use deno_ast::SourceRangedForSpanned; use indexmap::IndexMap; +use crate::swc_helpers::analyze_return_stmts_in_function_body; +use crate::swc_helpers::FunctionKind; +use crate::swc_helpers::ReturnStatementAnalysis; use crate::symbols::EsModuleInfo; use crate::symbols::ExpandoPropertyRef; use crate::symbols::Symbol; @@ -34,13 +37,11 @@ use crate::ParserModuleAnalyzer; use crate::WorkspaceMember; use super::range_finder::ModulePublicRanges; -use super::swc_helpers::analyze_return_stmts_in_function_body; use super::swc_helpers::any_type_ann; use super::swc_helpers::ident; use super::swc_helpers::is_void_type; use super::swc_helpers::maybe_lit_to_ts_type; use super::swc_helpers::ts_keyword_type; -use super::swc_helpers::ReturnStatementAnalysis; use super::transform_dts::FastCheckDtsDiagnostic; use super::transform_dts::FastCheckDtsTransformer; use super::FastCheckDiagnostic; @@ -1898,17 +1899,6 @@ impl<'a> FastCheckTransformer<'a> { } } -enum FunctionKind { - /// function declarations, class method declarations (both class decl and class expr) - DeclarationLike, - /// function expressions, arrow functions, object method shorthand properties - ExpressionLike, - /// getters, both on classes and object literals - Getter, - /// setters, both on classes and object literals - Setter, -} - fn is_ts_private_computed_class_member(m: &ClassMember) -> bool { match m { ClassMember::Method(m) => { diff --git a/src/lib.rs b/src/lib.rs index fc7980ef3..2d0b484bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ mod fast_check; pub mod packages; pub mod source; mod text_encoding; +mod swc_helpers; use source::FileSystem; use source::JsrUrlProvider; diff --git a/src/swc_helpers.rs b/src/swc_helpers.rs new file mode 100644 index 000000000..a8b33984a --- /dev/null +++ b/src/swc_helpers.rs @@ -0,0 +1,112 @@ +// Copyright 2018-2024 the Deno authors. MIT license. + +use std::ops::ControlFlow; + +use deno_ast::swc::ast::ReturnStmt; +use deno_ast::swc::ast::Stmt; + +pub enum FunctionKind { + /// function declarations, class method declarations (both class decl and class expr) + DeclarationLike, + /// function expressions, arrow functions, object method shorthand properties + ExpressionLike, + /// getters, both on classes and object literals + Getter, + /// setters, both on classes and object literals + Setter, +} + +#[derive(Debug)] +pub enum ReturnStatementAnalysis { + /// There are no return statements in the function body. + None, + /// There are only return statements without arguments in the function body, + /// or if the function body is empty. + Void, + /// There is only a single return statement in the function body, and it has + /// an argument. + Single(ReturnStmt), + /// There are multiple return statements in the function body, and at least + /// one of them has an argument. + Multiple, +} + +pub fn analyze_return_stmts_in_function_body( + body: &deno_ast::swc::ast::BlockStmt, +) -> ReturnStatementAnalysis { + if body.stmts.is_empty() { + ReturnStatementAnalysis::Void + } else { + let mut analysis = ReturnStatementAnalysis::None; + analyze_return_stmts_from_stmts(&body.stmts, &mut analysis); + analysis + } +} + +fn analyze_return_stmts_from_stmts( + stmts: &[Stmt], + analysis: &mut ReturnStatementAnalysis, +) -> ControlFlow<(), ()> { + for stmt in stmts { + analyze_return_stmts_from_stmt(stmt, analysis)?; + } + ControlFlow::Continue(()) +} + +fn analyze_return_stmts_from_stmt( + stmt: &Stmt, + analysis: &mut ReturnStatementAnalysis, +) -> ControlFlow<(), ()> { + match stmt { + Stmt::Block(n) => analyze_return_stmts_from_stmts(&n.stmts, analysis), + Stmt::With(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::Return(n) => { + match (&n.arg, &*analysis) { + (None, ReturnStatementAnalysis::None) => { + *analysis = ReturnStatementAnalysis::Void; + } + (None, ReturnStatementAnalysis::Void) => {} + (Some(_), ReturnStatementAnalysis::None) + | (Some(_), ReturnStatementAnalysis::Void) => { + *analysis = ReturnStatementAnalysis::Single(n.clone()); + } + (_, ReturnStatementAnalysis::Single(_)) => { + *analysis = ReturnStatementAnalysis::Multiple; + return ControlFlow::Break(()); + } + (_, ReturnStatementAnalysis::Multiple) => unreachable!(), // we break early when analysis is Multiple + } + ControlFlow::Continue(()) + } + Stmt::Labeled(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::If(n) => analyze_return_stmts_from_stmt(&n.cons, analysis), + Stmt::Switch(n) => { + for case in &n.cases { + analyze_return_stmts_from_stmts(&case.cons, analysis)?; + } + ControlFlow::Continue(()) + } + Stmt::Try(n) => { + analyze_return_stmts_from_stmts(&n.block.stmts, analysis)?; + if let Some(n) = &n.handler { + analyze_return_stmts_from_stmts(&n.body.stmts, analysis)?; + } + if let Some(n) = &n.finalizer { + analyze_return_stmts_from_stmts(&n.stmts, analysis)?; + } + ControlFlow::Continue(()) + } + Stmt::While(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::DoWhile(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::For(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::ForIn(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::ForOf(n) => analyze_return_stmts_from_stmt(&n.body, analysis), + Stmt::Break(_) + | Stmt::Continue(_) + | Stmt::Throw(_) + | Stmt::Debugger(_) + | Stmt::Decl(_) + | Stmt::Expr(_) + | Stmt::Empty(_) => ControlFlow::Continue(()), + } +} diff --git a/src/symbols/analyzer.rs b/src/symbols/analyzer.rs index a8fc4ee9a..f327ad1bd 100644 --- a/src/symbols/analyzer.rs +++ b/src/symbols/analyzer.rs @@ -10,6 +10,8 @@ use deno_ast::swc::ast::*; use deno_ast::swc::atoms::Atom; use deno_ast::swc::utils::find_pat_ids; use deno_ast::swc::utils::is_valid_ident; +use deno_ast::swc::visit::Visit; +use deno_ast::swc::visit::VisitWith as _; use deno_ast::ModuleSpecifier; use deno_ast::ParsedSource; use deno_ast::SourceRange; @@ -18,6 +20,9 @@ use deno_ast::SourceTextInfo; use indexmap::IndexMap; use indexmap::IndexSet; +use crate::swc_helpers::analyze_return_stmts_in_function_body; +use crate::swc_helpers::FunctionKind; +use crate::swc_helpers::ReturnStatementAnalysis; use crate::JsModule; use crate::JsonModule; use crate::ModuleGraph; @@ -368,6 +373,7 @@ impl std::fmt::Debug for SymbolNode { SymbolNodeInner::FnDecl(d) => { d.value().text_fast(d.source.text_info()).to_string() } + SymbolNodeInner::Param { ident, .. } => ident.sym.to_string(), SymbolNodeInner::TsEnum(d) => { d.value().text_fast(d.source.text_info()).to_string() } @@ -492,6 +498,9 @@ impl SymbolNode { SymbolNodeInner::FnDecl(n) => { Some((SymbolNodeRef::FnDecl(n.value()), n.source())) } + SymbolNodeInner::Param { ident, source } => { + Some((SymbolNodeRef::Param(ident), source)) + } SymbolNodeInner::TsEnum(n) => { Some((SymbolNodeRef::TsEnum(n.value()), n.source())) } @@ -577,6 +586,7 @@ enum SymbolNodeInner { ExportDefaultDecl(NodeRefBox), ExportDefaultExpr(NodeRefBox), FnDecl(NodeRefBox), + Param { ident: Ident, source: ParsedSource }, TsEnum(NodeRefBox), TsNamespace(NodeRefBox), TsTypeAlias(NodeRefBox), @@ -617,6 +627,7 @@ pub enum SymbolNodeRef<'a> { ExportDefaultExpr(&'a ExportDefaultExpr), ClassDecl(&'a ClassDecl), FnDecl(&'a FnDecl), + Param(&'a Ident), TsEnum(&'a TsEnumDecl), TsInterface(&'a TsInterfaceDecl), TsNamespace(&'a TsModuleDecl), @@ -715,6 +726,7 @@ impl<'a> SymbolNodeRef<'a> { }, Self::ExportDefaultExpr(_) => None, Self::FnDecl(n) => Some(Cow::Borrowed(&n.ident.sym)), + Self::Param(n) => Some(Cow::Borrowed(&n.sym)), Self::TsEnum(n) => Some(Cow::Borrowed(&n.id.sym)), Self::TsInterface(n) => Some(Cow::Borrowed(&n.id.sym)), Self::TsNamespace(n) => { @@ -829,7 +841,8 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::ClassDecl(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) => true, - SymbolNodeRef::TsTypeAlias(_) + SymbolNodeRef::Param(_) + | SymbolNodeRef::TsTypeAlias(_) | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::Var(..) | SymbolNodeRef::UsingVar(..) @@ -859,6 +872,7 @@ impl<'a> SymbolNodeRef<'a> { SymbolNodeRef::Module(_) | SymbolNodeRef::ClassDecl(_) | SymbolNodeRef::FnDecl(_) + | SymbolNodeRef::Param(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) | SymbolNodeRef::TsNamespace(_) @@ -902,6 +916,7 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::ClassParamProp(_) | SymbolNodeRef::Constructor(_) | SymbolNodeRef::ExpandoProperty(..) + | SymbolNodeRef::Param(_) | SymbolNodeRef::TsIndexSignature(_) | SymbolNodeRef::TsCallSignatureDecl(_) | SymbolNodeRef::TsConstructSignatureDecl(_) @@ -921,6 +936,7 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::ExportDefaultDecl(_) | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) + | SymbolNodeRef::Param(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) | SymbolNodeRef::TsNamespace(_) @@ -952,6 +968,7 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::ExportDefaultDecl(_) | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) + | SymbolNodeRef::Param(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) | SymbolNodeRef::TsNamespace(_) @@ -1335,6 +1352,16 @@ impl Symbol { .map(|n| n.is_private_member()) .unwrap_or(false) } + + /// If the symbol is a parameter. + pub fn is_param(&self) -> bool { + self + .decls() + .first() + .and_then(|d| d.maybe_node()) + .map(|n| matches!(n, SymbolNodeRef::Param(_))) + .unwrap_or(false) + } } /// A unique identifier for a symbol, which consists of the module id and symbol id. @@ -1989,6 +2016,11 @@ impl<'a> SymbolFiller<'a> { ); module_symbol.add_export(n.ident.sym.to_string(), symbol_id); module_symbol.add_child_id(symbol_id); + self.fill_function( + symbol_id, + &n.function, + FunctionKind::DeclarationLike, + ); } Decl::Var(n) => { for decl in &n.decls { @@ -2015,6 +2047,7 @@ impl<'a> SymbolFiller<'a> { module_symbol.add_child_id(symbol_id); module_symbol.add_export(export_name, symbol_id); } + self.fill_var_decl(module_symbol.symbol_id(), decl); } } Decl::TsInterface(n) => { @@ -2261,8 +2294,12 @@ impl<'a> SymbolFiller<'a> { DefaultDecl::Class(n) => { self.fill_class(symbol, &n.class); } - DefaultDecl::Fn(_) => { - // nothing to fill + DefaultDecl::Fn(n) => { + self.fill_function( + symbol.symbol_id(), + &n.function, + FunctionKind::DeclarationLike, + ); } DefaultDecl::TsInterfaceDecl(n) => { self.fill_ts_interface(symbol, n) @@ -2394,6 +2431,11 @@ impl<'a> SymbolFiller<'a> { if decls_are_exports { module_symbol.add_export(n.ident.sym.to_string(), symbol_id); } + self.fill_function( + symbol_id, + &n.function, + FunctionKind::DeclarationLike, + ); } Decl::Var(var_decl) => { for decl in &var_decl.decls { @@ -2419,6 +2461,7 @@ impl<'a> SymbolFiller<'a> { module_symbol.add_export(export_name, symbol_id); } } + self.fill_var_decl(module_symbol.symbol_id(), decl); } } Decl::TsInterface(n) => { @@ -2534,6 +2577,7 @@ impl<'a> SymbolFiller<'a> { module_symbol.add_export(export_name, symbol_id); } } + self.fill_var_decl(module_symbol.symbol_id(), decl); } } }; @@ -2620,6 +2664,11 @@ impl<'a> SymbolFiller<'a> { } fn fill_class(&self, symbol: &SymbolMut, n: &Class) { + if let Some(type_params) = n.type_params.as_ref() { + for param in &type_params.params { + self.add_param(symbol.symbol_id(), param.name.clone()); + } + } self.fill_ts_class_members(symbol, &n.body); } @@ -2732,33 +2781,49 @@ impl<'a> SymbolFiller<'a> { for member in members { match member { ClassMember::Constructor(ctor) => { - self.create_symbol_member_or_export( + let ctor_symbol = self.create_symbol_member_or_export( symbol, SymbolNodeRef::Constructor(ctor), ); for param in &ctor.params { - if let ParamOrTsParamProp::TsParamProp(prop) = param { - self.create_symbol_member_or_export( - symbol, - SymbolNodeRef::ClassParamProp(prop), - ); + match param { + ParamOrTsParamProp::TsParamProp(prop) => { + self.create_symbol_member_or_export( + symbol, + SymbolNodeRef::ClassParamProp(prop), + ); + } + ParamOrTsParamProp::Param(param) => self.fill_fn_params( + ctor_symbol.symbol_id(), + [¶m.pat].into_iter(), + ), } } } ClassMember::Method(method) => { - self.create_symbol_member_or_export( + let method_symbol = self.create_symbol_member_or_export( symbol, SymbolNodeRef::ClassMethod(method), ); + self.fill_function( + method_symbol.symbol_id(), + &method.function, + FunctionKind::DeclarationLike, + ); } ClassMember::PrivateMethod(_) => { // todo(dsherret): add private methods } ClassMember::ClassProp(prop) => { - self.create_symbol_member_or_export( + let prop_symbol = self.create_symbol_member_or_export( symbol, SymbolNodeRef::ClassProp(prop), ); + if prop.type_ann.is_none() { + if let Some(value) = &prop.value { + self.fill_expr(prop_symbol.symbol_id(), value); + } + } } ClassMember::PrivateProp(_) => { // todo(dsherret): add private properties @@ -2883,6 +2948,7 @@ impl<'a> SymbolFiller<'a> { | SymbolNodeRef::ExportDefaultDecl(_) | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) + | SymbolNodeRef::Param(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) | SymbolNodeRef::TsNamespace(_) @@ -2978,4 +3044,196 @@ impl<'a> SymbolFiller<'a> { } None } + + fn fill_function( + &self, + symbol_id: SymbolId, + n: &Function, + fn_kind: FunctionKind, + ) { + if let Some(type_params) = n.type_params.as_ref() { + for param in &type_params.params { + self.add_param(symbol_id, param.name.clone()); + } + } + self.fill_fn_params(symbol_id, n.params.iter().map(|p| &p.pat)); + if let Some(type_ann) = n.return_type.as_ref() { + self.fill_ts_type(symbol_id, &type_ann.type_ann); + } else if let Some(body) = n.body.as_ref() { + self.fill_fn_body(symbol_id, body, fn_kind); + } + } + + fn fill_arrow_expr(&self, symbol_id: SymbolId, n: &ArrowExpr) { + if let Some(type_params) = n.type_params.as_ref() { + for param in &type_params.params { + self.add_param(symbol_id, param.name.clone()); + } + } + self.fill_fn_params(symbol_id, n.params.iter()); + if let Some(type_ann) = n.return_type.as_ref() { + self.fill_ts_type(symbol_id, &type_ann.type_ann); + } else { + match &*n.body { + BlockStmtOrExpr::BlockStmt(block) => { + self.fill_fn_body(symbol_id, block, FunctionKind::ExpressionLike); + } + BlockStmtOrExpr::Expr(expr) => { + self.fill_expr(symbol_id, expr); + } + } + } + } + + fn fill_fn_params<'b>( + &self, + symbol_id: SymbolId, + params: impl Iterator, + ) { + for param in params { + let (pat, default_value) = match param { + Pat::Assign(pat) => (&*pat.left, Some(&pat.right)), + _ => (param, None), + }; + + let type_ann = match pat { + Pat::Ident(n) => n.type_ann.as_ref(), + Pat::Array(n) => n.type_ann.as_ref(), + Pat::Object(n) => n.type_ann.as_ref(), + Pat::Rest(n) => n.type_ann.as_ref(), + Pat::Invalid(_) | Pat::Expr(_) | Pat::Assign(_) => unreachable!(), + }; + + if let Some(type_ann) = type_ann { + self.fill_ts_type(symbol_id, &type_ann.type_ann); + } else if let Some(default_value) = default_value { + self.fill_expr(symbol_id, default_value); + } + + for ident in find_pat_ids::<_, Ident>(pat) { + self.add_param(symbol_id, ident); + } + } + } + + fn add_param(&self, parent_id: SymbolId, ident: Ident) { + let range = ident.range(); + self.builder.ensure_symbol_for_swc_id( + ident.to_id(), + SymbolDecl::new( + SymbolDeclKind::Definition(SymbolNode(SymbolNodeInner::Param { + ident, + source: self.source.clone(), + })), + range, + ), + parent_id, + ); + } + + fn fill_ts_type(&self, symbol_id: SymbolId, type_ann: &TsType) { + struct TsTypeVisitor { + idents: I, + } + impl Visit for TsTypeVisitor { + fn visit_ts_infer_type(&mut self, n: &TsInferType) { + (self.idents)(&n.type_param.name); + } + + fn visit_ts_type_param(&mut self, n: &TsTypeParam) { + (self.idents)(&n.name); + } + + fn visit_ts_fn_param(&mut self, n: &TsFnParam) { + match n { + TsFnParam::Ident(ident) => { + (self.idents)(&ident.id); + } + TsFnParam::Array(n) => { + for ident in find_pat_ids::<_, Ident>(n) { + (self.idents)(&ident); + } + } + TsFnParam::Rest(n) => { + for ident in find_pat_ids::<_, Ident>(n) { + (self.idents)(&ident); + } + } + TsFnParam::Object(n) => { + for ident in find_pat_ids::<_, Ident>(n) { + (self.idents)(&ident); + } + } + } + } + } + + let mut visitor = TsTypeVisitor { + idents: |ident| { + self.add_param(symbol_id, ident.clone()); + }, + }; + type_ann.visit_with(&mut visitor); + } + + fn fill_fn_body( + &self, + symbol_id: SymbolId, + body: &BlockStmt, + fn_kind: FunctionKind, + ) { + let analysis = analyze_return_stmts_in_function_body(body); + if let ( + FunctionKind::DeclarationLike | FunctionKind::ExpressionLike, + ReturnStatementAnalysis::Single(stmt), + ) = (fn_kind, analysis) + { + self.fill_expr(symbol_id, stmt.arg.as_ref().unwrap()); + } + } + + fn fill_expr(&self, symbol_id: SymbolId, expr: &Expr) { + struct ExprVisitor { + fn_exprs: F, + arrow_exprs: A, + } + impl Visit for ExprVisitor { + fn visit_fn_expr(&mut self, n: &FnExpr) { + (self.fn_exprs)(n); + } + fn visit_arrow_expr(&mut self, n: &ArrowExpr) { + (self.arrow_exprs)(n); + } + } + + let mut visitor = ExprVisitor { + fn_exprs: |n| { + self.fill_function( + symbol_id, + &n.function, + FunctionKind::ExpressionLike, + ); + }, + arrow_exprs: |n| { + self.fill_arrow_expr(symbol_id, n); + }, + }; + expr.visit_with(&mut visitor); + } + + fn fill_var_decl(&self, symbol_id: SymbolId, decl: &VarDeclarator) { + let type_ann = match &decl.name { + Pat::Ident(n) => n.type_ann.as_ref(), + Pat::Array(n) => n.type_ann.as_ref(), + Pat::Object(n) => n.type_ann.as_ref(), + Pat::Rest(_) | Pat::Invalid(_) | Pat::Expr(_) | Pat::Assign(_) => { + unreachable!() + } + }; + if let Some(type_ann) = type_ann { + self.fill_ts_type(symbol_id, &type_ann.type_ann); + } else if let Some(init) = decl.init.as_ref() { + self.fill_expr(symbol_id, init); + } + } } diff --git a/src/symbols/dep_analyzer.rs b/src/symbols/dep_analyzer.rs index c2867d953..5cf99d2c0 100644 --- a/src/symbols/dep_analyzer.rs +++ b/src/symbols/dep_analyzer.rs @@ -127,6 +127,7 @@ impl DepsFiller { self.visit_expr(&n.expr); } SymbolNodeRef::FnDecl(n) => self.visit_function(&n.function), + SymbolNodeRef::Param(_) => {} SymbolNodeRef::TsEnum(n) => { self.visit_ts_enum_decl(n); } diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index af1fa7889..b250619c9 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -300,39 +300,41 @@ impl TestBuilder { } } - if let Some(parent_id) = symbol.parent_id() { - let parent_symbol = module.symbol(parent_id).unwrap(); - let has_child = - parent_symbol.child_ids().any(|id| id == symbol.symbol_id()); - let has_member = parent_symbol - .members() - .iter() - .any(|id| *id == symbol.symbol_id()); - let is_definition_decl = - symbol.decls().iter().all(|d| d.kind.is_definition()); - if is_definition_decl { - // ensure it's possible to go from a parent to its child - if !has_child && !has_member { + if !symbol.is_param() { + if let Some(parent_id) = symbol.parent_id() { + let parent_symbol = module.symbol(parent_id).unwrap(); + let has_child = + parent_symbol.child_ids().any(|id| id == symbol.symbol_id()); + let has_member = parent_symbol + .members() + .iter() + .any(|id| *id == symbol.symbol_id()); + let is_definition_decl = + symbol.decls().iter().all(|d| d.kind.is_definition()); + if is_definition_decl { + // ensure it's possible to go from a parent to its child + if !has_child && !has_member { + results.push(format!( + "Parent {:#?} does not have child {:#?}", + parent_symbol.symbol_id(), + symbol.symbol_id() + )); + } + } else if has_child || has_member { results.push(format!( - "Parent {:#?} does not have child {:#?}", + "Parent {:#?} should not have the child or member {:#?}", parent_symbol.symbol_id(), symbol.symbol_id() )); } - } else if has_child || has_member { - results.push(format!( - "Parent {:#?} should not have the child or member {:#?}", - parent_symbol.symbol_id(), - symbol.symbol_id() - )); - } - if has_child && has_member { - results.push(format!( - "Parent {:?} should not have both a child and a member {:?}", - parent_symbol.symbol_id(), - symbol.symbol_id() - )); + if has_child && has_member { + results.push(format!( + "Parent {:?} should not have both a child and a member {:?}", + parent_symbol.symbol_id(), + symbol.symbol_id() + )); + } } } diff --git a/tests/specs/symbols/Classes01.txt b/tests/specs/symbols/Classes01.txt index 770d7275e..f808ce5a8 100644 --- a/tests/specs/symbols/Classes01.txt +++ b/tests/specs/symbols/Classes01.txt @@ -59,6 +59,14 @@ class ClassWithIndexSignatures { [value: string]: number; } +class ClassWithTypeParams { + prop: T; + + method(a: T): X { + return null as any; + } +} + # output file:///mod.ts: EsModuleInfo { module_id: ModuleId( @@ -71,74 +79,114 @@ file:///mod.ts: EsModuleInfo { "A", #2, ): 1, + ( + "c", + #3, + ): 4, ( "C", #2, - ): 4, + ): 5, ( "BBase", #2, - ): 5, + ): 6, ( "IBase", #2, - ): 6, + ): 7, ( "B", #2, - ): 7, + ): 8, + ( + "prop", + #4, + ): 10, + ( + "other", + #6, + ): 14, + ( + "private", + #7, + ): 16, + ( + "param", + #8, + ): 18, + ( + "param", + #9, + ): 19, ( "PropValue", #2, - ): 14, + ): 20, ( "ReturnValue", #2, - ): 15, + ): 21, ( "Param", #2, - ): 16, + ): 22, ( "PrivateParam", #2, - ): 17, + ): 23, ( "PrivateReturn", #2, - ): 18, + ): 24, ( "PrivateProp", #2, - ): 19, + ): 25, ( "CtorProp", #2, - ): 20, + ): 26, ( "OverloadParam", #2, - ): 21, + ): 27, ( "OverloadReturn", #2, - ): 22, + ): 28, ( "PrivateImplementationParam", #2, - ): 23, + ): 29, ( "PrivateImplementationReturn", #2, - ): 24, + ): 30, ( "ClassWithStatic", #2, - ): 25, + ): 31, ( "ClassWithIndexSignatures", #2, - ): 27, + ): 33, + ( + "ClassWithTypeParams", + #2, + ): 36, + ( + "T", + #11, + ): 37, + ( + "X", + #12, + ): 40, + ( + "a", + #12, + ): 41, }, symbols: { 0: Symbol { @@ -151,7 +199,7 @@ file:///mod.ts: EsModuleInfo { SymbolDecl { kind: Definition( SymbolNode( - "export class A {\n b: B;\n constructor(c: C) {\n }\n}\n\ninterface C {}\n\nclass BBase {\n\n}\n\ninterface IBase {\n}\n\nclass B extends BBase implements IBase {\n private constructor(prop: CtorProp) {}\n prop: PropValue;\n method(): ReturnValue {\n }\n\n method2(other: Param): void {\n }\n\n private asdf(private: PrivateParam): PrivateReturn {\n }\n\n private prop: PrivateProp;\n\n methodOverload(param: OverloadParam): OverloadReturn;\n methodOverload(param: PrivateImplementationParam): PrivateImplementationReturn {\n }\n\n #private: PrivateProp;\n #privateMethod(private: PrivateParam): PrivateReturn {\n }\n}\n\nclass PropValue {}\nclass ReturnValue {}\nclass Param {}\nclass PrivateParam {}\nclass PrivateReturn {}\nclass PrivateProp {}\nclass CtorProp {}\nclass OverloadParam {}\nclass OverloadReturn {}\nclass PrivateImplementationParam {}\nclass PrivateImplementationReturn {}\n\nclass ClassWithStatic {\n static prop: string;\n}\n\nclass ClassWithIndexSignatures {\n static [value: string]: number;\n [value: number]: string;\n [value: string]: number;\n}", + "export class A {\n b: B;\n constructor(c: C) {\n }\n}\n\ninterface C {}\n\nclass BBase {\n\n}\n\ninterface IBase {\n}\n\nclass B extends BBase implements IBase {\n private constructor(prop: CtorProp) {}\n prop: PropValue;\n method(): ReturnValue {\n }\n\n method2(other: Param): void {\n }\n\n private asdf(private: PrivateParam): PrivateReturn {\n }\n\n private prop: PrivateProp;\n\n methodOverload(param: OverloadParam): OverloadReturn;\n methodOverload(param: PrivateImplementationParam): PrivateImplementationReturn {\n }\n\n #private: PrivateProp;\n #privateMethod(private: PrivateParam): PrivateReturn {\n }\n}\n\nclass PropValue {}\nclass ReturnValue {}\nclass Param {}\nclass PrivateParam {}\nclass PrivateReturn {}\nclass PrivateProp {}\nclass CtorProp {}\nclass OverloadParam {}\nclass OverloadReturn {}\nclass PrivateImplementationParam {}\nclass PrivateImplementationReturn {}\n\nclass ClassWithStatic {\n static prop: string;\n}\n\nclass ClassWithIndexSignatures {\n static [value: string]: number;\n [value: number]: string;\n [value: string]: number;\n}\n\nclass ClassWithTypeParams {\n prop: T;\n\n method(a: T): X {\n return null as any;\n }\n}", ), ), range: SourceRange { @@ -159,7 +207,7 @@ file:///mod.ts: EsModuleInfo { 0, ), end: SourcePos( - 1033, + 1130, ), }, flags: 0, @@ -167,23 +215,24 @@ file:///mod.ts: EsModuleInfo { ], child_ids: { 1, - 4, 5, 6, 7, - 14, - 15, - 16, - 17, - 18, - 19, + 8, 20, 21, 22, 23, 24, 25, + 26, 27, + 28, + 29, + 30, + 31, + 33, + 36, }, exports: { "A": 1, @@ -291,6 +340,36 @@ file:///mod.ts: EsModuleInfo { 0, ), symbol_id: 4, + parent_id: Some( + 3, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "c", + ), + ), + range: SourceRange { + start: SourcePos( + 39, + ), + end: SourcePos( + 40, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 5: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 5, parent_id: Some( 0, ), @@ -316,11 +395,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 5: Symbol { + 6: Symbol { module_id: ModuleId( 0, ), - symbol_id: 5, + symbol_id: 6, parent_id: Some( 0, ), @@ -346,11 +425,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 6: Symbol { + 7: Symbol { module_id: ModuleId( 0, ), - symbol_id: 6, + symbol_id: 7, parent_id: Some( 0, ), @@ -376,11 +455,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 7: Symbol { + 8: Symbol { module_id: ModuleId( 0, ), - symbol_id: 7, + symbol_id: 8, parent_id: Some( 0, ), @@ -403,26 +482,26 @@ file:///mod.ts: EsModuleInfo { }, ], child_ids: { - 8, + 9, }, exports: { - "%%dg_ctor%%": 8, + "%%dg_ctor%%": 9, }, members: { - 9, - 10, 11, 12, 13, + 15, + 17, }, }, - 8: Symbol { + 9: Symbol { module_id: ModuleId( 0, ), - symbol_id: 8, + symbol_id: 9, parent_id: Some( - 7, + 8, ), decls: [ SymbolDecl { @@ -446,13 +525,43 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 9: Symbol { + 10: Symbol { module_id: ModuleId( 0, ), - symbol_id: 9, + symbol_id: 10, parent_id: Some( - 7, + 9, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "prop", + ), + ), + range: SourceRange { + start: SourcePos( + 172, + ), + end: SourcePos( + 176, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 11: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 11, + parent_id: Some( + 8, ), decls: [ SymbolDecl { @@ -492,13 +601,13 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 10: Symbol { + 12: Symbol { module_id: ModuleId( 0, ), - symbol_id: 10, + symbol_id: 12, parent_id: Some( - 7, + 8, ), decls: [ SymbolDecl { @@ -522,13 +631,13 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 11: Symbol { + 13: Symbol { module_id: ModuleId( 0, ), - symbol_id: 11, + symbol_id: 13, parent_id: Some( - 7, + 8, ), decls: [ SymbolDecl { @@ -552,13 +661,43 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 12: Symbol { + 14: Symbol { module_id: ModuleId( 0, ), - symbol_id: 12, + symbol_id: 14, parent_id: Some( - 7, + 13, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "other", + ), + ), + range: SourceRange { + start: SourcePos( + 251, + ), + end: SourcePos( + 256, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 15: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 15, + parent_id: Some( + 8, ), decls: [ SymbolDecl { @@ -582,13 +721,43 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 13: Symbol { + 16: Symbol { module_id: ModuleId( 0, ), - symbol_id: 13, + symbol_id: 16, parent_id: Some( - 7, + 15, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "private", + ), + ), + range: SourceRange { + start: SourcePos( + 293, + ), + end: SourcePos( + 300, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 17: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 17, + parent_id: Some( + 8, ), decls: [ SymbolDecl { @@ -628,11 +797,71 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 14: Symbol { + 18: Symbol { module_id: ModuleId( 0, ), - symbol_id: 14, + symbol_id: 18, + parent_id: Some( + 17, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "param", + ), + ), + range: SourceRange { + start: SourcePos( + 385, + ), + end: SourcePos( + 390, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 19: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 19, + parent_id: Some( + 17, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "param", + ), + ), + range: SourceRange { + start: SourcePos( + 441, + ), + end: SourcePos( + 446, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 20: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 20, parent_id: Some( 0, ), @@ -658,11 +887,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 15: Symbol { + 21: Symbol { module_id: ModuleId( 0, ), - symbol_id: 15, + symbol_id: 21, parent_id: Some( 0, ), @@ -688,11 +917,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 16: Symbol { + 22: Symbol { module_id: ModuleId( 0, ), - symbol_id: 16, + symbol_id: 22, parent_id: Some( 0, ), @@ -718,11 +947,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 17: Symbol { + 23: Symbol { module_id: ModuleId( 0, ), - symbol_id: 17, + symbol_id: 23, parent_id: Some( 0, ), @@ -748,11 +977,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 18: Symbol { + 24: Symbol { module_id: ModuleId( 0, ), - symbol_id: 18, + symbol_id: 24, parent_id: Some( 0, ), @@ -778,11 +1007,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 19: Symbol { + 25: Symbol { module_id: ModuleId( 0, ), - symbol_id: 19, + symbol_id: 25, parent_id: Some( 0, ), @@ -808,11 +1037,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 20: Symbol { + 26: Symbol { module_id: ModuleId( 0, ), - symbol_id: 20, + symbol_id: 26, parent_id: Some( 0, ), @@ -838,11 +1067,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 21: Symbol { + 27: Symbol { module_id: ModuleId( 0, ), - symbol_id: 21, + symbol_id: 27, parent_id: Some( 0, ), @@ -868,11 +1097,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 22: Symbol { + 28: Symbol { module_id: ModuleId( 0, ), - symbol_id: 22, + symbol_id: 28, parent_id: Some( 0, ), @@ -898,11 +1127,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 23: Symbol { + 29: Symbol { module_id: ModuleId( 0, ), - symbol_id: 23, + symbol_id: 29, parent_id: Some( 0, ), @@ -928,11 +1157,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 24: Symbol { + 30: Symbol { module_id: ModuleId( 0, ), - symbol_id: 24, + symbol_id: 30, parent_id: Some( 0, ), @@ -958,11 +1187,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 25: Symbol { + 31: Symbol { module_id: ModuleId( 0, ), - symbol_id: 25, + symbol_id: 31, parent_id: Some( 0, ), @@ -985,20 +1214,20 @@ file:///mod.ts: EsModuleInfo { }, ], child_ids: { - 26, + 32, }, exports: { - "prop": 26, + "prop": 32, }, members: {}, }, - 26: Symbol { + 32: Symbol { module_id: ModuleId( 0, ), - symbol_id: 26, + symbol_id: 32, parent_id: Some( - 25, + 31, ), decls: [ SymbolDecl { @@ -1022,11 +1251,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 27: Symbol { + 33: Symbol { module_id: ModuleId( 0, ), - symbol_id: 27, + symbol_id: 33, parent_id: Some( 0, ), @@ -1049,22 +1278,22 @@ file:///mod.ts: EsModuleInfo { }, ], child_ids: { - 28, + 34, }, exports: { - "%%dg_index%%": 28, + "%%dg_index%%": 34, }, members: { - 29, + 35, }, }, - 28: Symbol { + 34: Symbol { module_id: ModuleId( 0, ), - symbol_id: 28, + symbol_id: 34, parent_id: Some( - 27, + 33, ), decls: [ SymbolDecl { @@ -1088,13 +1317,13 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 29: Symbol { + 35: Symbol { module_id: ModuleId( 0, ), - symbol_id: 29, + symbol_id: 35, parent_id: Some( - 27, + 33, ), decls: [ SymbolDecl { @@ -1134,33 +1363,220 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, + 36: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 36, + parent_id: Some( + 0, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "class ClassWithTypeParams {\n prop: T;\n\n method(a: T): X {\n return null as any;\n }\n}", + ), + ), + range: SourceRange { + start: SourcePos( + 1035, + ), + end: SourcePos( + 1130, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: { + 38, + 39, + }, + }, + 37: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 37, + parent_id: Some( + 36, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "T", + ), + ), + range: SourceRange { + start: SourcePos( + 1061, + ), + end: SourcePos( + 1062, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 38: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 38, + parent_id: Some( + 36, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "prop: T;", + ), + ), + range: SourceRange { + start: SourcePos( + 1068, + ), + end: SourcePos( + 1076, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 39: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 39, + parent_id: Some( + 36, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "method(a: T): X {\n return null as any;\n }", + ), + ), + range: SourceRange { + start: SourcePos( + 1080, + ), + end: SourcePos( + 1128, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 40: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 40, + parent_id: Some( + 39, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "X", + ), + ), + range: SourceRange { + start: SourcePos( + 1087, + ), + end: SourcePos( + 1088, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 41: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 41, + parent_id: Some( + 39, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "a", + ), + ), + range: SourceRange { + start: SourcePos( + 1090, + ), + end: SourcePos( + 1091, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, }, } == symbol deps (types and exprs) == 2:19..24 [Id(("B", #2))] 3:27..50 [Id(("C", #2))] -7:109..599 [Id(("BBase", #2)), Id(("IBase", #2))] -8:152..190 [Id(("CtorProp", #2))] -9:193..209 [Id(("PropValue", #2))] -9:340..366 [Id(("PrivateProp", #2))] -10:212..239 [Id(("ReturnValue", #2))] -11:243..276 [Id(("Param", #2))] -12:280..336 [Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] -13:370..423 [Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] -13:426..510 [Id(("PrivateImplementationParam", #2)), Id(("PrivateImplementationReturn", #2))] +8:109..599 [Id(("BBase", #2)), Id(("IBase", #2))] +9:152..190 [Id(("CtorProp", #2))] +11:193..209 [Id(("PropValue", #2))] +11:340..366 [Id(("PrivateProp", #2))] +12:212..239 [Id(("ReturnValue", #2))] +13:243..276 [Id(("Param", #2))] +15:280..336 [Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] +17:370..423 [Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] +17:426..510 [Id(("PrivateImplementationParam", #2)), Id(("PrivateImplementationReturn", #2))] +38:1068..1076 [Id(("T", #11))] +39:1080..1128 [Id(("T", #11)), Id(("X", #12))] == symbol deps (types only) == 2:19..24 [Id(("B", #2))] 3:27..50 [Id(("C", #2))] -7:109..599 [Id(("BBase", #2)), Id(("IBase", #2))] -8:152..190 [Id(("CtorProp", #2))] -9:193..209 [Id(("PropValue", #2))] -9:340..366 [Id(("PrivateProp", #2))] -10:212..239 [Id(("ReturnValue", #2))] -11:243..276 [Id(("Param", #2))] -12:280..336 [Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] -13:370..423 [Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] -13:426..510 [Id(("PrivateImplementationParam", #2)), Id(("PrivateImplementationReturn", #2))] +8:109..599 [Id(("BBase", #2)), Id(("IBase", #2))] +9:152..190 [Id(("CtorProp", #2))] +11:193..209 [Id(("PropValue", #2))] +11:340..366 [Id(("PrivateProp", #2))] +12:212..239 [Id(("ReturnValue", #2))] +13:243..276 [Id(("Param", #2))] +15:280..336 [Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] +17:370..423 [Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] +17:426..510 [Id(("PrivateImplementationParam", #2)), Id(("PrivateImplementationReturn", #2))] +38:1068..1076 [Id(("T", #11))] +39:1080..1128 [Id(("T", #11)), Id(("X", #12))] == export definitions == [A]: file:///mod.ts:0..52 diff --git a/tests/specs/symbols/Functions01.txt b/tests/specs/symbols/Functions01.txt index 5815bf0bf..7e58f1baf 100644 --- a/tests/specs/symbols/Functions01.txt +++ b/tests/specs/symbols/Functions01.txt @@ -33,50 +33,74 @@ file:///mod.ts: EsModuleInfo { "test", #2, ): 1, + ( + "T", + #3, + ): 2, + ( + "param", + #3, + ): 3, ( "TypeParam", #2, - ): 2, + ): 4, ( "Param", #2, - ): 3, + ): 5, ( "Return", #2, - ): 4, + ): 6, ( "Default", #2, - ): 5, + ): 7, ( "overloaded", #2, - ): 6, + ): 8, + ( + "T", + #4, + ): 9, + ( + "param", + #4, + ): 10, + ( + "T", + #6, + ): 11, + ( + "param", + #6, + ): 12, ( "OverloadTypeParam", #2, - ): 7, + ): 13, ( "OverloadParam", #2, - ): 8, + ): 14, ( "OverloadReturn", #2, - ): 9, + ): 15, ( "PrivateTypeParam", #2, - ): 10, + ): 16, ( "PrivateParam", #2, - ): 11, + ): 17, ( "PrivateReturn", #2, - ): 12, + ): 18, }, symbols: { 0: Symbol { @@ -105,21 +129,21 @@ file:///mod.ts: EsModuleInfo { ], child_ids: { 1, - 2, - 3, 4, 5, 6, 7, 8, - 9, - 10, - 11, - 12, + 13, + 14, + 15, + 16, + 17, + 18, }, exports: { "test": 1, - "overloaded": 6, + "overloaded": 8, }, members: {}, }, @@ -158,6 +182,66 @@ file:///mod.ts: EsModuleInfo { 0, ), symbol_id: 2, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "T", + ), + ), + range: SourceRange { + start: SourcePos( + 21, + ), + end: SourcePos( + 22, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 3: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 3, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "param", + ), + ), + range: SourceRange { + start: SourcePos( + 52, + ), + end: SourcePos( + 57, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 4: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 4, parent_id: Some( 0, ), @@ -183,11 +267,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 3: Symbol { + 5: Symbol { module_id: ModuleId( 0, ), - symbol_id: 3, + symbol_id: 5, parent_id: Some( 0, ), @@ -213,11 +297,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 4: Symbol { + 6: Symbol { module_id: ModuleId( 0, ), - symbol_id: 4, + symbol_id: 6, parent_id: Some( 0, ), @@ -243,11 +327,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 5: Symbol { + 7: Symbol { module_id: ModuleId( 0, ), - symbol_id: 5, + symbol_id: 7, parent_id: Some( 0, ), @@ -273,11 +357,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 6: Symbol { + 8: Symbol { module_id: ModuleId( 0, ), - symbol_id: 6, + symbol_id: 8, parent_id: Some( 0, ), @@ -335,11 +419,131 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 7: Symbol { + 9: Symbol { module_id: ModuleId( 0, ), - symbol_id: 7, + symbol_id: 9, + parent_id: Some( + 8, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "T", + ), + ), + range: SourceRange { + start: SourcePos( + 228, + ), + end: SourcePos( + 229, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 10: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 10, + parent_id: Some( + 8, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "param", + ), + ), + range: SourceRange { + start: SourcePos( + 257, + ), + end: SourcePos( + 262, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 11: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 11, + parent_id: Some( + 8, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "T", + ), + ), + range: SourceRange { + start: SourcePos( + 361, + ), + end: SourcePos( + 362, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 12: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 12, + parent_id: Some( + 8, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "param", + ), + ), + range: SourceRange { + start: SourcePos( + 389, + ), + end: SourcePos( + 394, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 13: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 13, parent_id: Some( 0, ), @@ -365,11 +569,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 8: Symbol { + 14: Symbol { module_id: ModuleId( 0, ), - symbol_id: 8, + symbol_id: 14, parent_id: Some( 0, ), @@ -395,11 +599,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 9: Symbol { + 15: Symbol { module_id: ModuleId( 0, ), - symbol_id: 9, + symbol_id: 15, parent_id: Some( 0, ), @@ -425,11 +629,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 10: Symbol { + 16: Symbol { module_id: ModuleId( 0, ), - symbol_id: 10, + symbol_id: 16, parent_id: Some( 0, ), @@ -455,11 +659,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 11: Symbol { + 17: Symbol { module_id: ModuleId( 0, ), - symbol_id: 11, + symbol_id: 17, parent_id: Some( 0, ), @@ -485,11 +689,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 12: Symbol { + 18: Symbol { module_id: ModuleId( 0, ), - symbol_id: 12, + symbol_id: 18, parent_id: Some( 0, ), @@ -519,13 +723,13 @@ file:///mod.ts: EsModuleInfo { } == symbol deps (types and exprs) == 1:0..77 [Id(("TypeParam", #2)), Id(("Default", #2)), Id(("Param", #2)), Id(("Return", #2))] -6:201..295 [Id(("OverloadTypeParam", #2)), Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] -6:334..428 [Id(("PrivateTypeParam", #2)), Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] +8:201..295 [Id(("OverloadTypeParam", #2)), Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] +8:334..428 [Id(("PrivateTypeParam", #2)), Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] == symbol deps (types only) == 1:0..77 [Id(("TypeParam", #2)), Id(("Default", #2)), Id(("Param", #2)), Id(("Return", #2))] -6:201..295 [Id(("OverloadTypeParam", #2)), Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] -6:334..428 [Id(("PrivateTypeParam", #2)), Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] +8:201..295 [Id(("OverloadTypeParam", #2)), Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] +8:334..428 [Id(("PrivateTypeParam", #2)), Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] == export definitions == [test]: file:///mod.ts:0..77 diff --git a/tests/specs/symbols/class_private_ctor_param_props.txt b/tests/specs/symbols/class_private_ctor_param_props.txt index 482502cb8..fee42f42b 100644 --- a/tests/specs/symbols/class_private_ctor_param_props.txt +++ b/tests/specs/symbols/class_private_ctor_param_props.txt @@ -23,14 +23,18 @@ file:///mod.ts: EsModuleInfo { "Class", #2, ): 1, + ( + "regularParam", + #3, + ): 5, ( "PublicClass", #2, - ): 5, + ): 6, ( "PrivateClass", #2, - ): 6, + ): 7, }, symbols: { 0: Symbol { @@ -59,8 +63,8 @@ file:///mod.ts: EsModuleInfo { ], child_ids: { 1, - 5, 6, + 7, }, exports: { "Class": 1, @@ -199,6 +203,36 @@ file:///mod.ts: EsModuleInfo { 0, ), symbol_id: 5, + parent_id: Some( + 2, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "regularParam", + ), + ), + range: SourceRange { + start: SourcePos( + 119, + ), + end: SourcePos( + 131, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 6: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 6, parent_id: Some( 0, ), @@ -224,11 +258,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 6: Symbol { + 7: Symbol { module_id: ModuleId( 0, ), - symbol_id: 6, + symbol_id: 7, parent_id: Some( 0, ), diff --git a/tests/specs/symbols/declaration_merging01.txt b/tests/specs/symbols/declaration_merging01.txt index 24283e0d1..54406b522 100644 --- a/tests/specs/symbols/declaration_merging01.txt +++ b/tests/specs/symbols/declaration_merging01.txt @@ -48,14 +48,18 @@ file:///mod.ts: EsModuleInfo { "mixColor", #4, ): 4, + ( + "colorName", + #5, + ): 5, ( "Test", #2, - ): 5, + ): 6, ( "other", #7, - ): 6, + ): 7, }, symbols: { 0: Symbol { @@ -85,12 +89,12 @@ file:///mod.ts: EsModuleInfo { child_ids: { 1, 3, - 5, + 6, }, exports: { - "Album": 7, - "Color": 8, - "Test": 9, + "Album": 8, + "Color": 9, + "Test": 10, }, members: {}, }, @@ -259,6 +263,36 @@ file:///mod.ts: EsModuleInfo { 0, ), symbol_id: 5, + parent_id: Some( + 4, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "colorName", + ), + ), + range: SourceRange { + start: SourcePos( + 145, + ), + end: SourcePos( + 154, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 6: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 6, parent_id: Some( 0, ), @@ -297,20 +331,20 @@ file:///mod.ts: EsModuleInfo { }, ], child_ids: { - 6, + 7, }, exports: { - "other": 6, + "other": 7, }, members: {}, }, - 6: Symbol { + 7: Symbol { module_id: ModuleId( 0, ), - symbol_id: 6, + symbol_id: 7, parent_id: Some( - 5, + 6, ), decls: [ SymbolDecl { @@ -334,11 +368,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 7: Symbol { + 8: Symbol { module_id: ModuleId( 0, ), - symbol_id: 7, + symbol_id: 8, parent_id: Some( 0, ), @@ -365,11 +399,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 8: Symbol { + 9: Symbol { module_id: ModuleId( 0, ), - symbol_id: 8, + symbol_id: 9, parent_id: Some( 0, ), @@ -396,11 +430,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 9: Symbol { + 10: Symbol { module_id: ModuleId( 0, ), - symbol_id: 9, + symbol_id: 10, parent_id: Some( 0, ), diff --git a/tests/specs/symbols/function_overloads01.txt b/tests/specs/symbols/function_overloads01.txt index 0b616fddd..91118481b 100644 --- a/tests/specs/symbols/function_overloads01.txt +++ b/tests/specs/symbols/function_overloads01.txt @@ -26,18 +26,42 @@ file:///mod.ts: EsModuleInfo { "test", #2, ): 1, + ( + "value", + #3, + ): 2, + ( + "value", + #4, + ): 3, + ( + "value", + #5, + ): 4, ( "Inner", #2, - ): 2, + ): 5, ( "inner", #6, - ): 3, + ): 6, + ( + "value", + #7, + ): 7, + ( + "value", + #8, + ): 8, + ( + "value", + #9, + ): 9, ( "InnerInner", #2, - ): 4, + ): 10, }, symbols: { 0: Symbol { @@ -66,11 +90,11 @@ file:///mod.ts: EsModuleInfo { ], child_ids: { 1, - 2, + 5, }, exports: { "test": 1, - "InnerInner": 5, + "InnerInner": 11, }, members: {}, }, @@ -141,6 +165,96 @@ file:///mod.ts: EsModuleInfo { 0, ), symbol_id: 2, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "value", + ), + ), + range: SourceRange { + start: SourcePos( + 21, + ), + end: SourcePos( + 26, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 3: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 3, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "value", + ), + ), + range: SourceRange { + start: SourcePos( + 58, + ), + end: SourcePos( + 63, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 4: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 4, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "value", + ), + ), + range: SourceRange { + start: SourcePos( + 95, + ), + end: SourcePos( + 100, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 5: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 5, parent_id: Some( 0, ), @@ -163,20 +277,20 @@ file:///mod.ts: EsModuleInfo { }, ], child_ids: { - 3, + 6, }, exports: { - "inner": 3, + "inner": 6, }, members: {}, }, - 3: Symbol { + 6: Symbol { module_id: ModuleId( 0, ), - symbol_id: 3, + symbol_id: 6, parent_id: Some( - 2, + 5, ), decls: [ SymbolDecl { @@ -232,11 +346,101 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 4: Symbol { + 7: Symbol { module_id: ModuleId( 0, ), - symbol_id: 4, + symbol_id: 7, + parent_id: Some( + 6, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "value", + ), + ), + range: SourceRange { + start: SourcePos( + 166, + ), + end: SourcePos( + 171, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 8: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 8, + parent_id: Some( + 6, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "value", + ), + ), + range: SourceRange { + start: SourcePos( + 206, + ), + end: SourcePos( + 211, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 9: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 9, + parent_id: Some( + 6, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "value", + ), + ), + range: SourceRange { + start: SourcePos( + 246, + ), + end: SourcePos( + 251, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 10: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 10, parent_id: Some( 0, ), @@ -266,11 +470,11 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, - 5: Symbol { + 11: Symbol { module_id: ModuleId( 0, ), - symbol_id: 5, + symbol_id: 11, parent_id: Some( 0, ), diff --git a/tests/specs/symbols/ts_namespace_export01.txt b/tests/specs/symbols/ts_namespace_export01.txt index c5109ed77..2bc65d0e0 100644 --- a/tests/specs/symbols/ts_namespace_export01.txt +++ b/tests/specs/symbols/ts_namespace_export01.txt @@ -19,6 +19,10 @@ file:///mod.ts: EsModuleInfo { "isPrime", #2, ): 1, + ( + "x", + #3, + ): 2, }, symbols: { 0: Symbol { @@ -83,6 +87,36 @@ file:///mod.ts: EsModuleInfo { exports: {}, members: {}, }, + 2: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 2, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "x", + ), + ), + range: SourceRange { + start: SourcePos( + 24, + ), + end: SourcePos( + 25, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, }, } == export definitions == From a0d48de7514e3d83df067ac2cc0e28994325837b Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Fri, 17 May 2024 15:13:42 +0200 Subject: [PATCH 2/4] feat(fast_check): syntax based type inference This commit updates fast check to perform syntax based type inference on variable initalizers and function return values. The inferred types are then used as variable declaration type annotations, or function return type annotations respectively. This is a first step in removing leavable types in favor of an aligned `.d.ts` and `.ts` emit. This commit also adds a slew of new diagnostic information to help users understand why a type was unable to be inferred. It gives more helpful messages than the previous "add an explicit type" messages. TODO: - more tests - finalize the error for referring to local variables --- Cargo.lock | 100 +- Cargo.toml | 3 + src/fast_check/mod.rs | 240 ++-- src/fast_check/swc_helpers.rs | 129 +- src/fast_check/transform.rs | 287 ++-- src/fast_check/transform_dts.rs | 8 +- src/fast_check/type_infer.rs | 1239 +++++++++++++++++ src/lib.rs | 2 + src/swc_helpers.rs | 3 +- src/symbols/dep_analyzer.rs | 53 + .../graph/fast_check/cache__diagnostic.txt | 14 +- .../fast_check/cache__diagnostic_deep.txt | 14 +- .../cache__diagnostic_multiple_exports.txt | 28 +- .../cache__diagnostic_then_nested_dep.txt | 14 +- ...s_member_ref_additional_prop_on_member.txt | 3 + .../fast_check/class_member_ref_not_found.txt | 3 + .../graph/fast_check/class_properties.txt | 4 +- .../class_static_member_ref_not_found.txt | 3 + .../fast_check/class_super_unsupported.txt | 1 + tests/specs/graph/fast_check/comments.txt | 2 +- .../const_infer_function_missing_return.txt | 14 +- .../cross_file_ref_module_not_found.txt | 4 + .../fast_check/default_export_expr_var.txt | 2 +- tests/specs/graph/fast_check/enums.txt | 4 +- .../specs/graph/fast_check/global_module.txt | 2 + .../graph/fast_check/inference/basic.txt | 160 +++ .../fast_check/inference/const_assertion.txt | 87 ++ .../graph/fast_check/inference/error.txt | 359 +++++ .../fast_check/inferred_unique_symbols.txt | 4 +- .../inferred_unique_symbols__not_global.txt | 13 +- .../fast_check/init_idents_and_members.txt | 40 +- tests/specs/graph/fast_check/issue_22544.txt | 8 +- tests/specs/graph/fast_check/issue_22829.txt | 68 +- .../specs/graph/fast_check/issue_23658_1.txt | 52 +- .../specs/graph/fast_check/issue_23658_2.txt | 75 +- .../graph/fast_check/missing_return_type.txt | 14 +- .../missing_return_type_generator.txt | 10 +- tests/specs/graph/fast_check/ref_obj_type.txt | 18 +- .../graph/fast_check/ref_var_no_type.txt | 10 +- tests/specs/graph/fast_check/ref_var_type.txt | 2 +- .../fast_check/return_inference/empty.txt | 21 +- .../return_inference/generator_err.txt | 102 ++ .../multiple_value_return_err.txt | 76 +- .../return_inference/multiple_void_return.txt | 25 +- .../fast_check/return_inference/no_return.txt | 3 +- .../return_inference/no_return_err.txt | 61 +- .../return_inference/value_return.txt | 85 ++ .../return_inference/value_return_err.txt | 110 +- .../return_inference/void_return.txt | 23 +- .../specs/graph/fast_check/test_test_test.txt | 54 + tests/specs/graph/fast_check/ts_as_const.txt | 52 +- .../graph/fast_check/ts_const_assertion.txt | 31 +- .../specs/graph/fast_check/type_assertion.txt | 10 +- .../unsupported_default_export_expr.txt | 2 + .../unsupported_private_member_ref.txt | 1 + .../graph/fast_check/var_arrow_leavable.txt | 4 +- .../fast_check/var_function_leavable.txt | 8 +- tests/specs/graph/fast_check/vars.txt | 119 +- tests/specs/graph/jsr/dynamic_import.txt | 2 +- tests/specs_test.rs | 3 +- 60 files changed, 3068 insertions(+), 820 deletions(-) create mode 100644 src/fast_check/type_infer.rs create mode 100644 tests/specs/graph/fast_check/inference/basic.txt create mode 100644 tests/specs/graph/fast_check/inference/const_assertion.txt create mode 100644 tests/specs/graph/fast_check/inference/error.txt create mode 100644 tests/specs/graph/fast_check/return_inference/generator_err.txt create mode 100644 tests/specs/graph/fast_check/return_inference/value_return.txt create mode 100644 tests/specs/graph/fast_check/test_test_test.txt diff --git a/Cargo.lock b/Cargo.lock index 057da0195..18abc0270 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "ast_node" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e3e06ec6ac7d893a0db7127d91063ad7d9da8988f8a1a256f03729e6eec026" +checksum = "2ab31376d309dd3bfc9cfb3c11c93ce0e0741bbe0354b20e7f8c60b044730b79" dependencies = [ "proc-macro2", "quote", @@ -279,9 +279,7 @@ dependencies = [ [[package]] name = "deno_ast" -version = "0.38.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2417aad5382d10d035e46d35f2f5fbbb93a922816408245ee585e7ca775194" +version = "0.38.2" dependencies = [ "anyhow", "base64 0.21.7", @@ -512,9 +510,9 @@ dependencies = [ [[package]] name = "from_variant" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a0b11eeb173ce52f84ebd943d42e58813a2ebb78a6a3ff0a243b71c5199cd7b" +checksum = "fdc9cc75639b041067353b9bce2450d6847e547276c6fbe4487d7407980e07db" dependencies = [ "proc-macro2", "swc_macros_common", @@ -1432,9 +1430,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "indexmap", "itoa", @@ -1579,9 +1577,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_enum" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b650ea2087d32854a0f20b837fc56ec987a1cb4f758c9757e1171ee9812da63" +checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90" dependencies = [ "proc-macro2", "quote", @@ -1623,9 +1621,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.33.25" +version = "0.33.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a529796c240cd87da18d26d63f9de4c7ad3680cf0a04b95f0c37f4c4f0a0da63" +checksum = "a2f9706038906e66f3919028f9f7a37f3ed552f1b85578e93f4468742e2da438" dependencies = [ "ast_node", "better_scoped_tls", @@ -1649,9 +1647,9 @@ dependencies = [ [[package]] name = "swc_config" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada712ac5e28a301683c8af957e8a56deca675cbc376473dd207a527b989efb5" +checksum = "7be1a689e146be1eae53139482cb061dcf0fa01dff296bbe7b96fff92d8e2936" dependencies = [ "anyhow", "indexmap", @@ -1663,9 +1661,9 @@ dependencies = [ [[package]] name = "swc_config_macro" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2574f75082322a27d990116cd2a24de52945fc94172b24ca0b3e9e2a6ceb6b" +checksum = "7c5f56139042c1a95b54f5ca48baa0e0172d369bcc9d3d473dad1de36bae8399" dependencies = [ "proc-macro2", "quote", @@ -1675,9 +1673,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.113.0" +version = "0.113.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99fdda741656887f4cf75c1cee249a5f0374d67d30acc2b073182e902546ff2" +checksum = "dc1690cc0c9ab60b44ac0225ba1e231ac532f7ba1d754df761c6ee607561afae" dependencies = [ "bitflags 2.5.0", "is-macro", @@ -1693,9 +1691,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.149.0" +version = "0.149.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c21b8ae99bc3b95c6f7909915cd1e5994bec4e5b576f2e2a6879e56f2770760" +checksum = "4fef147127a2926ca26171c7afcbf028ff86dc543ced87d316713f25620a15b9" dependencies = [ "memchr", "num-bigint", @@ -1712,9 +1710,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen_macros" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ab87ba81ae05efd394ab4a8cbdba595ac3554a5e393c76699449d47c43582e" +checksum = "090e409af49c8d1a3c13b3aab1ed09dd4eda982207eb3e63c2ad342f072b49c8" dependencies = [ "proc-macro2", "quote", @@ -1724,9 +1722,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.45.27" +version = "0.45.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a923880fc27cf5f3d2a684debb7c5a0ee60100af1bfe424cb5e722d290bf88a" +checksum = "92c68f934bd2c51f29c4ad0bcae09924e9dc30d7ce0680367d45b42d40338a67" dependencies = [ "anyhow", "pathdiff", @@ -1738,9 +1736,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.144.0" +version = "0.144.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da9f3a58f0a64410f4006eb1fdb64d190ad3cc6cd12a7bf1f0dbb916e4ca4c7" +checksum = "0499e69683ae5d67a20ff0279b94bc90f29df7922a46331b54d5dd367bf89570" dependencies = [ "either", "new_debug_unreachable", @@ -1760,9 +1758,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.138.0" +version = "0.138.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91771e358664649cf2cabec86a270bd9ce267f5213f299cacb255951b5edf06b" +checksum = "eddb95c2bdad1c9c29edf35712e1e0f9b9ddc1cdb5ba2d582fd93468cb075a03" dependencies = [ "better_scoped_tls", "bitflags 2.5.0", @@ -1783,9 +1781,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.127.0" +version = "0.127.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa5cd60314f35a114dc85955c6b645e9bb13fdfda4f732137ed62a482ca8990" +checksum = "53043d81678f3c693604eeb1d1f0fe6ba10f303104a31b954dbeebed9cadf530" dependencies = [ "swc_atoms", "swc_common", @@ -1797,9 +1795,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_macros" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e309b88f337da54ef7fe4c5b99c2c522927071f797ee6c9fb8b6bf2d100481" +checksum = "500a1dadad1e0e41e417d633b3d6d5de677c9e0d3159b94ba3348436cdb15aab" dependencies = [ "proc-macro2", "quote", @@ -1809,9 +1807,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.172.0" +version = "0.172.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "067a79cba791af32fb0634c0ca08051e47b56ccfe8d07349b7787b22da401084" +checksum = "7fbc414d6a9c5479cfb4c6e92fcdac504582bd7bc89a0ed7f8808b72dc8bd1f0" dependencies = [ "either", "rustc-hash", @@ -1829,9 +1827,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.184.0" +version = "0.184.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69c87599f4a10987fe2687967e5448858b458f2924faa62f044acd56f4e3ffda" +checksum = "565a76c4ca47ce31d78301c0beab878e4c2cb4f624691254d834ec8c0e236755" dependencies = [ "base64 0.21.7", "dashmap", @@ -1853,9 +1851,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.189.0" +version = "0.189.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ea0dc9076708448e8ded8e1119717e0e6e095b1ba42b4c8d7fdb1d26fba418" +checksum = "e209026c1d3c577cafac257d87e7c0d23119282fbdc8ed03d7f56077e95beb90" dependencies = [ "ryu-js", "serde", @@ -1870,9 +1868,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.128.0" +version = "0.128.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd533f5751b7a8673bd843151c4e6e64a2dcf6c1f65331401e88f244c0e85de7" +checksum = "fe5242670bc74e0a0b64b9d4912b37be36944517ce0881314162aeb4381272c3" dependencies = [ "indexmap", "num_cpus", @@ -1888,9 +1886,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.99.0" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c74008ebc5e0d3d9a1b3df54083ddbff1a375cfadff857da1fdc7837b48c52d" +checksum = "28a6ce28ad8e591f8d627f1f9cb26b25e5d83052a9bc1b674d95fc28040cfa98" dependencies = [ "num-bigint", "swc_atoms", @@ -1913,9 +1911,9 @@ dependencies = [ [[package]] name = "swc_macros_common" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5be7766a95a2840ded618baeaab63809b71230ef19094b34f76c8af4d85aa2" +checksum = "91745f3561057493d2da768437c427c0e979dff7396507ae02f16c981c4a8466" dependencies = [ "proc-macro2", "quote", @@ -1924,9 +1922,9 @@ dependencies = [ [[package]] name = "swc_visit" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0263be55289abfe9c877ffef83d877b5bdfac036ffe2de793f48f5e47e41dbae" +checksum = "043d11fe683dcb934583ead49405c0896a5af5face522e4682c16971ef7871b9" dependencies = [ "either", "swc_visit_macros", @@ -1934,9 +1932,9 @@ dependencies = [ [[package]] name = "swc_visit_macros" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fc817055fe127b4285dc85058596768bfde7537ae37da82c67815557f03e33" +checksum = "4ae9ef18ff8daffa999f729db056d2821cd2f790f3a11e46422d19f46bb193e7" dependencies = [ "Inflector", "proc-macro2", @@ -2199,9 +2197,9 @@ checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" [[package]] name = "unicode-id-start" -version = "1.1.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f73150333cb58412db36f2aca8f2875b013049705cc77b94ded70a1ab1f5da" +checksum = "02aebfa694eccbbbffdd92922c7de136b9fe764396d2f10e21bce1681477cfc1" [[package]] name = "unicode-ident" diff --git a/Cargo.toml b/Cargo.toml index a4a4913a9..3af54f594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,3 +73,6 @@ codegen-units = 1 incremental = true lto = true opt-level = "z" + +[patch.crates-io] +deno_ast = { path = "/mnt/artemis/Projects/github.com/denoland/deno_ast" } \ No newline at end of file diff --git a/src/fast_check/mod.rs b/src/fast_check/mod.rs index 9b1cc3e1b..975d2a9ed 100644 --- a/src/fast_check/mod.rs +++ b/src/fast_check/mod.rs @@ -25,6 +25,7 @@ mod swc_helpers; mod transform; #[cfg(feature = "fast_check")] mod transform_dts; +mod type_infer; pub use cache::FastCheckCache; pub use cache::FastCheckCacheItem; @@ -39,6 +40,9 @@ pub use transform::FastCheckModule; #[cfg(feature = "fast_check")] pub use transform::TransformOptions; +use self::type_infer::ExprInferFailCause; +use self::type_infer::ReturnTypeInferFailCause; + #[derive(Clone)] pub struct FastCheckDiagnosticRange { pub specifier: ModuleSpecifier, @@ -46,6 +50,15 @@ pub struct FastCheckDiagnosticRange { pub text_info: SourceTextInfo, } +impl FastCheckDiagnosticRange { + fn into_diagnostic_range(&self) -> DiagnosticSourceRange { + DiagnosticSourceRange { + start: DiagnosticSourcePos::SourcePos(self.range.start), + end: DiagnosticSourcePos::SourcePos(self.range.end), + } + } +} + impl std::fmt::Debug for FastCheckDiagnosticRange { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FastCheckDiagnosticRange") @@ -84,11 +97,15 @@ pub enum FastCheckDiagnostic { }, #[error("missing explicit type in the public API")] MissingExplicitType { range: FastCheckDiagnosticRange }, - #[error("missing explicit return type in the public API")] - MissingExplicitReturnType { - range: FastCheckDiagnosticRange, - is_definitely_void_or_never: bool, - is_async: bool, + #[error("unknown type for variable in the public API")] + UnknownVarType { + ident: FastCheckDiagnosticRange, + cause: Option>, + }, + #[error("unknown return type for function in the public API")] + UnknownReturnType { + ident: FastCheckDiagnosticRange, + cause: Option>, }, #[error( "found an ambient module, which is a global augmentation, which are not unsupported" @@ -158,51 +175,14 @@ pub enum FastCheckDiagnostic { Cached { specifier: ModuleSpecifier }, } -impl FastCheckDiagnostic { - /// Return a human readable description of what the range of the diagnostic - /// is. - /// - /// Panics if the diagnostic does not have a range. - pub fn range_description(&self) -> Option<&'static str> { - use FastCheckDiagnostic::*; - match self { - NotFoundReference { .. } => Some("this is the reference"), - MissingExplicitType { .. } => { - Some("this symbol is missing an explicit type") - } - MissingExplicitReturnType { .. } => { - Some("this function is missing an explicit return type") - } - UnsupportedAmbientModule { .. } => None, - UnsupportedComplexReference { .. } => Some("this is the reference"), - UnsupportedDefaultExportExpr { .. } => None, - UnsupportedDestructuring { .. } => None, - UnsupportedExpandoProperty { .. } => None, - UnsupportedGlobalModule { .. } => None, - UnsupportedRequire { .. } => None, - UnsupportedPrivateMemberReference { .. } => Some("this is the reference"), - UnsupportedSuperClassExpr { .. } => { - Some("this is the superclass expression") - } - UnsupportedTsExportAssignment { .. } => None, - UnsupportedTsNamespaceExport { .. } => None, - UnsupportedUsing { .. } => None, - UnsupportedNestedJavaScript { .. } => None, - UnsupportedJavaScriptEntrypoint { .. } => None, - Emit { .. } => None, - ExportNotFound { .. } => None, - Cached { .. } => None, - } - } -} - impl FastCheckDiagnostic { pub fn specifier(&self) -> &ModuleSpecifier { use FastCheckDiagnostic::*; match self { NotFoundReference { range, .. } => &range.specifier, - MissingExplicitType { range } => &range.specifier, - MissingExplicitReturnType { range, .. } => &range.specifier, + MissingExplicitType { range, .. } => &range.specifier, + UnknownVarType { ident, .. } => &ident.specifier, + UnknownReturnType { ident, .. } => &ident.specifier, UnsupportedAmbientModule { range } => &range.specifier, UnsupportedComplexReference { range, .. } => &range.specifier, UnsupportedDefaultExportExpr { range } => &range.specifier, @@ -227,8 +207,9 @@ impl FastCheckDiagnostic { use FastCheckDiagnostic::*; match self { NotFoundReference { range, .. } => Some(range), - MissingExplicitType { range } => Some(range), - MissingExplicitReturnType { range, .. } => Some(range), + MissingExplicitType { range, .. } => Some(range), + UnknownVarType { ident, .. } => Some(ident), + UnknownReturnType { ident: range, .. } => Some(range), UnsupportedAmbientModule { range } => Some(range), UnsupportedComplexReference { range, .. } => Some(range), UnsupportedDefaultExportExpr { range } => Some(range), @@ -256,7 +237,8 @@ impl deno_ast::diagnostics::Diagnostic for FastCheckDiagnostic { match self { NotFoundReference { .. } | MissingExplicitType { .. } - | MissingExplicitReturnType { .. } + | UnknownVarType { .. } + | UnknownReturnType { .. } | UnsupportedAmbientModule { .. } | UnsupportedComplexReference { .. } | UnsupportedDefaultExportExpr { .. } @@ -283,7 +265,8 @@ impl deno_ast::diagnostics::Diagnostic for FastCheckDiagnostic { Cow::Borrowed(match self { NotFoundReference { .. } => "not-found-reference", MissingExplicitType { .. } => "missing-explicit-type", - MissingExplicitReturnType { .. } => "missing-explicit-return-type", + UnknownVarType { .. } => "unknown-var-type", + UnknownReturnType { .. } => "unknown-return-type", UnsupportedAmbientModule { .. } => "unsupported-ambient-module", UnsupportedComplexReference { .. } => "unsupported-complex-reference", UnsupportedDefaultExportExpr { .. } => "unsupported-default-export-expr", @@ -327,18 +310,110 @@ impl deno_ast::diagnostics::Diagnostic for FastCheckDiagnostic { } } - fn snippet(&self) -> Option> { - self.range().map(|range| DiagnosticSnippet { - source: Cow::Borrowed(&range.text_info), - highlight: DiagnosticSnippetHighlight { - style: DiagnosticSnippetHighlightStyle::Error, - range: DiagnosticSourceRange { - start: DiagnosticSourcePos::SourcePos(range.range.start), - end: DiagnosticSourcePos::SourcePos(range.range.end), - }, - description: self.range_description().map(Cow::Borrowed), - }, - }) + fn snippet(&self) -> Option> { + fn simple<'a>( + range: &'a FastCheckDiagnosticRange, + description: Option<&'static str>, + ) -> DiagnosticSnippet<'a> { + DiagnosticSnippet { + source: Cow::Borrowed(&range.text_info), + highlights: vec![DiagnosticSnippetHighlight { + style: DiagnosticSnippetHighlightStyle::Error, + range: range.into_diagnostic_range(), + description: description.map(Cow::Borrowed), + }], + } + } + + match self { + FastCheckDiagnostic::NotFoundReference { range, .. } => { + Some(simple(range, Some("this is the reference"))) + } + FastCheckDiagnostic::MissingExplicitType { range } => Some(simple( + range, + Some("this symbol is missing an explicit type"), + )), + FastCheckDiagnostic::UnknownVarType { ident, cause } => { + if let Some(cause) = cause { + let mut highlights = vec![ + DiagnosticSnippetHighlight { + style: DiagnosticSnippetHighlightStyle::Error, + range: ident.into_diagnostic_range(), + description: Some(Cow::Borrowed("this variable's type could not be inferred because its initializer")), + }, + ]; + cause.highlights(&mut highlights); + + Some(DiagnosticSnippet { + source: Cow::Borrowed(&ident.text_info), + highlights, + }) + } else { + Some(simple( + ident, + Some("this variable is missing an explicit type"), + )) + } + } + FastCheckDiagnostic::UnknownReturnType { ident, cause } => { + if let Some(cause) = cause { + let mut highlights = vec![]; + cause.highlights(&mut highlights, ident, "this function", false); + Some(DiagnosticSnippet { + source: Cow::Borrowed(&ident.text_info), + highlights, + }) + } else { + Some(simple( + ident, + Some("this function is missing an explicit return type"), + )) + } + } + FastCheckDiagnostic::UnsupportedAmbientModule { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedComplexReference { range, .. } => { + Some(simple(range, Some("this is the reference"))) + } + FastCheckDiagnostic::UnsupportedDefaultExportExpr { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedDestructuring { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedExpandoProperty { range, .. } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedGlobalModule { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedRequire { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedPrivateMemberReference { + range, .. + } => Some(simple(range, Some("this is the reference"))), + FastCheckDiagnostic::UnsupportedSuperClassExpr { range } => { + Some(simple(range, Some("this is the superclass expression"))) + } + FastCheckDiagnostic::UnsupportedTsExportAssignment { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedTsNamespaceExport { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedUsing { range } => { + Some(simple(range, None)) + } + FastCheckDiagnostic::UnsupportedNestedJavaScript { .. } => None, + FastCheckDiagnostic::UnsupportedJavaScriptEntrypoint { .. } => None, + FastCheckDiagnostic::Emit { .. } => None, + FastCheckDiagnostic::ExportNotFound { .. } => None, + FastCheckDiagnostic::Cached { .. } => { + unreachable!("cached diagnostics should not be displayed") + } + } } fn hint(&self) -> Option> { @@ -350,11 +425,16 @@ impl deno_ast::diagnostics::Diagnostic for FastCheckDiagnostic { MissingExplicitType { .. } => { Cow::Borrowed("add an explicit type annotation to the symbol") } - MissingExplicitReturnType { is_definitely_void_or_never, is_async, .. } => { - if *is_definitely_void_or_never { - Cow::Borrowed("add an explicit return type of 'void' or 'never' to the function") - } else if *is_async { - Cow::Borrowed("add an explicit return type of 'Promise' or 'Promise' to the function") + UnknownVarType { cause, .. } => { + if let Some(hint) = cause.as_ref().and_then(|cause| cause.hint(" in the variable declaration initializer".into())) { + Cow::Owned(hint) + } else { + Cow::Borrowed("add an explicit type annotation to the variable declaration") + } + } + UnknownReturnType { cause, .. } => { + if let Some(hint) = cause.as_ref().and_then(|cause|cause.hint("function", "".into())) { + Cow::Owned(hint) } else { Cow::Borrowed("add an explicit return type to the function") } @@ -384,9 +464,7 @@ impl deno_ast::diagnostics::Diagnostic for FastCheckDiagnostic { }) } - fn snippet_fixed( - &self, - ) -> Option> { + fn snippet_fixed(&self) -> Option> { None } @@ -399,15 +477,21 @@ impl deno_ast::diagnostics::Diagnostic for FastCheckDiagnostic { MissingExplicitType { .. } => Cow::Borrowed(&[ Cow::Borrowed("all symbols in the public API must have an explicit type") ]), - MissingExplicitReturnType { is_definitely_void_or_never, is_async, .. } => { - let mut lines = vec![Cow::Borrowed("all functions in the public API must have an explicit return type")]; - if *is_definitely_void_or_never { - if *is_async { - lines.push(Cow::Borrowed("async function expressions without a return statement can have a return type of either 'Promise' or 'Promise'")); - } else { - lines.push(Cow::Borrowed("function expressions without a return statement can have a return type of either 'void' or 'never'")); - } - lines.push(Cow::Borrowed("this function has no return statements, so a return type could not be inferred automatically")); + UnknownVarType { cause, .. } => { + let mut lines = vec![Cow::Borrowed("all variables in the public API must have a known type")]; + if let Some(cause) = cause { + cause.info(&mut lines); + } else { + lines.push(Cow::Borrowed("variables without an initializer can not infer a type")); + } + Cow::Owned(lines) + }, + UnknownReturnType { cause, .. } => { + let mut lines = vec![Cow::Borrowed("all functions in the public API must have an known return type")]; + if let Some(cause) = cause { + cause.info(&mut lines, "a function declaration"); + } else { + lines.push(Cow::Borrowed("functions without a body can not infer a return type from the return value")); } Cow::Owned(lines) }, diff --git a/src/fast_check/swc_helpers.rs b/src/fast_check/swc_helpers.rs index 5b501eb65..79259b86b 100644 --- a/src/fast_check/swc_helpers.rs +++ b/src/fast_check/swc_helpers.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. MIT license. use deno_ast::swc::ast::*; +use deno_ast::swc::common::SyntaxContext; use deno_ast::swc::common::DUMMY_SP; pub fn ident(name: String) -> Ident { @@ -75,28 +76,122 @@ pub fn ts_tuple_element(ts_type: TsType) -> TsTupleElement { } } -pub fn maybe_lit_to_ts_type_const(lit: &Lit) -> Option { +pub fn lit_to_ts_type_const(lit: &Lit) -> TsType { match lit { - Lit::Str(lit_str) => Some(ts_lit_type(TsLit::Str(lit_str.clone()))), - Lit::Bool(lit_bool) => Some(ts_lit_type(TsLit::Bool(*lit_bool))), - Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)), - Lit::Num(lit_num) => Some(ts_lit_type(TsLit::Number(lit_num.clone()))), - Lit::BigInt(lit_bigint) => { - Some(ts_lit_type(TsLit::BigInt(lit_bigint.clone()))) + Lit::Str(lit_str) => ts_lit_type(TsLit::Str(lit_str.clone())), + Lit::Bool(lit_bool) => ts_lit_type(TsLit::Bool(*lit_bool)), + Lit::Null(_) => ts_keyword_type(TsKeywordTypeKind::TsNullKeyword), + Lit::Num(lit_num) => ts_lit_type(TsLit::Number(lit_num.clone())), + Lit::BigInt(lit_bigint) => ts_lit_type(TsLit::BigInt(lit_bigint.clone())), + Lit::Regex(_) => regex_type(), + Lit::JSXText(_) => { + unreachable!("jsx text can only happen inside of jsx elements") } - Lit::Regex(_) => Some(regex_type()), - Lit::JSXText(_) => None, } } -pub fn maybe_lit_to_ts_type(lit: &Lit) -> Option { +pub fn lit_to_ts_type(lit: &Lit) -> TsType { match lit { - Lit::Str(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)), - Lit::Bool(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)), - Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)), - Lit::Num(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)), - Lit::BigInt(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)), - Lit::Regex(_) => Some(regex_type()), - Lit::JSXText(_) => None, + Lit::Str(_) => ts_keyword_type(TsKeywordTypeKind::TsStringKeyword), + Lit::Bool(_) => ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword), + Lit::Null(_) => ts_keyword_type(TsKeywordTypeKind::TsNullKeyword), + Lit::Num(_) => ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword), + Lit::BigInt(_) => ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword), + Lit::Regex(_) => regex_type(), + Lit::JSXText(_) => { + unreachable!("jsx text can only happen inside of jsx elements") + } + } +} + +pub fn prop_name_to_ts_key( + prop_name: &PropName, +) -> (Box, /* computed */ bool) { + match prop_name { + PropName::Ident(ident) => (Box::new(Expr::Ident(ident.clone())), false), + PropName::Str(str) => match Ident::verify_symbol(str.value.as_str()) { + Ok(_) => ( + Box::new(Expr::Ident(Ident::new(str.value.clone(), str.span))), + false, + ), + Err(_) => (Box::new(Expr::Lit(Lit::Str(str.clone()))), false), + }, + PropName::Num(num) => (Box::new(Expr::Lit(Lit::Num(num.clone()))), false), + PropName::Computed(expr) => { + // We leave the expression as is. See also https://github.com/microsoft/TypeScript/issues/58533 + (expr.expr.clone(), true) + } + PropName::BigInt(bigint) => { + (Box::new(Expr::Lit(Lit::BigInt(bigint.clone()))), false) + } + } +} + +pub fn void_or_promise_void(is_async: bool) -> Box { + let void_type = Box::new(ts_keyword_type(TsKeywordTypeKind::TsVoidKeyword)); + if is_async { + Box::new(TsType::TsTypeRef(TsTypeRef { + span: DUMMY_SP, + type_name: TsEntityName::Ident(Ident::new("Promise".into(), DUMMY_SP)), + type_params: Some(Box::new(TsTypeParamInstantiation { + span: DUMMY_SP, + params: vec![void_type], + })), + })) + } else { + void_type + } +} + +pub fn is_param_pat_optional(pat: &Pat) -> bool { + match pat { + Pat::Ident(ident) => ident.optional, + Pat::Array(a) => a.optional, + Pat::Object(o) => o.optional, + Pat::Assign(_) | Pat::Rest(_) => true, + Pat::Invalid(_) | Pat::Expr(_) => false, + } +} + +/// Looks if the call expr is `Symbol("example")` or `Symbol.for("example")` +pub fn is_call_expr_symbol_create( + call_expr: &CallExpr, + unresolved_context: SyntaxContext, +) -> bool { + let Some(expr) = call_expr.callee.as_expr() else { + return false; + }; + let (expr_ident, is_for) = match &**expr { + Expr::Ident(ident) => (ident, false), + Expr::Member(member_expr) => { + let Some(ident) = member_expr.obj.as_ident() else { + return false; + }; + let Some(prop_ident) = member_expr.prop.as_ident() else { + return false; + }; + if prop_ident.sym != "for" { + return false; + } + (ident, true) + } + _ => return false, + }; + + let is_symbol_global = + expr_ident.sym == "Symbol" && expr_ident.to_id().1 == unresolved_context; + if !is_symbol_global { + return false; + } + if !is_for && call_expr.args.is_empty() { + return true; + } + if call_expr.args.len() != 1 { + return false; } + let Some(arg_lit) = call_expr.args.first().and_then(|a| a.expr.as_lit()) + else { + return false; + }; + matches!(arg_lit, Lit::Str(_)) } diff --git a/src/fast_check/transform.rs b/src/fast_check/transform.rs index 6a9bc6323..918310a37 100644 --- a/src/fast_check/transform.rs +++ b/src/fast_check/transform.rs @@ -25,9 +25,7 @@ use deno_ast::SourceRange; use deno_ast::SourceRangedForSpanned; use indexmap::IndexMap; -use crate::swc_helpers::analyze_return_stmts_in_function_body; use crate::swc_helpers::FunctionKind; -use crate::swc_helpers::ReturnStatementAnalysis; use crate::symbols::EsModuleInfo; use crate::symbols::ExpandoPropertyRef; use crate::symbols::Symbol; @@ -40,10 +38,12 @@ use super::range_finder::ModulePublicRanges; use super::swc_helpers::any_type_ann; use super::swc_helpers::ident; use super::swc_helpers::is_void_type; -use super::swc_helpers::maybe_lit_to_ts_type; +use super::swc_helpers::lit_to_ts_type; use super::swc_helpers::ts_keyword_type; use super::transform_dts::FastCheckDtsDiagnostic; use super::transform_dts::FastCheckDtsTransformer; +use super::type_infer::ReturnTypeInferFailCause; +use super::type_infer::TypeInferrer; use super::FastCheckDiagnostic; use super::FastCheckDiagnosticRange; @@ -829,7 +829,7 @@ impl<'a> FastCheckTransformer<'a> { _ => None, }; explicit_type_ann.or_else(|| { - self.maybe_infer_type_from_expr(&assign.right).map( + self.maybe_infer_type_from_expr_old(&assign.right).map( |type_ann| { Box::new(TsTypeAnn { span: DUMMY_SP, @@ -981,7 +981,7 @@ impl<'a> FastCheckTransformer<'a> { let inferred_type = n .value .as_ref() - .and_then(|e| self.maybe_infer_type_from_expr(e)); + .and_then(|e| self.maybe_infer_type_from_expr_old(e)); match inferred_type { Some(t) => { n.type_ann = Some(Box::new(TsTypeAnn { @@ -1030,7 +1030,7 @@ impl<'a> FastCheckTransformer<'a> { let inferred_type = n .value .as_ref() - .and_then(|e| self.maybe_infer_type_from_expr(e)); + .and_then(|e| self.maybe_infer_type_from_expr_old(e)); match inferred_type { Some(t) => Box::new(TsTypeAnn { span: DUMMY_SP, @@ -1140,21 +1140,30 @@ impl<'a> FastCheckTransformer<'a> { if missing_return_type { let range = parent_id_range.unwrap_or_else(|| n.range()); if n.is_generator { - self.mark_diagnostic( - FastCheckDiagnostic::MissingExplicitReturnType { - range: self.source_range_to_range(range), - is_definitely_void_or_never: false, - is_async: n.is_async, - }, - )?; + self.mark_diagnostic(FastCheckDiagnostic::UnknownReturnType { + ident: self.source_range_to_range(range), + cause: Some(Box::new(ReturnTypeInferFailCause::IsGenerator)), + })?; } else if let Some(body) = &mut n.body { - self.transform_function_body_block_stmt( - range, - &mut n.return_type, + let res = self.infer().infer_function_body_block_stmt( body, function_kind, n.is_async, - )?; + ); + match res { + Ok(type_ann) => { + n.return_type = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann, + })); + } + Err(cause) => { + self.mark_diagnostic(FastCheckDiagnostic::UnknownReturnType { + ident: self.source_range_to_range(range), + cause: Some(Box::new(cause)), + })?; + } + } } } @@ -1204,19 +1213,30 @@ impl<'a> FastCheckTransformer<'a> { match &mut *n.body { BlockStmtOrExpr::BlockStmt(body) => { - self.transform_function_body_block_stmt( - range, - &mut n.return_type, + let res = self.infer().infer_function_body_block_stmt( body, FunctionKind::ExpressionLike, n.is_async, - )?; + ); + match res { + Ok(type_ann) => { + n.return_type = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann, + })); + } + Err(cause) => { + self.mark_diagnostic(FastCheckDiagnostic::UnknownReturnType { + ident: self.source_range_to_range(range), + cause: Some(Box::new(cause)), + })?; + } + } } BlockStmtOrExpr::Expr(expr) => { - let inferred_type = self.maybe_infer_type_from_expr(expr); - match inferred_type { - Some(t) => { - let mut return_type = Box::new(t); + let res = self.infer().infer_expr(expr, false, None); + match res { + Ok(mut return_type) => { if n.is_async { return_type = self.promise_wrap_type(return_type) } @@ -1225,18 +1245,15 @@ impl<'a> FastCheckTransformer<'a> { type_ann: return_type, })); } - None => { - let is_expr_leavable = - self.maybe_transform_expr_if_leavable(expr, None)?; - if !is_expr_leavable { - self.mark_diagnostic( - FastCheckDiagnostic::MissingExplicitReturnType { - range: self.source_range_to_range(range), - is_definitely_void_or_never: false, - is_async: n.is_async, + Err(cause) => { + self.mark_diagnostic(FastCheckDiagnostic::UnknownReturnType { + ident: self.source_range_to_range(range), + cause: Some(Box::new( + ReturnTypeInferFailCause::ArrowExpressionValue { + cause: Box::new(cause), }, - )?; - } + )), + })?; } } } @@ -1268,60 +1285,6 @@ impl<'a> FastCheckTransformer<'a> { Ok(()) } - fn transform_function_body_block_stmt( - &mut self, - range: SourceRange, - return_type: &mut Option>, - body: &mut BlockStmt, - function_kind: FunctionKind, - is_async: bool, - ) -> Result<(), Vec> { - let analysis = analyze_return_stmts_in_function_body(body); - match (analysis, function_kind) { - (_, FunctionKind::Setter) => unreachable!(), - (ReturnStatementAnalysis::None, FunctionKind::DeclarationLike) - | (ReturnStatementAnalysis::Void, FunctionKind::DeclarationLike) - | (ReturnStatementAnalysis::Void, FunctionKind::ExpressionLike) => { - *return_type = Some(Box::new(TsTypeAnn { - span: DUMMY_SP, - type_ann: void_or_promise_void(is_async), - })); - } - (ReturnStatementAnalysis::None, FunctionKind::ExpressionLike) => { - self.mark_diagnostic( - FastCheckDiagnostic::MissingExplicitReturnType { - range: self.source_range_to_range(range), - is_definitely_void_or_never: true, - is_async, - }, - )?; - } - (ReturnStatementAnalysis::Single(_), _) => { - // TODO: infer return type based on return type - self.mark_diagnostic( - FastCheckDiagnostic::MissingExplicitReturnType { - range: self.source_range_to_range(range), - is_definitely_void_or_never: false, - is_async, - }, - )?; - } - (ReturnStatementAnalysis::None, FunctionKind::Getter) - | (ReturnStatementAnalysis::Void, FunctionKind::Getter) - | (ReturnStatementAnalysis::Multiple, _) => { - self.mark_diagnostic( - FastCheckDiagnostic::MissingExplicitReturnType { - range: self.source_range_to_range(range), - is_definitely_void_or_never: false, - is_async, - }, - )?; - } - } - - Ok(()) - } - fn handle_param_pat( &mut self, pat: &mut Pat, @@ -1338,7 +1301,8 @@ impl<'a> FastCheckTransformer<'a> { Pat::Assign(assign) => match &mut *assign.left { Pat::Ident(ident) => { if ident.type_ann.is_none() { - let inferred_type = self.maybe_infer_type_from_expr(&assign.right); + let inferred_type = + self.maybe_infer_type_from_expr_old(&assign.right); match inferred_type { Some(t) => { ident.type_ann = Some(Box::new(TsTypeAnn { @@ -1459,8 +1423,9 @@ impl<'a> FastCheckTransformer<'a> { // don't need to do anything for ambient decls if !is_ambient { for decl in &mut n.decls { - self.transform_var_declarator(decl)?; + self.transform_var_declarator(decl, n.kind)?; } + n.declare = true; } Ok(TransformItemResult::from_retain(!n.decls.is_empty())) @@ -1469,41 +1434,43 @@ impl<'a> FastCheckTransformer<'a> { fn transform_var_declarator( &mut self, n: &mut VarDeclarator, + kind: VarDeclKind, ) -> Result<(), Vec> { match &mut n.name { Pat::Ident(ident) => { if ident.type_ann.is_none() { - let inferred_type = n - .init - .as_ref() - .and_then(|e| self.maybe_infer_type_from_expr(e)); - match inferred_type { - Some(t) => { - ident.type_ann = Some(Box::new(TsTypeAnn { - span: DUMMY_SP, - type_ann: Box::new(t), - })); - n.init = Some(obj_as_never_expr()); - } - None => { - let is_init_leavable = match n.init.as_mut() { - Some(init) => self.maybe_transform_expr_if_leavable( - init, - Some(ident.id.range()), - )?, - None => false, - }; - if !is_init_leavable { - self.mark_diagnostic( - FastCheckDiagnostic::MissingExplicitType { - range: self.source_range_to_range(ident.range()), - }, - )?; + if kind == VarDeclKind::Const { + self.transform_const_decl( + ident.range(), + &mut n.init, + &mut ident.type_ann, + ident.id.range(), + )?; + } else { + if let Some(expr) = n.init.as_mut() { + let res = + self.infer().infer_expr(expr, false, Some(ident.id.range())); + n.init = None; + match res { + Ok(type_ann) => { + ident.type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann, + })); + } + Err(cause) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnknownVarType { + ident: self.source_range_to_range(ident.range()), + cause: Some(Box::new(cause)), + }, + )?; + } } } } } else { - n.init = Some(obj_as_never_expr()); + n.init = None; } } Pat::Array(_) @@ -1523,6 +1490,62 @@ impl<'a> FastCheckTransformer<'a> { Ok(()) } + fn transform_const_decl( + &mut self, + range: SourceRange, + init_opt: &mut Option>, + type_ann: &mut Option>, + ident_range: SourceRange, + ) -> Result<(), Vec> { + let init = init_opt.as_mut().unwrap(); + + let res = self + .infer() + .infer_expr_in_const_pos(init, Some(ident_range)); + let inferred_type = match res { + Ok(inferred_type) => inferred_type, + Err(cause) => { + self.mark_diagnostic(FastCheckDiagnostic::UnknownVarType { + ident: self.source_range_to_range(range), + cause: Some(Box::new(cause)), + })?; + return Ok(()); + } + }; + + if let TsType::TsLitType(lit) = &*inferred_type { + match &lit.lit { + TsLit::Number(number) => { + **init = Expr::Lit(Lit::Num(number.clone())); + } + TsLit::Str(str) => { + **init = Expr::Lit(Lit::Str(str.clone())); + } + TsLit::Bool(bool) => { + **init = Expr::Lit(Lit::Bool(bool.clone())); + } + TsLit::BigInt(bigint) => { + **init = Expr::Lit(Lit::BigInt(bigint.clone())); + } + TsLit::Tpl(_) => { + *init_opt = None; + *type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: inferred_type, + })); + } + } + } else { + *init_opt = None; + *type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: inferred_type, + })); + } + + Ok(()) + } + fn transform_ts_module( &mut self, n: &mut TsModuleDecl, @@ -1664,6 +1687,10 @@ impl<'a> FastCheckTransformer<'a> { } } + fn infer(&self) -> TypeInferrer<'_> { + TypeInferrer::new(self.specifier, self.parsed_source, self.es_module_info) + } + fn maybe_transform_expr_if_leavable( &mut self, expr: &mut Expr, @@ -1784,11 +1811,11 @@ impl<'a> FastCheckTransformer<'a> { Ok(is_leavable) } - fn maybe_infer_type_from_expr(&self, expr: &Expr) -> Option { + fn maybe_infer_type_from_expr_old(&self, expr: &Expr) -> Option { match expr { Expr::TsTypeAssertion(n) => infer_simple_type_from_type(&n.type_ann), Expr::TsAs(n) => infer_simple_type_from_type(&n.type_ann), - Expr::Lit(lit) => maybe_lit_to_ts_type(lit), + Expr::Lit(lit) => Some(lit_to_ts_type(lit)), Expr::Call(call_expr) => { if self.is_call_expr_symbol_create(call_expr) { Some(TsType::TsTypeOperator(TsTypeOperator { @@ -1804,7 +1831,7 @@ impl<'a> FastCheckTransformer<'a> { None } } - Expr::Paren(n) => self.maybe_infer_type_from_expr(&n.expr), + Expr::Paren(n) => self.maybe_infer_type_from_expr_old(&n.expr), Expr::This(_) | Expr::Array(_) | Expr::Object(_) @@ -1939,22 +1966,6 @@ fn is_computed_prop_name(prop_name: &PropName) -> bool { } } -fn void_or_promise_void(is_async: bool) -> Box { - let void_type = Box::new(ts_keyword_type(TsKeywordTypeKind::TsVoidKeyword)); - if is_async { - Box::new(TsType::TsTypeRef(TsTypeRef { - span: DUMMY_SP, - type_name: TsEntityName::Ident(Ident::new("Promise".into(), DUMMY_SP)), - type_params: Some(Box::new(TsTypeParamInstantiation { - span: DUMMY_SP, - params: vec![void_type], - })), - })) - } else { - void_type - } -} - fn replacement_return_value(ty: &TsType) -> Option> { if is_void_type(ty) { None diff --git a/src/fast_check/transform_dts.rs b/src/fast_check/transform_dts.rs index 0852b383b..2b4c5e3b1 100644 --- a/src/fast_check/transform_dts.rs +++ b/src/fast_check/transform_dts.rs @@ -9,8 +9,8 @@ use crate::FastCheckDiagnostic; use crate::FastCheckDiagnosticRange; use super::swc_helpers::any_type_ann; -use super::swc_helpers::maybe_lit_to_ts_type; -use super::swc_helpers::maybe_lit_to_ts_type_const; +use super::swc_helpers::lit_to_ts_type; +use super::swc_helpers::lit_to_ts_type_const; use super::swc_helpers::ts_readonly; use super::swc_helpers::ts_tuple_element; use super::swc_helpers::type_ann; @@ -409,9 +409,9 @@ impl<'a> FastCheckDtsTransformer<'a> { } Expr::Lit(lit) => { if as_const { - maybe_lit_to_ts_type_const(&lit) + Some(lit_to_ts_type_const(&lit)) } else { - maybe_lit_to_ts_type(&lit) + Some(lit_to_ts_type(&lit)) } } Expr::TsConstAssertion(ts_const) => { diff --git a/src/fast_check/type_infer.rs b/src/fast_check/type_infer.rs new file mode 100644 index 000000000..2a19ed475 --- /dev/null +++ b/src/fast_check/type_infer.rs @@ -0,0 +1,1239 @@ +use std::borrow::Cow; + +use deno_ast::diagnostics::DiagnosticSnippetHighlight; +use deno_ast::diagnostics::DiagnosticSnippetHighlightStyle; +use deno_ast::swc::ast::ArrayLit; +use deno_ast::swc::ast::ArrowExpr; +use deno_ast::swc::ast::BindingIdent; +use deno_ast::swc::ast::BlockStmt; +use deno_ast::swc::ast::BlockStmtOrExpr; +use deno_ast::swc::ast::Expr; +use deno_ast::swc::ast::Function; +use deno_ast::swc::ast::Ident; +use deno_ast::swc::ast::ObjectLit; +use deno_ast::swc::ast::ObjectPatProp; +use deno_ast::swc::ast::Pat; +use deno_ast::swc::ast::Prop; +use deno_ast::swc::ast::PropOrSpread; +use deno_ast::swc::ast::Str; +use deno_ast::swc::ast::Tpl; +use deno_ast::swc::ast::TsFnOrConstructorType; +use deno_ast::swc::ast::TsFnParam; +use deno_ast::swc::ast::TsFnType; +use deno_ast::swc::ast::TsGetterSignature; +use deno_ast::swc::ast::TsKeywordTypeKind; +use deno_ast::swc::ast::TsLit; +use deno_ast::swc::ast::TsMethodSignature; +use deno_ast::swc::ast::TsPropertySignature; +use deno_ast::swc::ast::TsSetterSignature; +use deno_ast::swc::ast::TsTupleElement; +use deno_ast::swc::ast::TsTupleType; +use deno_ast::swc::ast::TsType; +use deno_ast::swc::ast::TsTypeAnn; +use deno_ast::swc::ast::TsTypeElement; +use deno_ast::swc::ast::TsTypeLit; +use deno_ast::swc::ast::TsTypeOperator; +use deno_ast::swc::ast::TsTypeOperatorOp; +use deno_ast::swc::common::Spanned; +use deno_ast::swc::common::DUMMY_SP; +use deno_ast::swc::visit::Visit; +use deno_ast::swc::visit::VisitWith; +use deno_ast::ModuleSpecifier; +use deno_ast::ParsedSource; +use deno_ast::SourceRange; +use deno_ast::SourceRangedForSpanned; + +use crate::swc_helpers::analyze_return_stmts_in_function_body; +use crate::swc_helpers::FunctionKind; +use crate::swc_helpers::ReturnStatementAnalysis; +use crate::symbols::EsModuleInfo; +use crate::FastCheckDiagnosticRange; + +use super::swc_helpers::is_call_expr_symbol_create; +use super::swc_helpers::is_param_pat_optional; +use super::swc_helpers::lit_to_ts_type; +use super::swc_helpers::lit_to_ts_type_const; +use super::swc_helpers::prop_name_to_ts_key; +use super::swc_helpers::ts_keyword_type; +use super::swc_helpers::ts_lit_type; +use super::swc_helpers::void_or_promise_void; + +#[derive(Debug, Clone)] +pub enum ExprInferFailCause { + ObjectLitSpread { + dot3_token: FastCheckDiagnosticRange, + }, + ObjectLitShorthand { + key: FastCheckDiagnosticRange, + }, + ArrayWithoutAsConst { + expr: FastCheckDiagnosticRange, + }, + ArraySpread { + dot3_token: FastCheckDiagnosticRange, + }, + ExprInTemplateLiteral { + expr: FastCheckDiagnosticRange, + }, + + ObjectGetter { + key: FastCheckDiagnosticRange, + cause: Box, + }, + ObjectSetter { + key: FastCheckDiagnosticRange, + cause: Box, + }, + ObjectMethod { + key: FastCheckDiagnosticRange, + cause: Box, + }, + + FunctionExpression { + name: FastCheckDiagnosticRange, + cause: Box, + }, + ArrowExpression { + name: FastCheckDiagnosticRange, + cause: Box, + }, + + LocalReference { + ident: FastCheckDiagnosticRange, + }, + + Other { + expr: FastCheckDiagnosticRange, + }, +} + +impl ExprInferFailCause { + pub fn highlights(&self, highlights: &mut Vec) { + // this is always prefixed with + // "this function's return type could not be inferred because the value returned here" + // "this function's return type could not be inferred because the returned value" + // "this variable's type could not be inferred because its initializer" + match self { + ExprInferFailCause::ObjectLitSpread { dot3_token } => { + highlights.push(DiagnosticSnippetHighlight { + range: dot3_token.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this spread property, which can not be inferred", + )), + }) + } + ExprInferFailCause::ObjectLitShorthand { key } => { + highlights.push(DiagnosticSnippetHighlight { + range: key.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this shorthand property, which can not be inferred", + )), + }) + } + ExprInferFailCause::ArrayWithoutAsConst { expr } => { + highlights.push(DiagnosticSnippetHighlight { + range: expr.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this array literal, which can not be inferred unless marked 'as const'", + )), + }) + } + ExprInferFailCause::ArraySpread { dot3_token } => { + highlights.push(DiagnosticSnippetHighlight { + range: dot3_token.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this spread element, which can not be inferred", + )), + }) + } + ExprInferFailCause::ExprInTemplateLiteral { expr } => { + highlights.push(DiagnosticSnippetHighlight { + range: expr.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this expression in a template literal, which can not be inferred", + )), + }) + }, + ExprInferFailCause::ObjectGetter { key, cause } => { + cause.highlights(highlights, key, "contains this object getter", true); + }, + ExprInferFailCause::ObjectSetter { key, cause } => { + cause.highlights(highlights, key, "contains this object setter", true); + }, + ExprInferFailCause::ObjectMethod { key, cause } => { + cause.highlights(highlights, key, "contains this object method", true); + }, + ExprInferFailCause::FunctionExpression { name, cause } => { + cause.highlights(highlights, name, "contains this function expression", true); + }, + ExprInferFailCause::ArrowExpression { name, cause } => { + cause.highlights(highlights, name, "contains this arrow expression", true); + }, + ExprInferFailCause::LocalReference { ident } => { + highlights.push(DiagnosticSnippetHighlight { + range: ident.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this reference to a local variable or type", + )), + }) + }, + ExprInferFailCause::Other { expr } => { + highlights.push(DiagnosticSnippetHighlight { + range: expr.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed( + "contains this expression, of which the type could not be inferred", + )), + }) + } + } + } + + pub fn hint(&self, location: Cow<'_, str>) -> Option { + match self { + ExprInferFailCause::ObjectLitSpread { .. } => None, + ExprInferFailCause::ObjectLitShorthand { .. } => None, + ExprInferFailCause::ArrayWithoutAsConst { .. } => None, + ExprInferFailCause::ArraySpread { .. } => None, + ExprInferFailCause::ExprInTemplateLiteral { .. } => None, + ExprInferFailCause::ObjectGetter { cause, .. } => { + cause.hint("object getter", location) + } + ExprInferFailCause::ObjectSetter { cause, .. } => { + cause.hint("object setter", location) + } + ExprInferFailCause::ObjectMethod { cause, .. } => { + cause.hint("object method", location) + } + ExprInferFailCause::FunctionExpression { cause, .. } => { + cause.hint("function expression", location) + } + ExprInferFailCause::ArrowExpression { cause, .. } => { + cause.hint("arrow expression", location) + } + ExprInferFailCause::LocalReference { .. } => None, + ExprInferFailCause::Other { .. } => None, + } + } + + pub fn info(&self, infos: &mut Vec>) { + match self { + ExprInferFailCause::ObjectLitSpread { .. } => { + infos.push(Cow::Borrowed("spread properties can not be inferred because the type of the resulting object can not be narrowed without a type checker")) + } + ExprInferFailCause::ObjectLitShorthand { .. } => { + infos.push(Cow::Borrowed("shorthand properties can not be inferred because the type of the value referred to by the shorthand property is not known without a type checker")) + } + ExprInferFailCause::ArrayWithoutAsConst { .. } => { + infos.push(Cow::Borrowed("array literals without 'as const' can not be inferred because the type of the array can not be narrowed without a type checker")); + } + ExprInferFailCause::ArraySpread { .. } => { + infos.push(Cow::Borrowed("spread elements can not be inferred because the type of the resulting array can not be narrowed without a type checker")); + } + ExprInferFailCause::ExprInTemplateLiteral { .. } => { + infos.push(Cow::Borrowed("expressions in template literals can not be inferred because the type of the resulting string can not be narrowed without a type checker")); + }, + ExprInferFailCause::ObjectGetter { cause, .. } => { + cause.info(infos, "an object getter"); + } + ExprInferFailCause::ObjectSetter { cause, ..} => { + cause.info(infos, "an object setter"); + }, + ExprInferFailCause::ObjectMethod { cause, .. } => { + cause.info(infos, "an object method"); + }, + ExprInferFailCause::FunctionExpression { cause, .. } => { + cause.info(infos, "a function expression"); + }, + ExprInferFailCause::ArrowExpression { cause, .. } => { + cause.info(infos, "an arrow expression"); + }, + ExprInferFailCause::LocalReference { .. } => { + infos.push(Cow::Borrowed("local variables or types can not be referenced in the public API because they are not visible at the top level of the module")); + }, + ExprInferFailCause::Other { .. } => infos.push(Cow::Borrowed("the type of arbitrary expressions can not be inferred without a type checker")), + } + } +} + +#[derive(Debug, Clone)] +pub enum FunctionInferFailCause { + ParamType { + pat: FastCheckDiagnosticRange, + cause: Option>, + }, + RequiredParamAfterOptional { + pat: FastCheckDiagnosticRange, + }, + ReturnType { + cause: Box, + }, +} + +impl FunctionInferFailCause { + fn highlights( + &self, + highlights: &mut Vec, + ident: &FastCheckDiagnosticRange, + prefix: &'static str, + which: bool, + ) { + match self { + FunctionInferFailCause::ParamType { pat, cause } => { + highlights.push(DiagnosticSnippetHighlight { + range: ident.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed(prefix)), + }); + let description = match (which, cause.is_some()) { + (false, false) => "has this parameter, the type of which could not be inferred because the default value", + (false, true) => "has this parameter, which is missing an explicit type annotation", + (true, false) => "which has this parameter, the type of which could not be inferred from the default value", + (true, true) => "which has this parameter, which is missing an explicit type annotation", + }; + highlights.push(DiagnosticSnippetHighlight { + range: pat.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed(description)), + }); + if let Some(cause) = cause { + cause.highlights(highlights); + } + } + FunctionInferFailCause::RequiredParamAfterOptional { pat } => { + highlights.push(DiagnosticSnippetHighlight { + range: ident.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed(prefix)), + }); + let description = if which { + "which has this required parameter following an optional parameter or a parameter with a default value" + } else { + "has this required parameter following an optional parameter or a parameter with a default value" + }; + highlights.push(DiagnosticSnippetHighlight { + range: pat.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed(description)), + }); + } + FunctionInferFailCause::ReturnType { cause } => { + cause.highlights(highlights, ident, prefix, which); + } + } + } + + fn hint( + &self, + subject: &'static str, + location: Cow<'_, str>, + ) -> Option { + match self { + FunctionInferFailCause::ParamType { cause, .. } => { + let hint = cause.as_ref().and_then(|cause| cause.hint(format!(" in the {subject} parameter{location}").into())) + .unwrap_or_else(|| format!("add an explicit type annotation to the {subject} parameter{location}")); + Some(hint) + } + FunctionInferFailCause::RequiredParamAfterOptional { .. } => { + Some(format!( + "make the {subject} parameter{location} optional or provide a default value" + ).into()) + }, + FunctionInferFailCause::ReturnType { cause } => { + let hint = cause.hint(subject, &location) + .unwrap_or_else(|| format!("add an explicit return type annotation to the {subject}{location}").into()); + Some(hint) + }, + } + } + + fn info(&self, infos: &mut Vec>, subject: &str) { + match self { + FunctionInferFailCause::ParamType { cause, .. } => { + if let Some(cause) = cause { + cause.info(infos) + } else { + infos.push(Cow::Owned(format!("all parameters of {subject} must have an explicit type annotation or an inferrable default value"))); + } + } + FunctionInferFailCause::RequiredParamAfterOptional { .. } => { + infos.push(Cow::Owned(format!("all required parameters of {subject} must precede optional parameters or parameters with default values"))); + infos.push(Cow::Borrowed("this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed")); + } + FunctionInferFailCause::ReturnType { cause } => { + cause.info(infos, subject); + } + } + } +} + +#[derive(Debug, Clone)] +pub enum ReturnTypeInferFailCause { + IsGenerator, + NoReturnStmt { + is_async: bool, + }, + NoValueReturnStmt, // getter only + ReturnStmtValue { + return_keyword: FastCheckDiagnosticRange, + cause: Box, + }, + ArrowExpressionValue { + cause: Box, + }, + MultipleReturnStmts, +} + +impl ReturnTypeInferFailCause { + pub fn highlights( + &self, + highlights: &mut Vec, + ident: &FastCheckDiagnosticRange, + prefix: &'static str, + which: bool, + ) { + match self { + ReturnTypeInferFailCause::IsGenerator + | ReturnTypeInferFailCause::NoReturnStmt { .. } + | ReturnTypeInferFailCause::NoValueReturnStmt + | ReturnTypeInferFailCause::MultipleReturnStmts => { + highlights.push(DiagnosticSnippetHighlight { + range: ident.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Owned(format!( + "{prefix}{} is missing an explicit return type annotation", + if which { ", which" } else { "" } + ))), + }); + } + ReturnTypeInferFailCause::ReturnStmtValue { + return_keyword, + cause, + } => { + highlights.push(DiagnosticSnippetHighlight { + range: ident.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Owned(format!( + "{prefix}{} return type could not be inferred", + if which { ", of which the" } else { "'s" } + ))), + }); + highlights.push(DiagnosticSnippetHighlight { + range: return_keyword.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Borrowed("because the value returned here")), + }); + cause.highlights(highlights); + } + ReturnTypeInferFailCause::ArrowExpressionValue { cause } => { + highlights.push(DiagnosticSnippetHighlight { + range: ident.into_diagnostic_range(), + style: DiagnosticSnippetHighlightStyle::Hint, + description: Some(Cow::Owned(format!( + "{prefix}{} return type could not be inferred from the returned value", + if which { ", of which the" } else { "'s" } + ))), + }); + cause.highlights(highlights); + } + }; + } + + pub fn hint(&self, subject: &'static str, location: &str) -> Option { + match self { + ReturnTypeInferFailCause::IsGenerator + | ReturnTypeInferFailCause::NoReturnStmt { .. } + | ReturnTypeInferFailCause::NoValueReturnStmt + | ReturnTypeInferFailCause::MultipleReturnStmts => None, + ReturnTypeInferFailCause::ReturnStmtValue { cause, .. } + | ReturnTypeInferFailCause::ArrowExpressionValue { cause } => { + cause.hint(format!(" in the {subject} return value{location}").into()) + } + } + } + + pub fn info(&self, infos: &mut Vec>, subject: &str) { + match self { + ReturnTypeInferFailCause::IsGenerator => { + infos.push(Cow::Owned(format!("{subject} that is a generator must have an explicit return type annotation because the return type can not be inferred without a type checker"))); + } + ReturnTypeInferFailCause::NoReturnStmt { is_async } => { + infos.push(Cow::Owned(format!("the return type of {subject} can not be inferred if it has no return statements"))); + if *is_async { + infos.push(Cow::Owned(format!("this is because async {subject}s without a return statement can either return 'Promise' or 'Promise', and the specific type can not be determined without a type checker"))); + } else { + infos.push(Cow::Owned(format!("this is because {subject}s without a return statement can either return 'void' or 'never', and the specific type can not be determined without a type checker"))); + } + } + ReturnTypeInferFailCause::NoValueReturnStmt => { + infos.push(Cow::Owned(format!("{subject} does not have a return statement with a value, which is required to infer the return type"))); + infos.push(Cow::Borrowed( + "this is because getters must have a return statement with a value", + )); + } + ReturnTypeInferFailCause::ReturnStmtValue { cause, .. } => { + cause.info(infos); + } + ReturnTypeInferFailCause::ArrowExpressionValue { cause } => { + cause.info(infos); + } + ReturnTypeInferFailCause::MultipleReturnStmts => { + infos.push(Cow::Owned(format!("{subject} has multiple return statements with values, so a return type can not be inferred without a type checker"))); + } + } + } +} + +pub struct TypeInferrer<'a> { + specifier: &'a ModuleSpecifier, + parsed_source: &'a ParsedSource, + module_info: &'a EsModuleInfo, +} + +impl<'a> TypeInferrer<'a> { + pub fn new( + specifier: &'a ModuleSpecifier, + parsed_source: &'a ParsedSource, + module_info: &'a EsModuleInfo, + ) -> Self { + Self { + specifier, + parsed_source, + module_info, + } + } + + fn source_range_to_range( + &self, + range: SourceRange, + ) -> FastCheckDiagnosticRange { + FastCheckDiagnosticRange { + specifier: self.specifier.clone(), + text_info: self.parsed_source.text_info().clone(), + range, + } + } + + /// Infer a type from an expression in a non `const x = ...` context. + pub fn infer_expr( + &self, + expr: &Expr, + as_const: bool, + ident_range: Option, + ) -> Result, ExprInferFailCause> { + let err_other = || { + Err(ExprInferFailCause::Other { + expr: self.source_range_to_range(expr.range()), + }) + }; + + match expr { + Expr::Paren(n) => self.infer_expr(&n.expr, as_const, ident_range), + Expr::TsSatisfies(n) => self.infer_expr(&n.expr, as_const, ident_range), + Expr::Seq(n) => { + self.infer_expr(n.exprs.last().unwrap(), as_const, ident_range) + } + Expr::Assign(n) => self.infer_expr(&n.right, as_const, ident_range), + + Expr::Lit(lit) => { + if as_const { + Ok(Box::new(lit_to_ts_type_const(lit))) + } else { + Ok(Box::new(lit_to_ts_type(lit))) + } + } + Expr::Tpl(tpl) => { + if as_const { + self.infer_tpl_const(tpl) + } else { + Ok(Box::new(ts_keyword_type( + TsKeywordTypeKind::TsStringKeyword, + ))) + } + } + + Expr::Object(n) => self.infer_object_lit(n, as_const), + Expr::Array(n) => self.infer_array_lit(n, as_const), + + Expr::Call(n) => { + if is_call_expr_symbol_create( + n, + self.parsed_source.unresolved_context(), + ) { + Ok(Box::new(ts_keyword_type( + TsKeywordTypeKind::TsSymbolKeyword, + ))) + } else { + // Can not infer because we don't know the return type of the function being called + err_other() + } + } + Expr::Ident(i) => { + let is_symbol_global = i.sym == "undefined" + && i.to_id().1 == self.parsed_source.unresolved_context(); + if is_symbol_global { + Ok(Box::new(ts_keyword_type( + TsKeywordTypeKind::TsUndefinedKeyword, + ))) + } else { + // Can not infer because we don't know the type of the identifier + err_other() + } + } + Expr::TsTypeAssertion(n) => { + self.ensure_valid_type_ann(&n.type_ann)?; + Ok(n.type_ann.clone()) + } + Expr::TsAs(n) => { + self.ensure_valid_type_ann(&n.type_ann)?; + Ok(n.type_ann.clone()) + } + Expr::TsConstAssertion(n) => self.infer_expr(&n.expr, true, ident_range), + + Expr::Fn(n) => { + let ident_range = n + .ident + .as_ref() + .map(|ident| ident.span.range()) + .or(ident_range) + .unwrap_or_else(|| { + let range = n.function.span.range(); + SourceRange::new(range.start, range.start + 1) // this could be improved + }); + self.infer_fn(&n.function).map_err(|cause| { + ExprInferFailCause::FunctionExpression { + name: self.source_range_to_range(ident_range), + cause: Box::new(cause), + } + }) + } + Expr::Arrow(n) => { + let ident_range = ident_range.unwrap_or_else(|| { + let range = n.span.range(); + SourceRange::new(range.start, range.start + 1) + }); + self.infer_arrow(&n).map_err(|cause| { + ExprInferFailCause::ArrowExpression { + name: self.source_range_to_range(ident_range), + cause: Box::new(cause), + } + }) + } + + Expr::Unary(_) => { + // TODO(@lucacasonato): maybe support `void`? + err_other() + } + Expr::Bin(_) => { + // TODO(@lucacasonato): maybe support inferring comparison operators and `in` and `instanceof`? + err_other() + } + Expr::Class(_) => { + // TODO(@lucacasonato): maybe support inferring class expressions + err_other() + } + Expr::MetaProp(_) | Expr::Update(_) => err_other(), // Does not make sense to infer, usage not frequent for possible inferences + Expr::This(_) => err_other(), // Can not infer because we don't know the type of this + Expr::TsInstantiation(_) => err_other(), // Can not infer because we don't know the type being instantiated + Expr::Member(_) | Expr::SuperProp(_) | Expr::OptChain(_) => err_other(), // Can not infer because we don't know the type of the object being accessed + Expr::New(_) | Expr::TaggedTpl(_) => err_other(), // Can not infer because we don't know the type of the function being called + Expr::Yield(_) => err_other(), // Can not infer because we don't know the type of the generators next value + Expr::Cond(_) => err_other(), // Can not narrow the type of branches without type information + Expr::JSXMember(_) + | Expr::JSXNamespacedName(_) + | Expr::JSXEmpty(_) + | Expr::JSXElement(_) + | Expr::JSXFragment(_) => err_other(), // Can not infer because we don't know the type of the JSX element + Expr::Await(_) | Expr::TsNonNull(_) => err_other(), // Can not infer because we need type information to narrow the inner type + Expr::PrivateName(_) => err_other(), // Unreachable in valid code + Expr::Invalid(_) => err_other(), + } + } + + fn infer_array_lit( + &self, + n: &ArrayLit, + as_const: bool, + ) -> Result, ExprInferFailCause> { + if !as_const { + // Can not infer array types in non-const context, because narrowing would require type information + Err(ExprInferFailCause::ArrayWithoutAsConst { + expr: self.source_range_to_range(n.range()), + }) + } else { + let mut elem_types = vec![]; + + for elem in &n.elems { + match elem { + Some(expr) => { + if let Some(span) = expr.spread { + return Err(ExprInferFailCause::ArraySpread { + dot3_token: self.source_range_to_range(span.range()), + }); + } + let ty = self.infer_expr(&expr.expr, as_const, None)?; + elem_types.push(TsTupleElement { + span: expr.expr.span(), + ty, + label: None, + }); + } + None => elem_types.push(TsTupleElement { + span: DUMMY_SP, + ty: Box::new(ts_keyword_type( + TsKeywordTypeKind::TsUndefinedKeyword, + )), + label: None, + }), + } + } + + Ok(Box::new(TsType::TsTypeOperator(TsTypeOperator { + span: DUMMY_SP, + op: TsTypeOperatorOp::ReadOnly, + type_ann: Box::new(TsType::TsTupleType(TsTupleType { + span: n.span(), + elem_types, + })), + }))) + } + } + + // Infer a type from an expression in a `const x = ...` context. + pub fn infer_expr_in_const_pos( + &mut self, + expr: &Expr, + ident_range: Option, + ) -> Result, ExprInferFailCause> { + match expr { + Expr::Paren(n) => self.infer_expr_in_const_pos(&n.expr, ident_range), + Expr::TsSatisfies(n) => { + self.infer_expr_in_const_pos(&n.expr, ident_range) + } + Expr::Seq(n) => { + self.infer_expr_in_const_pos(n.exprs.last().unwrap(), ident_range) + } + Expr::Assign(n) => self.infer_expr_in_const_pos(&n.right, ident_range), + + Expr::Lit(lit) => Ok(Box::new(lit_to_ts_type_const(lit))), + Expr::Tpl(tpl) => self.infer_tpl_const(tpl), + + Expr::Call(n) + if is_call_expr_symbol_create( + n, + self.parsed_source.unresolved_context(), + ) => + { + Ok(Box::new(TsType::TsTypeOperator(TsTypeOperator { + span: DUMMY_SP, + op: TsTypeOperatorOp::Unique, + type_ann: Box::new(ts_keyword_type( + TsKeywordTypeKind::TsSymbolKeyword, + )), + }))) + } + + _ => self.infer_expr(expr, false, ident_range), + } + } + + fn infer_tpl_const( + &self, + tpl: &Tpl, + ) -> Result, ExprInferFailCause> { + if tpl.exprs.is_empty() { + let quasi = tpl.quasis.first().unwrap(); + Ok(Box::new(ts_lit_type(TsLit::Str(Str { + span: quasi.span, + value: quasi.cooked.clone().unwrap(), + raw: None, + })))) + } else { + Err(ExprInferFailCause::ExprInTemplateLiteral { + expr: self.source_range_to_range(tpl.exprs.first().unwrap().range()), + }) + } + } + + fn infer_object_lit( + &self, + n: &ObjectLit, + as_const: bool, + ) -> Result, ExprInferFailCause> { + let mut members = vec![]; + for prop in &n.props { + match prop { + PropOrSpread::Prop(prop) => match prop.as_ref() { + Prop::KeyValue(kv) => { + let (key, computed) = prop_name_to_ts_key(&kv.key); + let type_ann = + self.infer_expr(&kv.value, as_const, Some(key.range()))?; + members.push(TsTypeElement::TsPropertySignature( + TsPropertySignature { + span: kv.span(), + key, + computed, + type_ann: Some(Box::new(TsTypeAnn { + span: kv.value.span(), + type_ann, + })), + readonly: as_const, + optional: false, + // TODO: remove after https://github.com/swc-project/swc/pull/8955 lands + init: None, + params: vec![], + type_params: None, + }, + )); + } + Prop::Getter(getter) => { + let (key, computed) = prop_name_to_ts_key(&getter.key); + if getter.type_ann.is_none() { + // TODO(lucacasonato): if we know the type of the setter, use that + + let body = getter.body.as_ref().unwrap(); + let res = self.infer_function_body_block_stmt( + body, + FunctionKind::Getter, + /* is async */ false, + ); + match res { + Ok(ty) => members.push(TsTypeElement::TsGetterSignature( + TsGetterSignature { + span: getter.span(), + key, + computed, + type_ann: Some(Box::new(TsTypeAnn { + span: getter.span(), + type_ann: ty, + })), + readonly: false, + optional: false, + }, + )), + Err(cause) => { + return Err(ExprInferFailCause::ObjectGetter { + key: self.source_range_to_range(getter.key.range()), + cause: Box::new(FunctionInferFailCause::ReturnType { + cause: Box::new(cause), + }), + }) + } + } + } + } + Prop::Setter(setter) => { + let (key, computed) = prop_name_to_ts_key(&setter.key); + // TODO(lucacasonato): if we know the type of the setter, use that + + let param = + self + .infer_fn_param(&*setter.param, false) + .map_err(|cause| ExprInferFailCause::ObjectSetter { + key: self.source_range_to_range(setter.key.range()), + cause: Box::new(cause), + })?; + + members.push(TsTypeElement::TsSetterSignature(TsSetterSignature { + span: setter.span(), + key, + computed, + param, + readonly: false, + optional: false, + })); + } + Prop::Method(n) => { + let (key, computed) = prop_name_to_ts_key(&n.key); + + let return_type = + self.infer_fn_return_type(&n.function).map_err(|cause| { + ExprInferFailCause::ObjectMethod { + key: self.source_range_to_range(n.key.range()), + cause: Box::new(FunctionInferFailCause::ReturnType { + cause: Box::new(cause), + }), + } + })?; + let params = self + .infer_fn_params(n.function.params.iter().map(|p| &p.pat)) + .map_err(|cause| ExprInferFailCause::ObjectMethod { + key: self.source_range_to_range(n.key.range()), + cause: Box::new(cause), + })?; + + members.push(TsTypeElement::TsMethodSignature(TsMethodSignature { + span: n.span(), + key, + computed, + params, + type_ann: Some(return_type), + type_params: n.function.type_params.clone(), + readonly: false, + optional: false, + })); + } + Prop::Shorthand(n) => { + return Err(ExprInferFailCause::ObjectLitShorthand { + key: self.source_range_to_range(n.range()), + }) + } + Prop::Assign(_) => unreachable!("not valid on object literal"), + }, + PropOrSpread::Spread(n) => { + return Err(ExprInferFailCause::ObjectLitSpread { + dot3_token: self.source_range_to_range(n.dot3_token.range()), + }) + } + } + } + + let obj_literal = Box::new(TsType::TsTypeLit(TsTypeLit { + span: Default::default(), + members, + })); + + if as_const { + Ok(Box::new(TsType::TsTypeOperator(TsTypeOperator { + span: DUMMY_SP, + op: TsTypeOperatorOp::ReadOnly, + type_ann: obj_literal, + }))) + } else { + Ok(obj_literal) + } + } + + pub fn infer_function_body_block_stmt( + &self, + body: &BlockStmt, + function_kind: FunctionKind, + is_async: bool, + ) -> Result, ReturnTypeInferFailCause> { + let analysis = analyze_return_stmts_in_function_body(body); + match (analysis, function_kind) { + (_, FunctionKind::Setter) => unreachable!(), + (ReturnStatementAnalysis::None, FunctionKind::DeclarationLike) + | (ReturnStatementAnalysis::Void, FunctionKind::DeclarationLike) + | (ReturnStatementAnalysis::Void, FunctionKind::ExpressionLike) => { + Ok(void_or_promise_void(is_async)) + } + (ReturnStatementAnalysis::None, FunctionKind::ExpressionLike) => { + Err(ReturnTypeInferFailCause::NoReturnStmt { is_async }) + } + (ReturnStatementAnalysis::Single(n), _) => { + let expr = n.arg.as_ref().unwrap(); + match self.infer_expr(expr, /* as const */ false, None) { + Ok(ty) => Ok(ty), + Err(cause) => Err(ReturnTypeInferFailCause::ReturnStmtValue { + return_keyword: self.source_range_to_range(SourceRange { + start: n.span.start(), + end: n.span.start() + 6, + }), + cause: Box::new(cause), + }), + } + } + (ReturnStatementAnalysis::None, FunctionKind::Getter) + | (ReturnStatementAnalysis::Void, FunctionKind::Getter) => { + Err(ReturnTypeInferFailCause::NoValueReturnStmt) + } + (ReturnStatementAnalysis::Multiple, _) => { + Err(ReturnTypeInferFailCause::MultipleReturnStmts) + } + } + } + + fn infer_fn( + &self, + function: &Function, + ) -> Result, FunctionInferFailCause> { + let return_type = self.infer_fn_return_type(function).map_err(|cause| { + FunctionInferFailCause::ReturnType { + cause: Box::new(cause), + } + })?; + + let param_pats = function.params.iter().map(|param| ¶m.pat); + let params = self.infer_fn_params(param_pats)?; + + Ok(Box::new(TsType::TsFnOrConstructorType( + TsFnOrConstructorType::TsFnType(TsFnType { + span: function.span, + params, + type_ann: return_type, + type_params: function.type_params.clone(), + }), + ))) + } + + fn infer_fn_return_type( + &self, + function: &Function, + ) -> Result, ReturnTypeInferFailCause> { + let return_type = match &function.return_type { + Some(ty) => ty.clone(), + None => { + if function.is_generator { + return Err(ReturnTypeInferFailCause::IsGenerator); + } + + let body = function.body.as_ref().unwrap(); + let type_ann = self.infer_function_body_block_stmt( + body, + FunctionKind::ExpressionLike, + function.is_async, + )?; + + Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann, + }) + } + }; + Ok(return_type) + } + + fn infer_fn_params( + &self, + param_pats: impl Iterator, + ) -> Result, FunctionInferFailCause> { + let mut is_previous_optional = false; + let mut params = vec![]; + for pat in param_pats { + let optional = is_param_pat_optional(pat); + if !optional && is_previous_optional { + return Err(FunctionInferFailCause::RequiredParamAfterOptional { + pat: self.source_range_to_range(pat.range()), + }); + } + is_previous_optional = optional; + let param = self.infer_fn_param(&pat, optional)?; + params.push(param) + } + Ok(params) + } + + fn infer_fn_param( + &self, + pat: &Pat, + optional: bool, + ) -> Result { + let (pat, default_value) = match &*pat { + Pat::Assign(assign) => (&*assign.left, Some(&assign.right)), + _ => (pat, None), + }; + + let range = pat.range(); + + let mut type_ann = match pat { + Pat::Ident(n) => n.type_ann.clone(), + Pat::Array(n) => n.type_ann.clone(), + Pat::Object(n) => n.type_ann.clone(), + Pat::Rest(n) => n.type_ann.clone(), + Pat::Assign(_) => unreachable!(), + Pat::Expr(_) => unreachable!(), + Pat::Invalid(_) => unreachable!(), + }; + + if type_ann.is_none() { + if let Some(default_value) = default_value { + match self.infer_expr(default_value, false, None) { + Ok(ty) => { + type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: ty, + })) + } + Err(cause) => { + return Err(FunctionInferFailCause::ParamType { + pat: self.source_range_to_range(range), + cause: Some(Box::new(cause)), + }); + } + }; + } else { + return Err(FunctionInferFailCause::ParamType { + pat: self.source_range_to_range(range), + cause: None, + }); + } + } + + Ok(pat_to_ts_fn_param(pat, type_ann, optional)) + } + + fn infer_arrow( + &self, + arrow: &ArrowExpr, + ) -> Result, FunctionInferFailCause> { + let params = self.infer_fn_params(arrow.params.iter())?; + + let return_type = match &arrow.return_type { + Some(ty) => ty.clone(), + None => { + if arrow.is_generator { + return Err(FunctionInferFailCause::ReturnType { + cause: Box::new(ReturnTypeInferFailCause::IsGenerator), + }); + } + + let type_ann = match &*arrow.body { + BlockStmtOrExpr::BlockStmt(body) => self + .infer_function_body_block_stmt( + body, + FunctionKind::ExpressionLike, + arrow.is_async, + ) + .map_err(|cause| FunctionInferFailCause::ReturnType { + cause: Box::new(cause), + })?, + BlockStmtOrExpr::Expr(expr) => self + .infer_expr(expr, false, None) + .map_err(|cause| FunctionInferFailCause::ReturnType { + cause: Box::new(ReturnTypeInferFailCause::ArrowExpressionValue { + cause: Box::new(cause), + }), + })?, + }; + + Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann, + }) + } + }; + + Ok(Box::new(TsType::TsFnOrConstructorType( + TsFnOrConstructorType::TsFnType(TsFnType { + span: arrow.span, + params, + type_ann: return_type, + type_params: arrow.type_params.clone(), + }), + ))) + } + + pub fn ensure_valid_type_ann( + &self, + ty: &TsType, + ) -> Result<(), ExprInferFailCause> { + struct Visitor<'a> { + es_module_info: &'a EsModuleInfo, + invalid_range: Option, + } + + impl Visit for Visitor<'_> { + fn visit_ident(&mut self, n: &Ident) { + if self.es_module_info.symbol_from_swc(&n.to_id()).is_none() { + self.invalid_range = Some(n.range()); + } + } + } + + let mut visitor = Visitor { + es_module_info: self.module_info, + invalid_range: None, + }; + ty.visit_with(&mut visitor); + match visitor.invalid_range { + Some(range) => Err(ExprInferFailCause::LocalReference { + ident: self.source_range_to_range(range), + }), + None => Ok(()), + } + } +} + +fn pat_to_ts_fn_param( + pat: &Pat, + type_ann: Option>, + optional: bool, +) -> TsFnParam { + match pat { + Pat::Ident(n) => TsFnParam::Ident(BindingIdent { + id: Ident { + span: n.span, + sym: n.sym.clone(), + optional, + }, + type_ann, + }), + pat @ Pat::Array(_) => { + let mut new_pat = pat.clone(); + clean_pat_for_ts_fn_param(&mut new_pat); + let Pat::Array(mut n) = new_pat else { + unreachable!() + }; + n.optional = optional; + n.type_ann = type_ann; + TsFnParam::Array(n) + } + pat @ Pat::Object(_) => { + let mut new_pat = pat.clone(); + clean_pat_for_ts_fn_param(&mut new_pat); + let Pat::Object(mut n) = new_pat else { + unreachable!() + }; + n.optional = optional; + n.type_ann = type_ann; + TsFnParam::Object(n) + } + pat @ Pat::Rest(_) => { + let mut new_pat = pat.clone(); + clean_pat_for_ts_fn_param(&mut new_pat); + let Pat::Rest(mut n) = new_pat else { + unreachable!() + }; + n.type_ann = type_ann; + TsFnParam::Rest(n) + } + Pat::Assign(p) => pat_to_ts_fn_param(&p.left, type_ann, optional), + Pat::Invalid(_) => unreachable!(), + Pat::Expr(_) => unreachable!(), + } +} + +fn clean_pat_for_ts_fn_param(p: &mut Pat) { + match &mut *p { + Pat::Assign(n) => { + *p = *n.left.clone(); + clean_pat_for_ts_fn_param(p); + } + Pat::Array(n) => { + n.optional = false; + n.type_ann = None; + for elem in &mut n.elems { + if let Some(elem) = elem { + clean_pat_for_ts_fn_param(elem); + } + } + } + Pat::Object(n) => { + n.optional = false; + n.type_ann = None; + for prop in &mut n.props { + match prop { + ObjectPatProp::KeyValue(p) => clean_pat_for_ts_fn_param(&mut p.value), + ObjectPatProp::Assign(p) => p.value = None, + ObjectPatProp::Rest(p) => clean_pat_for_ts_fn_param(&mut p.arg), + } + } + } + Pat::Rest(n) => { + n.type_ann = None; + clean_pat_for_ts_fn_param(&mut n.arg) + } + Pat::Ident(n) => { + n.id.optional = false; + n.type_ann = None; + } + Pat::Invalid(_) | Pat::Expr(_) => unreachable!(), + } +} diff --git a/src/lib.rs b/src/lib.rs index 2d0b484bc..ff532e285 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ mod fast_check; pub mod packages; pub mod source; mod text_encoding; + +#[cfg(any(feature = "symbols", feature = "fast_check"))] mod swc_helpers; use source::FileSystem; diff --git a/src/swc_helpers.rs b/src/swc_helpers.rs index a8b33984a..9e1757076 100644 --- a/src/swc_helpers.rs +++ b/src/swc_helpers.rs @@ -2,6 +2,7 @@ use std::ops::ControlFlow; +use deno_ast::swc::ast::BlockStmt; use deno_ast::swc::ast::ReturnStmt; use deno_ast::swc::ast::Stmt; @@ -32,7 +33,7 @@ pub enum ReturnStatementAnalysis { } pub fn analyze_return_stmts_in_function_body( - body: &deno_ast::swc::ast::BlockStmt, + body: &BlockStmt, ) -> ReturnStatementAnalysis { if body.stmts.is_empty() { ReturnStatementAnalysis::Void diff --git a/src/symbols/dep_analyzer.rs b/src/symbols/dep_analyzer.rs index 5cf99d2c0..b86789819 100644 --- a/src/symbols/dep_analyzer.rs +++ b/src/symbols/dep_analyzer.rs @@ -1,6 +1,8 @@ // Copyright 2018-2024 the Deno authors. MIT license. +use deno_ast::swc::ast::ArrowExpr; use deno_ast::swc::ast::BindingIdent; +use deno_ast::swc::ast::BlockStmtOrExpr; use deno_ast::swc::ast::Class; use deno_ast::swc::ast::DefaultDecl; use deno_ast::swc::ast::Expr; @@ -40,6 +42,8 @@ use deno_ast::swc::ast::VarDeclarator; use deno_ast::swc::visit::Visit; use deno_ast::swc::visit::VisitWith; +use crate::swc_helpers::analyze_return_stmts_in_function_body; + use super::swc_helpers::ts_entity_name_to_parts; use super::swc_helpers::ts_qualified_name_parts; use super::ExportDeclRef; @@ -340,6 +344,55 @@ impl Visit for DepsFiller { } if let Some(return_type) = &n.return_type { self.visit_ts_type_ann(return_type); + } else if let Some(body) = &n.body { + match analyze_return_stmts_in_function_body(body) { + crate::swc_helpers::ReturnStatementAnalysis::Single(return_stmt) => { + if let Some(arg) = &return_stmt.arg { + let visited_type_assertion = self.visit_type_if_type_assertion(arg); + if !visited_type_assertion && self.mode.visit_exprs() { + self.visit_expr(arg); + } + } + } + _ => {} + } + } + } + + fn visit_arrow_expr(&mut self, n: &ArrowExpr) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + for param in &n.params { + self.visit_pat(param); + } + if let Some(return_type) = &n.return_type { + self.visit_ts_type_ann(return_type); + } else { + match &*n.body { + BlockStmtOrExpr::BlockStmt(body) => { + match analyze_return_stmts_in_function_body(body) { + crate::swc_helpers::ReturnStatementAnalysis::Single( + return_stmt, + ) => { + if let Some(arg) = &return_stmt.arg { + let visited_type_assertion = + self.visit_type_if_type_assertion(arg); + if !visited_type_assertion && self.mode.visit_exprs() { + self.visit_expr(arg); + } + } + } + _ => {} + } + } + BlockStmtOrExpr::Expr(expr) => { + let visited_type_assertion = self.visit_type_if_type_assertion(expr); + if !visited_type_assertion && self.mode.visit_exprs() { + self.visit_expr(expr); + } + } + } } } diff --git a/tests/specs/graph/fast_check/cache__diagnostic.txt b/tests/specs/graph/fast_check/cache__diagnostic.txt index f8b337032..060782166 100644 --- a/tests/specs/graph/fast_check/cache__diagnostic.txt +++ b/tests/specs/graph/fast_check/cache__diagnostic.txt @@ -140,15 +140,21 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/c.ts:1:17 | 1 | export function add(a: number, b: number) { - | ^^^ this function is missing an explicit return type + | --- this function's return type could not be inferred + | + 2 | return Math.random(); + | ------ because the value returned here + | ------------- contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type == fast check cache == diff --git a/tests/specs/graph/fast_check/cache__diagnostic_deep.txt b/tests/specs/graph/fast_check/cache__diagnostic_deep.txt index 99551b31c..e872c8231 100644 --- a/tests/specs/graph/fast_check/cache__diagnostic_deep.txt +++ b/tests/specs/graph/fast_check/cache__diagnostic_deep.txt @@ -141,15 +141,21 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/c.ts:1:17 | 1 | export function add(a: number, b: number) { - | ^^^ this function is missing an explicit return type + | --- this function's return type could not be inferred + | + 2 | return Math.random(); + | ------ because the value returned here + | ------------- contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type == fast check cache == diff --git a/tests/specs/graph/fast_check/cache__diagnostic_multiple_exports.txt b/tests/specs/graph/fast_check/cache__diagnostic_multiple_exports.txt index dc972f177..67e0821be 100644 --- a/tests/specs/graph/fast_check/cache__diagnostic_multiple_exports.txt +++ b/tests/specs/graph/fast_check/cache__diagnostic_multiple_exports.txt @@ -162,27 +162,39 @@ import 'jsr:@scope/a/c' } Fast check https://jsr.io/@scope/a/1.0.0/c.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/a.ts:1:17 | 1 | export function add(a: number, b: number) { - | ^^^ this function is missing an explicit return type + | --- this function's return type could not be inferred + | + 2 | return Math.random(); + | ------ because the value returned here + | ------------- contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/a.ts:1:17 | 1 | export function add(a: number, b: number) { - | ^^^ this function is missing an explicit return type + | --- this function's return type could not be inferred + | + 2 | return Math.random(); + | ------ because the value returned here + | ------------- contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type == fast check cache == diff --git a/tests/specs/graph/fast_check/cache__diagnostic_then_nested_dep.txt b/tests/specs/graph/fast_check/cache__diagnostic_then_nested_dep.txt index 13d946b1a..9a3371978 100644 --- a/tests/specs/graph/fast_check/cache__diagnostic_then_nested_dep.txt +++ b/tests/specs/graph/fast_check/cache__diagnostic_then_nested_dep.txt @@ -174,15 +174,21 @@ jsr deps: { } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/mod.ts:1:17 | 1 | export function add(a: number, b: number) { - | ^^^ this function is missing an explicit return type + | --- this function's return type could not be inferred + | + 2 | return Math.random(); + | ------ because the value returned here + | ------------- contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: diff --git a/tests/specs/graph/fast_check/class_member_ref_additional_prop_on_member.txt b/tests/specs/graph/fast_check/class_member_ref_additional_prop_on_member.txt index 750e64fe4..20b07d08a 100644 --- a/tests/specs/graph/fast_check/class_member_ref_additional_prop_on_member.txt +++ b/tests/specs/graph/fast_check/class_member_ref_additional_prop_on_member.txt @@ -67,10 +67,13 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 5 | class MyClass { | ^^^^^^^^^^^^^^^ + | 6 | member: string; | ^^^^^^^^^^^^^^^^^ + | 7 | } | ^ this is the reference + | = hint: extract the shared type to a type alias and reference the type alias instead info: the reference was too complex to be resolved by fast check diff --git a/tests/specs/graph/fast_check/class_member_ref_not_found.txt b/tests/specs/graph/fast_check/class_member_ref_not_found.txt index a8e47cf35..9f2f5b551 100644 --- a/tests/specs/graph/fast_check/class_member_ref_not_found.txt +++ b/tests/specs/graph/fast_check/class_member_ref_not_found.txt @@ -66,10 +66,13 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 4 | class Public1 { | ^^^^^^^^^^^^^^^ + | 5 | prop: string; | ^^^^^^^^^^^^^^^ + | 6 | } | ^ this is the reference + | = hint: fix the reference to point to a symbol that exists info: this error may be the result of a bug in Deno - if you think this is the case, please open an issue diff --git a/tests/specs/graph/fast_check/class_properties.txt b/tests/specs/graph/fast_check/class_properties.txt index 83812a8f3..500d7e0f2 100644 --- a/tests/specs/graph/fast_check/class_properties.txt +++ b/tests/specs/graph/fast_check/class_properties.txt @@ -106,8 +106,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: return {} as never; } } - const isSecure: Symbol = {} as never; - const public2: Symbol = {} as never; + declare const isSecure: Symbol; + declare const public2: Symbol; export class CookieMapBase { declare [isSecure]: boolean; [public2](): number { diff --git a/tests/specs/graph/fast_check/class_static_member_ref_not_found.txt b/tests/specs/graph/fast_check/class_static_member_ref_not_found.txt index 58fb2e315..608c31a2e 100644 --- a/tests/specs/graph/fast_check/class_static_member_ref_not_found.txt +++ b/tests/specs/graph/fast_check/class_static_member_ref_not_found.txt @@ -66,10 +66,13 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 4 | class Public1 { | ^^^^^^^^^^^^^^^ + | 5 | static prop: string; | ^^^^^^^^^^^^^^^^^^^^^^ + | 6 | } | ^ this is the reference + | = hint: fix the reference to point to a symbol that exists info: this error may be the result of a bug in Deno - if you think this is the case, please open an issue diff --git a/tests/specs/graph/fast_check/class_super_unsupported.txt b/tests/specs/graph/fast_check/class_super_unsupported.txt index bdb722ef1..d7bdaf31d 100644 --- a/tests/specs/graph/fast_check/class_super_unsupported.txt +++ b/tests/specs/graph/fast_check/class_super_unsupported.txt @@ -65,6 +65,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 4 | export class Child extends MyFunction(Base) { | ^^^^^^^^^^^^^^^^ this is the superclass expression + | = hint: extract the superclass expression into a variable info: fast check was unable to infer the type of the superclass expression diff --git a/tests/specs/graph/fast_check/comments.txt b/tests/specs/graph/fast_check/comments.txt index 4b94d8069..631acd923 100644 --- a/tests/specs/graph/fast_check/comments.txt +++ b/tests/specs/graph/fast_check/comments.txt @@ -61,7 +61,7 @@ import 'jsr:@scope/a' Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: {} // @ts-ignore broken line - export const value1: number = {} as never; + export declare const value1: number; --- DTS --- // @ts-ignore broken line export declare const value1: number; diff --git a/tests/specs/graph/fast_check/const_infer_function_missing_return.txt b/tests/specs/graph/fast_check/const_infer_function_missing_return.txt index 1fed5c4ff..22e07ea35 100644 --- a/tests/specs/graph/fast_check/const_infer_function_missing_return.txt +++ b/tests/specs/graph/fast_check/const_infer_function_missing_return.txt @@ -56,13 +56,7 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API - --> https://jsr.io/@scope/a/1.0.0/mod.ts:1:14 - | - 1 | export const test4 = function () { return "test" }; - | ^^^^^ this function is missing an explicit return type - = hint: add an explicit return type to the function - - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type - + {} + export declare const test4: () => string; + --- DTS --- + export declare const test4: () => string; diff --git a/tests/specs/graph/fast_check/cross_file_ref_module_not_found.txt b/tests/specs/graph/fast_check/cross_file_ref_module_not_found.txt index 47f7c0b06..0ac5779e9 100644 --- a/tests/specs/graph/fast_check/cross_file_ref_module_not_found.txt +++ b/tests/specs/graph/fast_check/cross_file_ref_module_not_found.txt @@ -142,12 +142,16 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 1 | export class A { | ^^^^^^^^^^^^^^^^ + | 2 | member: string; | ^^^^^^^^^^^^^^^^^ + | 3 | member2: string; | ^^^^^^^^^^^^^^^^^^ + | 4 | } | ^ this is the reference + | = hint: fix the reference to point to a symbol that exists info: this error may be the result of a bug in Deno - if you think this is the case, please open an issue diff --git a/tests/specs/graph/fast_check/default_export_expr_var.txt b/tests/specs/graph/fast_check/default_export_expr_var.txt index 160bd7b92..0af4cb024 100644 --- a/tests/specs/graph/fast_check/default_export_expr_var.txt +++ b/tests/specs/graph/fast_check/default_export_expr_var.txt @@ -67,7 +67,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export interface $Type { prop: boolean; } - export const $: $Type = {} as never; + export declare const $: $Type; export default $; --- DTS --- export interface $Type { diff --git a/tests/specs/graph/fast_check/enums.txt b/tests/specs/graph/fast_check/enums.txt index e59258af7..3bbb91a96 100644 --- a/tests/specs/graph/fast_check/enums.txt +++ b/tests/specs/graph/fast_check/enums.txt @@ -108,7 +108,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: Value1 = "a", Value2 = "b" } - const value: number = {} as never; + declare const value = 10; export enum EnumWithNonConstInits { Value1 = new Public1().test, Value2 = new Public2().asdf * value + NonExportedEnum.Value @@ -120,7 +120,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: enum NonExportedEnum { Value } - const NUM: any = {} as never; + declare const NUM: any; export enum Foo1 { A = 1, B = "2", diff --git a/tests/specs/graph/fast_check/global_module.txt b/tests/specs/graph/fast_check/global_module.txt index 8220f4b12..6c6234097 100644 --- a/tests/specs/graph/fast_check/global_module.txt +++ b/tests/specs/graph/fast_check/global_module.txt @@ -63,8 +63,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 1 | global { | ^^^^^^^^ + | 2 | declare var Test: 5; | ^^^^^^^^^^^^^^^^^^^^^^ + | 3 | } | ^ = hint: remove the 'global' augmentation diff --git a/tests/specs/graph/fast_check/inference/basic.txt b/tests/specs/graph/fast_check/inference/basic.txt new file mode 100644 index 000000000..a0367fb4c --- /dev/null +++ b/tests/specs/graph/fast_check/inference/basic.txt @@ -0,0 +1,160 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/a@1.0.0", + "exports": { + ".": "./mod.ts" + } + } +] + +# mod.ts +export const str = "str"; +export let str2 = "str"; +export const num = 1; +export let num2 = 1; +export const bool = true; +export let bool2 = true; +export const regexp = /./; +export let regexp2 = /./; +export const bigint = 1n; +export let bigint2 = 1n; +export const symbol = Symbol(""); +export const symbol2 = Symbol.for(""); +export const symbol3 = Symbol(); +export let symbol4 = Symbol(""); +export let symbol5 = Symbol.for(""); +export let symbol6 = Symbol(); +export const tpl = `tpl`; +export let tpl2 = `tpl`; +export let tpl3 = `tpl ${globalThis}`; +export const nul = null; +export let nul2 = null; +export const undef = undefined; +export let undef2 = undefined; + +export const paren = (1); +export let paren2 = (1); + +export const satisfies = 1 satisfies number; +export let satisfies2 = 1 satisfies 1; + +export const sequence = (1, 2); + +let assign: string; +export const assign2 = assign = 1; + +export const assertion = globalThis as 2; +export const assertion2 = <2> globalThis; + +export const obj = { a: 1, 1: 2, "3": 3 }; +export const obj2 = { + get str() { return "" }, + set str(v: string) {}, + method(): string {} +}; + + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "size": 1113, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + } + ], + "redirects": {} +} + +Fast check file:///mod.ts: + {} + export declare const str = "str"; + export declare let str2: string; + export declare const num = 1; + export declare let num2: number; + export declare const bool = true; + export declare let bool2: boolean; + export declare const regexp: RegExp; + export declare let regexp2: RegExp; + export declare const bigint = 1n; + export declare let bigint2: bigint; + export declare const symbol: unique symbol; + export declare const symbol2: unique symbol; + export declare const symbol3: unique symbol; + export declare let symbol4: symbol; + export declare let symbol5: symbol; + export declare let symbol6: symbol; + export declare const tpl = "tpl"; + export declare let tpl2: string; + export declare let tpl3: string; + export declare const nul: null; + export declare let nul2: null; + export declare const undef: undefined; + export declare let undef2: undefined; + export declare const paren = 1; + export declare let paren2: number; + export declare const satisfies = 1; + export declare let satisfies2: number; + export declare const sequence = 2; + export declare const assign2 = 1; + export declare const assertion = 2; + export declare const assertion2 = 2; + export declare const obj: { + a: number; + 1: number; + "3": number; + }; + export declare const obj2: { + get str(): string; + set str(v: string); + method(): string; + }; + --- DTS --- + export declare const str: string; + export declare let str2: string; + export declare const num: number; + export declare let num2: number; + export declare const bool: boolean; + export declare let bool2: boolean; + export declare const regexp: RegExp; + export declare let regexp2: RegExp; + export declare const bigint: bigint; + export declare let bigint2: bigint; + export declare const symbol: unique symbol; + export declare const symbol2: unique symbol; + export declare const symbol3: unique symbol; + export declare let symbol4: symbol; + export declare let symbol5: symbol; + export declare let symbol6: symbol; + export declare const tpl: string; + export declare let tpl2: string; + export declare let tpl3: string; + export declare const nul: null; + export declare let nul2: null; + export declare const undef: undefined; + export declare let undef2: undefined; + export declare const paren: number; + export declare let paren2: number; + export declare const satisfies: number; + export declare let satisfies2: number; + export declare const sequence: number; + export declare const assign2: number; + export declare const assertion: number; + export declare const assertion2: number; + export declare const obj: { + a: number; + 1: number; + "3": number; + }; + export declare const obj2: { + get str(): string; + set str(v: string); + method(): string; + }; diff --git a/tests/specs/graph/fast_check/inference/const_assertion.txt b/tests/specs/graph/fast_check/inference/const_assertion.txt new file mode 100644 index 000000000..b18d18df4 --- /dev/null +++ b/tests/specs/graph/fast_check/inference/const_assertion.txt @@ -0,0 +1,87 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/a@1.0.0", + "exports": { + ".": "./mod.ts" + } + } +] + +# mod.ts +export let str = "Hello, world!" as const; +export let num = 42 as const; +export let bool = true as const; +export let bigint = 42n as const; + +export let paren = (1) as const; + +export let tuple = [1, 2] as const; +export let tuple2 = [1, [2, "str"]] as const; +export let tuple3 = [1, , 3] as const; + +export let obj = { a: 1, b: "str", c: [1] } as const; + +export let obj2 = { + get str() { return "" }, + set str(v: string) {}, + method(): string {} +} as const; + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "size": 458, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + } + ], + "redirects": {} +} + +Fast check file:///mod.ts: + {} + export declare let str: "Hello, world!"; + export declare let num: 42; + export declare let bool: true; + export declare let bigint: 42n; + export declare let paren: 1; + export declare let tuple: readonly [1, 2]; + export declare let tuple2: readonly [1, readonly [2, "str"]]; + export declare let tuple3: readonly [1, undefined, 3]; + export declare let obj: readonly { + readonly a: 1; + readonly b: "str"; + readonly c: readonly [1]; + }; + export declare let obj2: readonly { + get str(): string; + set str(v: string); + method(): string; + }; + --- DTS --- + export declare let str: "Hello, world!"; + export declare let num: 42; + export declare let bool: true; + export declare let bigint: 42n; + export declare let paren: 1; + export declare let tuple: readonly [1, 2]; + export declare let tuple2: readonly [1, readonly [2, "str"]]; + export declare let tuple3: readonly [1, undefined, 3]; + export declare let obj: readonly { + readonly a: 1; + readonly b: "str"; + readonly c: readonly [1]; + }; + export declare let obj2: readonly { + get str(): string; + set str(v: string); + method(): string; + }; diff --git a/tests/specs/graph/fast_check/inference/error.txt b/tests/specs/graph/fast_check/inference/error.txt new file mode 100644 index 000000000..d15332357 --- /dev/null +++ b/tests/specs/graph/fast_check/inference/error.txt @@ -0,0 +1,359 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/a@1.0.0", + "exports": { + ".": "./mod.ts" + } + } +] + +# mod.ts +export const tplExpr = `asd ${"asd"}`; + +export const arr = [1, 2, 3]; + +export const call = fetch(); + +export const objSpread = { ...{ a: 1 } }; +export const objShorthand = { globalThis }; + +export const tupleSpread = [1, ...[2, 3]] as const; + +export const fnExpr = function (a): void {}; +export const fnExpr2 = function () { return globalThis }; +export const fnExpr3 = function (a?: string, b: string): void { }; +export const fnExpr4 = function (a = "asd", b: string): void { }; + +export const arrowExpr = (a): void => {}; +export const arrowExpr2 = () => globalThis; +export const arrowExpr3 = (a?: string, b: string): void => { }; +export const arrowExpr4 = (a = "asd", b: string): void => { }; + +export const getter = { + get a() { + return globalThis; + } +}; +export const setter = { + set a(value) { + } +}; +export const method = { + a(): void { + return globalThis; + } +}; +export const method2 = { + a(val): void { + } +}; +export const method3 = { + a(val?: string, b: string): void { + } +}; +export const method4 = { + a(val = "asd", b: string): void { + } +}; + + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "size": 1061, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + } + ], + "redirects": {} +} + +Fast check file:///mod.ts: + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:1:14 + | + 1 | export const tplExpr = `asd ${"asd"}`; + | ^^^^^^^ this variable's type could not be inferred because its initializer + | ----- contains this expression in a template literal, which can not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: expressions in template literals can not be inferred because the type of the resulting string can not be narrowed without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:3:14 + | + 3 | export const arr = [1, 2, 3]; + | ^^^ this variable's type could not be inferred because its initializer + | --------- contains this array literal, which can not be inferred unless marked 'as const' + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: array literals without 'as const' can not be inferred because the type of the array can not be narrowed without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:5:14 + | + 5 | export const call = fetch(); + | ^^^^ this variable's type could not be inferred because its initializer + | ------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:7:14 + | + 7 | export const objSpread = { ...{ a: 1 } }; + | ^^^^^^^^^ this variable's type could not be inferred because its initializer + | --- contains this spread property, which can not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: spread properties can not be inferred because the type of the resulting object can not be narrowed without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:8:14 + | + 8 | export const objShorthand = { globalThis }; + | ^^^^^^^^^^^^ this variable's type could not be inferred because its initializer + | ---------- contains this shorthand property, which can not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: shorthand properties can not be inferred because the type of the value referred to by the shorthand property is not known without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:10:14 + | + 10 | export const tupleSpread = [1, ...[2, 3]] as const; + | ^^^^^^^^^^^ this variable's type could not be inferred because its initializer + | --- contains this spread element, which can not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: spread elements can not be inferred because the type of the resulting array can not be narrowed without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:12:14 + | + 12 | export const fnExpr = function (a): void {}; + | ^^^^^^ this variable's type could not be inferred because its initializer + | ------ contains this function expression + | - which has this parameter, the type of which could not be inferred from the default value + | + = hint: add an explicit type annotation to the function expression parameter in the variable declaration initializer + + info: all variables in the public API must have a known type + info: all parameters of a function expression must have an explicit type annotation or an inferrable default value + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:13:14 + | + 13 | export const fnExpr2 = function () { return globalThis }; + | ^^^^^^^ this variable's type could not be inferred because its initializer + | ------- contains this function expression, of which the return type could not be inferred + | ------ because the value returned here + | ---------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type annotation to the function expression in the variable declaration initializer + + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:14:14 + | + 14 | export const fnExpr3 = function (a?: string, b: string): void { }; + | ^^^^^^^ this variable's type could not be inferred because its initializer + | ------- contains this function expression + | - which has this required parameter following an optional parameter or a parameter with a default value + | + = hint: make the function expression parameter in the variable declaration initializer optional or provide a default value + + info: all variables in the public API must have a known type + info: all required parameters of a function expression must precede optional parameters or parameters with default values + info: this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:15:14 + | + 15 | export const fnExpr4 = function (a = "asd", b: string): void { }; + | ^^^^^^^ this variable's type could not be inferred because its initializer + | ------- contains this function expression + | - which has this required parameter following an optional parameter or a parameter with a default value + | + = hint: make the function expression parameter in the variable declaration initializer optional or provide a default value + + info: all variables in the public API must have a known type + info: all required parameters of a function expression must precede optional parameters or parameters with default values + info: this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:17:14 + | + 17 | export const arrowExpr = (a): void => {}; + | ^^^^^^^^^ this variable's type could not be inferred because its initializer + | --------- contains this arrow expression + | - which has this parameter, the type of which could not be inferred from the default value + | + = hint: add an explicit type annotation to the arrow expression parameter in the variable declaration initializer + + info: all variables in the public API must have a known type + info: all parameters of an arrow expression must have an explicit type annotation or an inferrable default value + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:18:14 + | + 18 | export const arrowExpr2 = () => globalThis; + | ^^^^^^^^^^ this variable's type could not be inferred because its initializer + | ---------- contains this arrow expression, of which the return type could not be inferred from the returned value + | ---------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type annotation to the arrow expression in the variable declaration initializer + + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:19:14 + | + 19 | export const arrowExpr3 = (a?: string, b: string): void => { }; + | ^^^^^^^^^^ this variable's type could not be inferred because its initializer + | ---------- contains this arrow expression + | --------- which has this required parameter following an optional parameter or a parameter with a default value + | + = hint: make the arrow expression parameter in the variable declaration initializer optional or provide a default value + + info: all variables in the public API must have a known type + info: all required parameters of an arrow expression must precede optional parameters or parameters with default values + info: this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:20:14 + | + 20 | export const arrowExpr4 = (a = "asd", b: string): void => { }; + | ^^^^^^^^^^ this variable's type could not be inferred because its initializer + | ---------- contains this arrow expression + | --------- which has this required parameter following an optional parameter or a parameter with a default value + | + = hint: make the arrow expression parameter in the variable declaration initializer optional or provide a default value + + info: all variables in the public API must have a known type + info: all required parameters of an arrow expression must precede optional parameters or parameters with default values + info: this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:22:14 + | + 22 | export const getter = { + | ^^^^^^ this variable's type could not be inferred because its initializer + | + 23 | get a() { + | - contains this object getter, of which the return type could not be inferred + | + 24 | return globalThis; + | ------ because the value returned here + | ---------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type annotation to the object getter in the variable declaration initializer + + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:27:14 + | + 27 | export const setter = { + | ^^^^^^ this variable's type could not be inferred because its initializer + | + 28 | set a(value) { + | - contains this object setter + | ----- which has this parameter, the type of which could not be inferred from the default value + | + = hint: add an explicit type annotation to the object setter parameter in the variable declaration initializer + + info: all variables in the public API must have a known type + info: all parameters of an object setter must have an explicit type annotation or an inferrable default value + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:36:14 + | + 36 | export const method2 = { + | ^^^^^^^ this variable's type could not be inferred because its initializer + | + 37 | a(val): void { + | - contains this object method + | --- which has this parameter, the type of which could not be inferred from the default value + | + = hint: add an explicit type annotation to the object method parameter in the variable declaration initializer + + info: all variables in the public API must have a known type + info: all parameters of an object method must have an explicit type annotation or an inferrable default value + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:40:14 + | + 40 | export const method3 = { + | ^^^^^^^ this variable's type could not be inferred because its initializer + | + 41 | a(val?: string, b: string): void { + | - contains this object method + | - which has this required parameter following an optional parameter or a parameter with a default value + | + = hint: make the object method parameter in the variable declaration initializer optional or provide a default value + + info: all variables in the public API must have a known type + info: all required parameters of an object method must precede optional parameters or parameters with default values + info: this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:44:14 + | + 44 | export const method4 = { + | ^^^^^^^ this variable's type could not be inferred because its initializer + | + 45 | a(val = "asd", b: string): void { + | - contains this object method + | - which has this required parameter following an optional parameter or a parameter with a default value + | + = hint: make the object method parameter in the variable declaration initializer optional or provide a default value + + info: all variables in the public API must have a known type + info: all required parameters of an object method must precede optional parameters or parameters with default values + info: this is because to compute the type of a optional parameter that is followed by a required parameter, a type checker is needed + docs: https://jsr.io/go/slow-type-unknown-var-type + diff --git a/tests/specs/graph/fast_check/inferred_unique_symbols.txt b/tests/specs/graph/fast_check/inferred_unique_symbols.txt index ea7fec7e2..3b7e254d4 100644 --- a/tests/specs/graph/fast_check/inferred_unique_symbols.txt +++ b/tests/specs/graph/fast_check/inferred_unique_symbols.txt @@ -63,8 +63,8 @@ import 'jsr:@scope/a' Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: {} - const key: unique symbol = {} as never; - const key2: unique symbol = {} as never; + declare const key: unique symbol; + declare const key2: unique symbol; export class MyClass { declare [key]: number; declare [key2]: string; diff --git a/tests/specs/graph/fast_check/inferred_unique_symbols__not_global.txt b/tests/specs/graph/fast_check/inferred_unique_symbols__not_global.txt index 852fdf536..8fb11cc7d 100644 --- a/tests/specs/graph/fast_check/inferred_unique_symbols__not_global.txt +++ b/tests/specs/graph/fast_check/inferred_unique_symbols__not_global.txt @@ -65,13 +65,16 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-type]: missing explicit type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> https://jsr.io/@scope/a/1.0.0/mod.ts:4:7 | 4 | const key = Symbol("#keys"); - | ^^^ this symbol is missing an explicit type - = hint: add an explicit type annotation to the symbol + | ^^^ this variable's type could not be inferred because its initializer + | --------------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit type annotation to the variable declaration - info: all symbols in the public API must have an explicit type - docs: https://jsr.io/go/slow-type-missing-explicit-type + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type diff --git a/tests/specs/graph/fast_check/init_idents_and_members.txt b/tests/specs/graph/fast_check/init_idents_and_members.txt index 7e1cb06ef..423c998ce 100644 --- a/tests/specs/graph/fast_check/init_idents_and_members.txt +++ b/tests/specs/graph/fast_check/init_idents_and_members.txt @@ -19,7 +19,11 @@ namespace Test { export class B {} } -export const handlers = { +export const handlers: { + BaseHandler: typeof BaseHandler, + Private: typeof Public, + C: typeof Test.A, +} = { BaseHandler, Private: Public, C: Test.A, @@ -42,8 +46,8 @@ namespace Test { } export function test( - a = BaseHandler, - C = Test.A, + a: typeof BaseHandler = BaseHandler, + C: typeof Test.A = Test.A, ) { } @@ -99,13 +103,13 @@ import 'jsr:@scope/b' }, { "kind": "esm", - "size": 224, + "size": 310, "mediaType": "TypeScript", "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" }, { "kind": "esm", - "size": 173, + "size": 208, "mediaType": "TypeScript", "specifier": "https://jsr.io/@scope/b/1.0.0/mod.ts" } @@ -130,10 +134,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export class A { } } - export const handlers = { - BaseHandler, - Private: Public, - C: Test.A + export declare const handlers: { + BaseHandler: typeof BaseHandler; + Private: typeof Public; + C: typeof Test.A; }; --- DTS --- declare class BaseHandler { @@ -145,12 +149,11 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } } export declare const handlers: { - readonly Private; - readonly C; + BaseHandler: typeof BaseHandler; + Private: typeof Public; + C: typeof Test.A; }; - --- DTS Diagnostics --- - unable to infer type from object property, skipping - at https://jsr.io/@scope/a/1.0.0/mod.ts@176 + Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: {} class BaseHandler { @@ -159,7 +162,7 @@ Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: export class A { } } - export function test(a = BaseHandler, C = Test.A): void {} + export function test(a?: typeof BaseHandler, C?: typeof Test.A): void {} --- DTS --- declare class BaseHandler { } @@ -167,9 +170,4 @@ Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: export class A { } } - export declare function test(a?: any, C?: any): void; - --- DTS Diagnostics --- - unable to infer type, falling back to any type - at https://jsr.io/@scope/b/1.0.0/mod.ts@140 - unable to infer type, falling back to any type - at https://jsr.io/@scope/b/1.0.0/mod.ts@159 + export declare function test(a?: typeof BaseHandler, C?: typeof Test.A): void; diff --git a/tests/specs/graph/fast_check/issue_22544.txt b/tests/specs/graph/fast_check/issue_22544.txt index f30bc6c29..83d1de1b0 100644 --- a/tests/specs/graph/fast_check/issue_22544.txt +++ b/tests/specs/graph/fast_check/issue_22544.txt @@ -67,10 +67,10 @@ import 'jsr:@scope/a' Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: {} - const rawString1: Symbol = {} as never; - const rawString2: Symbol = {} as never; - const rawString3: Symbol = {} as never; - const rawString4: Symbol = {} as never; + declare const rawString1: Symbol; + declare const rawString2: Symbol; + declare const rawString3: Symbol; + declare const rawString4: Symbol; export interface RawString { [rawString1]: string; [rawString2](): void; diff --git a/tests/specs/graph/fast_check/issue_22829.txt b/tests/specs/graph/fast_check/issue_22829.txt index 6ec70be5b..5761bd45f 100644 --- a/tests/specs/graph/fast_check/issue_22829.txt +++ b/tests/specs/graph/fast_check/issue_22829.txt @@ -100,57 +100,19 @@ import 'jsr:@scope/a' } } -Fast check https://jsr.io/@scope/a/1.0.0/functions.ts: - {} - abstract class Internal { - declare prop: T; - } - function func(): void {} - export function other_func(): void {} - export const functions = { - func, - other: other_func - }; - export declare namespace functions { - type Function = Internal; - } - --- DTS --- - declare abstract class Internal { - prop: T; - } - declare function func(): void; - export declare function other_func(): void; - export declare const functions: { - readonly other; - }; - export declare namespace functions { - type Function = Internal; - } - --- DTS Diagnostics --- - unable to infer type from object property, skipping - at https://jsr.io/@scope/a/1.0.0/functions.ts@125 Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - { - "./functions.ts": { - "code": { - "specifier": "https://jsr.io/@scope/a/1.0.0/functions.ts", - "span": { - "start": { - "line": 0, - "character": 26 - }, - "end": { - "line": 0, - "character": 42 - } - } - } - } - } - import { functions } from "./functions.ts"; - export type Test = functions.Function; - export { functions } from "./functions.ts"; - --- DTS --- - import { functions } from "./functions.ts"; - export type Test = functions.Function; - export { functions } from "./functions.ts"; + error[unknown-var-type]: unknown type for variable in the public API + --> https://jsr.io/@scope/a/1.0.0/functions.ts:9:14 + | + 9 | export const functions = { + | ^^^^^^^^^ this variable's type could not be inferred because its initializer + | + 10 | func, + | ---- contains this shorthand property, which can not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: shorthand properties can not be inferred because the type of the value referred to by the shorthand property is not known without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + diff --git a/tests/specs/graph/fast_check/issue_23658_1.txt b/tests/specs/graph/fast_check/issue_23658_1.txt index 486b94800..c5c535119 100644 --- a/tests/specs/graph/fast_check/issue_23658_1.txt +++ b/tests/specs/graph/fast_check/issue_23658_1.txt @@ -90,43 +90,17 @@ import 'jsr:@scope/a' } } -Fast check https://jsr.io/@scope/a/1.0.0/base.ts: - {} - export class Base { - } - --- DTS --- - export declare class Base { - } - Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - { - "./base.ts": { - "code": { - "specifier": "https://jsr.io/@scope/a/1.0.0/base.ts", - "span": { - "start": { - "line": 0, - "character": 30 - }, - "end": { - "line": 0, - "character": 41 - } - } - } - } - } - import * as adapter_base from "./base.ts"; - class ServerAdapter extends adapter_base.Base { - static adapt(): void {} - } - export const adapt = ServerAdapter.adapt; - --- DTS --- - import * as adapter_base from "./base.ts"; - declare class ServerAdapter extends adapter_base.Base { - static adapt(): void; - } - export declare const adapt: any; - --- DTS Diagnostics --- - unable to infer type, falling back to any type - at https://jsr.io/@scope/a/1.0.0/mod.ts@130 + error[unknown-var-type]: unknown type for variable in the public API + --> https://jsr.io/@scope/a/1.0.0/mod.ts:8:14 + | + 8 | export const adapt = ServerAdapter.adapt; + | ^^^^^ this variable's type could not be inferred because its initializer + | ------------------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + diff --git a/tests/specs/graph/fast_check/issue_23658_2.txt b/tests/specs/graph/fast_check/issue_23658_2.txt index 3222b7619..0e0454100 100644 --- a/tests/specs/graph/fast_check/issue_23658_2.txt +++ b/tests/specs/graph/fast_check/issue_23658_2.txt @@ -117,68 +117,17 @@ import 'jsr:@scope/a' } } -Fast check https://jsr.io/@scope/a/1.0.0/base.ts: - {} - export class Base { - } - --- DTS --- - export declare class Base { - } +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + error[unknown-var-type]: unknown type for variable in the public API + --> https://jsr.io/@scope/a/1.0.0/mod.ts:9:14 + | + 9 | export const adapt = ServerAdapter.adapt; + | ^^^^^ this variable's type could not be inferred because its initializer + | ------------------- contains this expression, of which the type could not be inferred + | + = hint: add an explicit type annotation to the variable declaration -Fast check https://jsr.io/@scope/a/1.0.0/interface.ts: - {} - export class IInterface { - } - --- DTS --- - export declare class IInterface { - } + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type -Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - { - "./base.ts": { - "code": { - "specifier": "https://jsr.io/@scope/a/1.0.0/base.ts", - "span": { - "start": { - "line": 0, - "character": 21 - }, - "end": { - "line": 0, - "character": 32 - } - } - } - }, - "./interface.ts": { - "type": { - "specifier": "https://jsr.io/@scope/a/1.0.0/interface.ts", - "span": { - "start": { - "line": 1, - "character": 32 - }, - "end": { - "line": 1, - "character": 48 - } - } - } - } - } - import { Base } from "./base.ts"; - import type { IInterface } from "./interface.ts"; - class ServerAdapter extends Base implements IInterface { - static adapt(): void {} - } - export const adapt = ServerAdapter.adapt; - --- DTS --- - import { Base } from "./base.ts"; - import type { IInterface } from "./interface.ts"; - declare class ServerAdapter extends Base implements IInterface { - static adapt(): void; - } - export declare const adapt: any; - --- DTS Diagnostics --- - unable to infer type, falling back to any type - at https://jsr.io/@scope/a/1.0.0/mod.ts@180 diff --git a/tests/specs/graph/fast_check/missing_return_type.txt b/tests/specs/graph/fast_check/missing_return_type.txt index cd31786bf..37b76a78e 100644 --- a/tests/specs/graph/fast_check/missing_return_type.txt +++ b/tests/specs/graph/fast_check/missing_return_type.txt @@ -58,13 +58,19 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/mod.ts:1:17 | 1 | export function missingReturnType() { - | ^^^^^^^^^^^^^^^^^ this function is missing an explicit return type + | ----------------- this function's return type could not be inferred + | + 2 | return Math.random(); + | ------ because the value returned here + | ------------- contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type diff --git a/tests/specs/graph/fast_check/missing_return_type_generator.txt b/tests/specs/graph/fast_check/missing_return_type_generator.txt index 4373df47e..68ecd9156 100644 --- a/tests/specs/graph/fast_check/missing_return_type_generator.txt +++ b/tests/specs/graph/fast_check/missing_return_type_generator.txt @@ -57,13 +57,15 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> https://jsr.io/@scope/a/1.0.0/mod.ts:1:18 | 1 | export function* missingReturnType() { - | ^^^^^^^^^^^^^^^^^ this function is missing an explicit return type + | ----------------- this function is missing an explicit return type annotation + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: a function declaration that is a generator must have an explicit return type annotation because the return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type diff --git a/tests/specs/graph/fast_check/ref_obj_type.txt b/tests/specs/graph/fast_check/ref_obj_type.txt index 1557cc598..f05cde9f0 100644 --- a/tests/specs/graph/fast_check/ref_obj_type.txt +++ b/tests/specs/graph/fast_check/ref_obj_type.txt @@ -68,18 +68,18 @@ import 'jsr:@scope/a' Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: {} - export const STATUS_CODE = { - /** RFC 7231, 6.2.1 */ Continue: 100, - /** RFC 7231, 6.2.2 */ SwitchingProtocols: 101, - /** RFC 2518, 10.1 */ Processing: 102, - /** RFC 8297 **/ EarlyHints: 103 + export declare const STATUS_CODE: { + /** RFC 7231, 6.2.1 */ Continue: number; + /** RFC 7231, 6.2.2 */ SwitchingProtocols: number; + /** RFC 2518, 10.1 */ Processing: number; + /** RFC 8297 **/ EarlyHints: number; }; export type Status = typeof STATUS_CODE.Continue; --- DTS --- export declare const STATUS_CODE: { - readonly /** RFC 7231, 6.2.1 */ Continue: number; - readonly /** RFC 7231, 6.2.2 */ SwitchingProtocols: number; - readonly /** RFC 2518, 10.1 */ Processing: number; - readonly /** RFC 8297 **/ EarlyHints: number; + /** RFC 7231, 6.2.1 */ Continue: number; + /** RFC 7231, 6.2.2 */ SwitchingProtocols: number; + /** RFC 2518, 10.1 */ Processing: number; + /** RFC 8297 **/ EarlyHints: number; }; export type Status = typeof STATUS_CODE.Continue; diff --git a/tests/specs/graph/fast_check/ref_var_no_type.txt b/tests/specs/graph/fast_check/ref_var_no_type.txt index 151189b42..e87f3896b 100644 --- a/tests/specs/graph/fast_check/ref_var_no_type.txt +++ b/tests/specs/graph/fast_check/ref_var_no_type.txt @@ -62,14 +62,14 @@ import 'jsr:@scope/a' Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: {} - export const symbols = { - writable: function(): void {}, - readable: 1234 + export declare const symbols: { + writable: () => void; + readable: number; }; export type Status = typeof symbols.writable; --- DTS --- export declare const symbols: { - readonly writable: () => void; - readonly readable: number; + writable: () => void; + readable: number; }; export type Status = typeof symbols.writable; diff --git a/tests/specs/graph/fast_check/ref_var_type.txt b/tests/specs/graph/fast_check/ref_var_type.txt index 172438fe9..4dd64992e 100644 --- a/tests/specs/graph/fast_check/ref_var_type.txt +++ b/tests/specs/graph/fast_check/ref_var_type.txt @@ -67,7 +67,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: interface Symbols { readable: unique symbol; } - export const symbols: Symbols = {} as never; + export declare const symbols: Symbols; export type Status = typeof symbols.readable; --- DTS --- interface Symbols { diff --git a/tests/specs/graph/fast_check/return_inference/empty.txt b/tests/specs/graph/fast_check/return_inference/empty.txt index f1d0b803e..a5adf7afd 100644 --- a/tests/specs/graph/fast_check/return_inference/empty.txt +++ b/tests/specs/graph/fast_check/return_inference/empty.txt @@ -25,11 +25,10 @@ export class D { } } -// TODO: these are not yet handled -// export const e = { -// e() { -// } -// }; +export const e = { + e() { + } +}; // TODO: these are not yet handled // export const f = class { @@ -46,7 +45,7 @@ export class D { "modules": [ { "kind": "esm", - "size": 288, + "size": 241, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -56,12 +55,15 @@ export class D { Fast check file:///mod.ts: {} - export const a = (): void =>{}; - export const b = function(): void {}; + export declare const a: () => void; + export declare const b: () => void; export function c(): void {} export class D { d(): void {} } + export declare const e: { + e(): void; + }; --- DTS --- export declare const a: () => void; export declare const b: () => void; @@ -69,3 +71,6 @@ Fast check file:///mod.ts: export declare class D { d(): void; } + export declare const e: { + e(): void; + }; diff --git a/tests/specs/graph/fast_check/return_inference/generator_err.txt b/tests/specs/graph/fast_check/return_inference/generator_err.txt new file mode 100644 index 000000000..9ab2178da --- /dev/null +++ b/tests/specs/graph/fast_check/return_inference/generator_err.txt @@ -0,0 +1,102 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/a@1.0.0", + "exports": { + ".": "./mod.ts" + } + } +] + +# mod.ts +// Arrow functions can not be generator functions + +export const b = function*() { +} + +export function* c() { +} + +export class D { + *d() { + } +} + +export const e = { + *e() { + } +}; + + + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "size": 181, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + } + ], + "redirects": {} +} + +Fast check file:///mod.ts: + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:3:14 + | + 3 | export const b = function*() { + | ^ this variable's type could not be inferred because its initializer + | - contains this function expression, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the function expression in the variable declaration initializer + + info: all variables in the public API must have a known type + info: a function expression that is a generator must have an explicit return type annotation because the return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-return-type]: unknown return type for function in the public API + --> /mod.ts:6:18 + | + 6 | export function* c() { + | - this function is missing an explicit return type annotation + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an known return type + info: a function declaration that is a generator must have an explicit return type annotation because the return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type + + error[unknown-return-type]: unknown return type for function in the public API + --> /mod.ts:10:4 + | + 10 | *d() { + | - this function is missing an explicit return type annotation + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an known return type + info: a function declaration that is a generator must have an explicit return type annotation because the return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:14:14 + | + 14 | export const e = { + | ^ this variable's type could not be inferred because its initializer + | + 15 | *e() { + | - contains this object method, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the object method in the variable declaration initializer + + info: all variables in the public API must have a known type + info: an object method that is a generator must have an explicit return type annotation because the return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + diff --git a/tests/specs/graph/fast_check/return_inference/multiple_value_return_err.txt b/tests/specs/graph/fast_check/return_inference/multiple_value_return_err.txt index fa29c8b10..6e48ad05f 100644 --- a/tests/specs/graph/fast_check/return_inference/multiple_value_return_err.txt +++ b/tests/specs/graph/fast_check/return_inference/multiple_value_return_err.txt @@ -33,13 +33,12 @@ export class D { } } -// TODO: we don't support this at all -// export const e = { -// e() { -// if (globalThis) return 1; -// return 2; -// } -// }; +export const e = { + e() { + if (globalThis) return 1; + return 2; + } +}; // TODO: we don't support this at all // export const f = class { @@ -57,7 +56,7 @@ export class D { "modules": [ { "kind": "esm", - "size": 557, + "size": 501, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -66,43 +65,68 @@ export class D { } Fast check file:///mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> /mod.ts:1:14 | 1 | export const a = () => { - | ^ this function is missing an explicit return type - = hint: add an explicit return type to the function + | ^ this variable's type could not be inferred because its initializer + | - contains this arrow expression, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the arrow expression in the variable declaration initializer - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all variables in the public API must have a known type + info: an arrow expression has multiple return statements with values, so a return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> /mod.ts:6:14 | 6 | export const b = function() { - | ^ this function is missing an explicit return type - = hint: add an explicit return type to the function + | ^ this variable's type could not be inferred because its initializer + | - contains this function expression, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the function expression in the variable declaration initializer - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all variables in the public API must have a known type + info: a function expression has multiple return statements with values, so a return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> /mod.ts:11:17 | 11 | export function c() { - | ^ this function is missing an explicit return type + | - this function is missing an explicit return type annotation + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: a function declaration has multiple return statements with values, so a return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> /mod.ts:17:3 | 17 | d() { - | ^ this function is missing an explicit return type + | - this function is missing an explicit return type annotation + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: a function declaration has multiple return statements with values, so a return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:23:14 + | + 23 | export const e = { + | ^ this variable's type could not be inferred because its initializer + | + 24 | e() { + | - contains this object method, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the object method in the variable declaration initializer + + info: all variables in the public API must have a known type + info: an object method has multiple return statements with values, so a return type can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type diff --git a/tests/specs/graph/fast_check/return_inference/multiple_void_return.txt b/tests/specs/graph/fast_check/return_inference/multiple_void_return.txt index 9f040e1a2..aa1d69634 100644 --- a/tests/specs/graph/fast_check/return_inference/multiple_void_return.txt +++ b/tests/specs/graph/fast_check/return_inference/multiple_void_return.txt @@ -33,13 +33,12 @@ export class D { } } -// TODO: these are not yet handled -// export const e = { -// e() { -// if (globalThis) return; -// return; -// } -// }; +export const e = { + e() { + if (globalThis) return; + return; + } +}; // TODO: these are not yet handled // export const f = class { @@ -58,7 +57,7 @@ export class D { "modules": [ { "kind": "esm", - "size": 528, + "size": 475, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -68,12 +67,15 @@ export class D { Fast check file:///mod.ts: {} - export const a = (): void =>{}; - export const b = function(): void {}; + export declare const a: () => void; + export declare const b: () => void; export function c(): void {} export class D { d(): void {} } + export declare const e: { + e(): void; + }; --- DTS --- export declare const a: () => void; export declare const b: () => void; @@ -81,3 +83,6 @@ Fast check file:///mod.ts: export declare class D { d(): void; } + export declare const e: { + e(): void; + }; diff --git a/tests/specs/graph/fast_check/return_inference/no_return.txt b/tests/specs/graph/fast_check/return_inference/no_return.txt index 6cdde238f..a5285df7e 100644 --- a/tests/specs/graph/fast_check/return_inference/no_return.txt +++ b/tests/specs/graph/fast_check/return_inference/no_return.txt @@ -23,7 +23,6 @@ export class D { } } - # output { "roots": [ @@ -32,7 +31,7 @@ export class D { "modules": [ { "kind": "esm", - "size": 112, + "size": 111, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } diff --git a/tests/specs/graph/fast_check/return_inference/no_return_err.txt b/tests/specs/graph/fast_check/return_inference/no_return_err.txt index 4e3ddae38..6eeebaa78 100644 --- a/tests/specs/graph/fast_check/return_inference/no_return_err.txt +++ b/tests/specs/graph/fast_check/return_inference/no_return_err.txt @@ -25,12 +25,11 @@ export const b = function() { // This can be inferred for class declarations. -// TODO: we don't support this at all -// export const e = { -// e() { -// exit(); -// } -// }; +export const e = { + e() { + exit(); + } +}; // TODO: we don't support this at all // export const f = class { @@ -48,7 +47,7 @@ export const b = function() { "modules": [ { "kind": "esm", - "size": 421, + "size": 368, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -57,27 +56,47 @@ export const b = function() { } Fast check file:///mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> /mod.ts:3:14 | 3 | export const a = () => { - | ^ this function is missing an explicit return type - = hint: add an explicit return type of 'void' or 'never' to the function + | ^ this variable's type could not be inferred because its initializer + | - contains this arrow expression, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the arrow expression in the variable declaration initializer - info: all functions in the public API must have an explicit return type - info: function expressions without a return statement can have a return type of either 'void' or 'never' - info: this function has no return statements, so a return type could not be inferred automatically - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all variables in the public API must have a known type + info: the return type of an arrow expression can not be inferred if it has no return statements + info: this is because an arrow expressions without a return statement can either return 'void' or 'never', and the specific type can not be determined without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> /mod.ts:7:14 | 7 | export const b = function() { - | ^ this function is missing an explicit return type - = hint: add an explicit return type of 'void' or 'never' to the function + | ^ this variable's type could not be inferred because its initializer + | - contains this function expression, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the function expression in the variable declaration initializer + + info: all variables in the public API must have a known type + info: the return type of a function expression can not be inferred if it has no return statements + info: this is because a function expressions without a return statement can either return 'void' or 'never', and the specific type can not be determined without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:15:14 + | + 15 | export const e = { + | ^ this variable's type could not be inferred because its initializer + | + 16 | e() { + | - contains this object method, which is missing an explicit return type annotation + | + = hint: add an explicit return type annotation to the object method in the variable declaration initializer - info: all functions in the public API must have an explicit return type - info: function expressions without a return statement can have a return type of either 'void' or 'never' - info: this function has no return statements, so a return type could not be inferred automatically - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all variables in the public API must have a known type + info: the return type of an object method can not be inferred if it has no return statements + info: this is because an object methods without a return statement can either return 'void' or 'never', and the specific type can not be determined without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type diff --git a/tests/specs/graph/fast_check/return_inference/value_return.txt b/tests/specs/graph/fast_check/return_inference/value_return.txt new file mode 100644 index 000000000..52c7e4e54 --- /dev/null +++ b/tests/specs/graph/fast_check/return_inference/value_return.txt @@ -0,0 +1,85 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/a@1.0.0", + "exports": { + ".": "./mod.ts" + } + } +] + +# mod.ts +export const a = () => { + return 2; +} + +export const b = function() { + return 2; +} + +export function c() { + return 2; +} + +export class D { + d() { + return 2; + } +} + +export const e = { + e() { + return 2; + } +}; + +// TODO: we don't support this at all +// export const f = class { +// f() { +// return 2; +// } +// }; + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "size": 324, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + } + ], + "redirects": {} +} + +Fast check file:///mod.ts: + {} + export declare const a: () => number; + export declare const b: () => number; + export function c(): number { + return {} as never; + } + export class D { + d(): number { + return {} as never; + } + } + export declare const e: { + e(): number; + }; + --- DTS --- + export declare const a: () => number; + export declare const b: () => number; + export declare function c(): number; + export declare class D { + d(): number; + } + export declare const e: { + e(): number; + }; diff --git a/tests/specs/graph/fast_check/return_inference/value_return_err.txt b/tests/specs/graph/fast_check/return_inference/value_return_err.txt index 095e1d54d..e60e448cb 100644 --- a/tests/specs/graph/fast_check/return_inference/value_return_err.txt +++ b/tests/specs/graph/fast_check/return_inference/value_return_err.txt @@ -12,29 +12,28 @@ # mod.ts export const a = () => { - return 2; + return a; } export const b = function() { - return 2; + return b; } export function c() { - return 2; + return c; } export class D { d() { - return 2; + return D; } } -// TODO: we don't support this at all -// export const e = { -// e() { -// return 2; -// } -// }; +export const e = { + e() { + return e; + } +}; // TODO: we don't support this at all // export const f = class { @@ -51,7 +50,7 @@ export class D { "modules": [ { "kind": "esm", - "size": 377, + "size": 324, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -60,43 +59,88 @@ export class D { } Fast check file:///mod.ts: - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> /mod.ts:1:14 | 1 | export const a = () => { - | ^ this function is missing an explicit return type - = hint: add an explicit return type to the function + | ^ this variable's type could not be inferred because its initializer + | - contains this arrow expression, of which the return type could not be inferred + | + 2 | return a; + | ------ because the value returned here + | - contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type annotation to the arrow expression in the variable declaration initializer - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-var-type]: unknown type for variable in the public API --> /mod.ts:5:14 | 5 | export const b = function() { - | ^ this function is missing an explicit return type - = hint: add an explicit return type to the function + | ^ this variable's type could not be inferred because its initializer + | - contains this function expression, of which the return type could not be inferred + | + 6 | return b; + | ------ because the value returned here + | - contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type annotation to the function expression in the variable declaration initializer - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type - error[missing-explicit-return-type]: missing explicit return type in the public API - --> /mod.ts:9:17 - | - 9 | export function c() { - | ^ this function is missing an explicit return type - = hint: add an explicit return type to the function + error[unknown-return-type]: unknown return type for function in the public API + --> /mod.ts:9:17 + | + 9 | export function c() { + | - this function's return type could not be inferred + | + 10 | return c; + | ------ because the value returned here + | - contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type - error[missing-explicit-return-type]: missing explicit return type in the public API + error[unknown-return-type]: unknown return type for function in the public API --> /mod.ts:14:3 | 14 | d() { - | ^ this function is missing an explicit return type + | - this function's return type could not be inferred + | + 15 | return D; + | ------ because the value returned here + | - contains this expression, of which the type could not be inferred + | = hint: add an explicit return type to the function - info: all functions in the public API must have an explicit return type - docs: https://jsr.io/go/slow-type-missing-explicit-return-type + info: all functions in the public API must have an known return type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-return-type + + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:19:14 + | + 19 | export const e = { + | ^ this variable's type could not be inferred because its initializer + | + 20 | e() { + | - contains this object method, of which the return type could not be inferred + | + 21 | return e; + | ------ because the value returned here + | - contains this expression, of which the type could not be inferred + | + = hint: add an explicit return type annotation to the object method in the variable declaration initializer + + info: all variables in the public API must have a known type + info: the type of arbitrary expressions can not be inferred without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type diff --git a/tests/specs/graph/fast_check/return_inference/void_return.txt b/tests/specs/graph/fast_check/return_inference/void_return.txt index fcd21b032..d6703a3d1 100644 --- a/tests/specs/graph/fast_check/return_inference/void_return.txt +++ b/tests/specs/graph/fast_check/return_inference/void_return.txt @@ -29,12 +29,11 @@ export class D { } } -// TODO: these are not yet handled -// export const e = { -// e() { -// return; -// } -// }; +export const e = { + e() { + return; + } +}; // TODO: these are not yet handled // export const f = class { @@ -52,7 +51,7 @@ export class D { "modules": [ { "kind": "esm", - "size": 360, + "size": 310, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -62,12 +61,15 @@ export class D { Fast check file:///mod.ts: {} - export const a = (): void =>{}; - export const b = function(): void {}; + export declare const a: () => void; + export declare const b: () => void; export function c(): void {} export class D { d(): void {} } + export declare const e: { + e(): void; + }; --- DTS --- export declare const a: () => void; export declare const b: () => void; @@ -75,3 +77,6 @@ Fast check file:///mod.ts: export declare class D { d(): void; } + export declare const e: { + e(): void; + }; diff --git a/tests/specs/graph/fast_check/test_test_test.txt b/tests/specs/graph/fast_check/test_test_test.txt new file mode 100644 index 000000000..b8347af7e --- /dev/null +++ b/tests/specs/graph/fast_check/test_test_test.txt @@ -0,0 +1,54 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/a@1.0.0", + "exports": { + ".": "./mod.ts" + } + } +] + +# mod.ts +export function a() { + var x = ""; + return {} as typeof x; +}; + +export function a(b: string): typeof b { +}; + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "size": 65, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + } + ], + "redirects": {} +} + +Fast check file:///mod.ts: + error[unknown-return-type]: unknown return type for function in the public API + --> /mod.ts:1:17 + | + 1 | export function a () { + | - this function's return type could not be inferred + | + 3 | return {} as typeof x; + | ------ because the value returned here + | - contains this reference to a local variable or type + | + = hint: add an explicit return type to the function + + info: all functions in the public API must have an known return type + info: local variables or types can not be referenced because they are not visible at the top level of the module + docs: https://jsr.io/go/slow-type-unknown-return-type + diff --git a/tests/specs/graph/fast_check/ts_as_const.txt b/tests/specs/graph/fast_check/ts_as_const.txt index c818b8a78..6b9bcb980 100644 --- a/tests/specs/graph/fast_check/ts_as_const.txt +++ b/tests/specs/graph/fast_check/ts_as_const.txt @@ -69,40 +69,18 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - {} - export const foo = [ - 1, - 2 - ] as const; - export const bar = [ - 1, - , - 2 - ] as const; - export const obj = { - str: "bar", - bool: true, - bool2: false, - num: 42, - nullish: null - } as const; - export const spread = { - foo: 1, - ...obj - } as const; - --- DTS --- - export declare const foo: readonly [1, 2]; - export declare const bar: readonly [1, any, 2]; - export declare const obj: { - readonly str: "bar"; - readonly bool: true; - readonly bool2: false; - readonly num: 42; - readonly nullish: null; - }; - export declare const spread: { - readonly foo: 1; - }; - --- DTS Diagnostics --- - unable to infer type from spread, skipping - at https://jsr.io/@scope/a/1.0.0/mod.ts@215 + error[unknown-var-type]: unknown type for variable in the public API + --> https://jsr.io/@scope/a/1.0.0/mod.ts:11:14 + | + 11 | export const spread = { + | ^^^^^^ this variable's type could not be inferred because its initializer + | + 13 | ...obj, + | --- contains this spread property, which can not be inferred + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: spread properties can not be inferred because the type of the resulting object can not be narrowed without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + diff --git a/tests/specs/graph/fast_check/ts_const_assertion.txt b/tests/specs/graph/fast_check/ts_const_assertion.txt index c2a592589..2088c08b9 100644 --- a/tests/specs/graph/fast_check/ts_const_assertion.txt +++ b/tests/specs/graph/fast_check/ts_const_assertion.txt @@ -62,25 +62,22 @@ import 'jsr:@scope/a' Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: {} - export const a = "foo" as const; - export const b = 2 as const; - export const c = true as const; - export const d = false as const; - export const g = [ - 1, - 2 - ] as const; - export const h = { - a: 1, - b: 2 - } as const; + export declare const a = "foo"; + export declare const b = 2; + export declare const c = true; + export declare const d = false; + export declare const g: readonly [1, 2]; + export declare const h: readonly { + readonly a: 1; + readonly b: 2; + }; --- DTS --- - export declare const a: "foo"; - export declare const b: 2; - export declare const c: true; - export declare const d: false; + export declare const a: string; + export declare const b: number; + export declare const c: boolean; + export declare const d: boolean; export declare const g: readonly [1, 2]; - export declare const h: { + export declare const h: readonly { readonly a: 1; readonly b: 2; }; diff --git a/tests/specs/graph/fast_check/type_assertion.txt b/tests/specs/graph/fast_check/type_assertion.txt index 2b26b0dbb..347786c13 100644 --- a/tests/specs/graph/fast_check/type_assertion.txt +++ b/tests/specs/graph/fast_check/type_assertion.txt @@ -94,9 +94,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export class Application { declare prop: S; } - export const str: "foo" & { + export declare const str: "foo" & { __brand: "foo"; - } = {} as never; + }; export function createMockApp = Record>(state?: S): Application { return {} as never; } @@ -106,7 +106,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: declare handler: Public2; declare secondProp: Public4; } - export const var1: Public5 = {} as never; + export declare const var1: Public5; interface Public1 { } interface Public2 { @@ -121,9 +121,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } class Public7 { } - export const str2: "foo" & { + export declare const str2: "foo" & { data: Public7; - } = {} as never; + }; --- DTS --- export declare class Application { prop: S; diff --git a/tests/specs/graph/fast_check/unsupported_default_export_expr.txt b/tests/specs/graph/fast_check/unsupported_default_export_expr.txt index ff104fe91..1f8e744d4 100644 --- a/tests/specs/graph/fast_check/unsupported_default_export_expr.txt +++ b/tests/specs/graph/fast_check/unsupported_default_export_expr.txt @@ -65,8 +65,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 3 | export default { | ^^^^^^^^^^^^^^^^ + | 4 | test: new Class(), | ^^^^^^^^^^^^^^^^^^^^ + | 5 | }; | ^^ = hint: add an 'as' clause with an explicit type after the expression, or extract to a variable diff --git a/tests/specs/graph/fast_check/unsupported_private_member_ref.txt b/tests/specs/graph/fast_check/unsupported_private_member_ref.txt index 91a090372..992e88cd1 100644 --- a/tests/specs/graph/fast_check/unsupported_private_member_ref.txt +++ b/tests/specs/graph/fast_check/unsupported_private_member_ref.txt @@ -64,6 +64,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: | 3 | private myPrivateMember!: string; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is the reference + | = hint: extract the type of the private member to a type alias and reference the type alias instead info: private members can not be referenced from public API members diff --git a/tests/specs/graph/fast_check/var_arrow_leavable.txt b/tests/specs/graph/fast_check/var_arrow_leavable.txt index 63a452a90..35340d297 100644 --- a/tests/specs/graph/fast_check/var_arrow_leavable.txt +++ b/tests/specs/graph/fast_check/var_arrow_leavable.txt @@ -115,8 +115,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } } import type { Foo, Bar } from "./b.ts"; - export const foo = (foo: Foo): string =>({} as never); - export const bar = (): Bar =>({} as never); + export declare const foo: (foo: Foo) => string; + export declare const bar: () => Bar; --- DTS --- import type { Foo, Bar } from "./b.ts"; export declare const foo: (foo: Foo) => string; diff --git a/tests/specs/graph/fast_check/var_function_leavable.txt b/tests/specs/graph/fast_check/var_function_leavable.txt index 894462395..96131afcd 100644 --- a/tests/specs/graph/fast_check/var_function_leavable.txt +++ b/tests/specs/graph/fast_check/var_function_leavable.txt @@ -115,12 +115,8 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } } import type { Foo, Bar } from "./b.ts"; - export const foo = function(foo: Foo): string { - return {} as never; - }; - export const bar = function(): Bar { - return {} as never; - }; + export declare const foo: (foo: Foo) => string; + export declare const bar: () => Bar; --- DTS --- import type { Foo, Bar } from "./b.ts"; export declare const foo: (foo: Foo) => string; diff --git a/tests/specs/graph/fast_check/vars.txt b/tests/specs/graph/fast_check/vars.txt index 967a85f6f..bc06f5db2 100644 --- a/tests/specs/graph/fast_check/vars.txt +++ b/tests/specs/graph/fast_check/vars.txt @@ -108,109 +108,16 @@ import 'jsr:@scope/a' } Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: - {} - const public1: Public1 = {} as never; - export { public1 }; - class Public1 { - } - const ERROR_STATUS_MAP = { - "BadRequest": 400, - "Unauthorized": 401, - "PaymentRequired": 402, - "Forbidden": 403 - } as const; - export type ErrorStatusKeys = keyof typeof ERROR_STATUS_MAP; - export const asIs1 = [ - 1, - 2, - 3 - ]; - export const asIs2 = { - a: [ - 1, - 2, - 3 - ], - b: true, - c: 10, - d: 10n, - e: 1 * 2, - [1]: 4, - f: 1 ? true : false, - g: 1++, - h: [ - ...[ - 1, - 2 - ], - ...[ - "test" - ] - ] - }; - export const func1 = function(): string { - return {} as never; - }; - export const func2 = (): string =>({} as never); - export const func3 = (): string =>({} as never); - export const func4 = ()=>"test" as const; - export const func5 = (): Promise =>({} as never); - export const func6 = (): Promise<1> =>({} as never); - export const func7 = (): Promise<1> =>({} as never); - export const func8 = async ()=>"test" as const; - export const inferred1: 1 = {} as never; - export const inferred2: string = {} as never; - export const inferred3: true = {} as never; - export const inferred4: RegExp = {} as never; - export const inferred5: boolean = {} as never; - export const inferred6: boolean = {} as never; - export const inferred7: 1 | 2 | 3 & 4 = {} as never; - export const inferred8: ["test", 1] = {} as never; - export const inferred9: [...string] = {} as never; - export const inferred10: (1 | 2n)[] = {} as never; - export const inferred11: (keyof string)[] = {} as never; - export const inferred12: number = {} as never; - --- DTS --- - declare const public1: Public1; - export { public1 }; - declare class Public1 { - } - declare const ERROR_STATUS_MAP: { - readonly "BadRequest": 400; - readonly "Unauthorized": 401; - readonly "PaymentRequired": 402; - readonly "Forbidden": 403; - }; - export type ErrorStatusKeys = keyof typeof ERROR_STATUS_MAP; - export declare const asIs1: readonly [number, number, number]; - export declare const asIs2: { - readonly a: readonly [number, number, number]; - readonly b: boolean; - readonly c: number; - readonly d: bigint; - readonly e; - readonly [1]: number; - readonly f; - readonly g; - readonly h: readonly [readonly [number, number], readonly [string]]; - }; - export declare const func1: () => string; - export declare const func2: () => string; - export declare const func3: () => string; - export declare const func4: () => any; - export declare const func5: () => Promise; - export declare const func6: () => Promise<1>; - export declare const func7: () => Promise<1>; - export declare const func8: () => any; - export declare const inferred1: 1; - export declare const inferred2: string; - export declare const inferred3: true; - export declare const inferred4: RegExp; - export declare const inferred5: boolean; - export declare const inferred6: boolean; - export declare const inferred7: 1 | 2 | 3 & 4; - export declare const inferred8: ["test", 1]; - export declare const inferred9: [...string]; - export declare const inferred10: (1 | 2n)[]; - export declare const inferred11: (keyof string)[]; - export declare const inferred12: number; + error[unknown-var-type]: unknown type for variable in the public API + --> https://jsr.io/@scope/a/1.0.0/mod.ts:19:14 + | + 19 | export const asIs1 = [1, 2, 3]; + | ^^^^^ this variable's type could not be inferred because its initializer + | --------- contains this array literal, which can not be inferred unless marked 'as const' + | + = hint: add an explicit type annotation to the variable declaration + + info: all variables in the public API must have a known type + info: array literals without 'as const' can not be inferred because the type of the array can not be narrowed without a type checker + docs: https://jsr.io/go/slow-type-unknown-var-type + diff --git a/tests/specs/graph/jsr/dynamic_import.txt b/tests/specs/graph/jsr/dynamic_import.txt index 0648cbd9a..8618bdbc9 100644 --- a/tests/specs/graph/jsr/dynamic_import.txt +++ b/tests/specs/graph/jsr/dynamic_import.txt @@ -126,7 +126,7 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: } } } - const test: typeof import('./a.ts') = {} as never; + declare const test: typeof import('./a.ts'); export { test }; --- DTS --- declare const test: typeof import('./a.ts'); diff --git a/tests/specs_test.rs b/tests/specs_test.rs index 633ad00fd..020c022fa 100644 --- a/tests/specs_test.rs +++ b/tests/specs_test.rs @@ -1,10 +1,9 @@ // Copyright 2018-2024 the Deno authors. MIT license. use std::collections::BTreeMap; -use std::panic::AssertUnwindSafe; - use std::collections::HashMap; use std::fmt::Write; +use std::panic::AssertUnwindSafe; use deno_ast::diagnostics::Diagnostic; use deno_ast::emit; From d7e5cbc2b3ec00fcd0e7ed4163fd58b13fe85d0d Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Fri, 17 May 2024 21:02:23 +0200 Subject: [PATCH 3/4] failing test --- .../specs/graph/fast_check/test_test_test.txt | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/tests/specs/graph/fast_check/test_test_test.txt b/tests/specs/graph/fast_check/test_test_test.txt index b8347af7e..92c4257dc 100644 --- a/tests/specs/graph/fast_check/test_test_test.txt +++ b/tests/specs/graph/fast_check/test_test_test.txt @@ -11,12 +11,12 @@ ] # mod.ts -export function a() { - var x = ""; - return {} as typeof x; -}; +// export function a() { +// var x = ""; +// return {} as typeof x; +// }; -export function a(b: string): typeof b { +export const a = (b: string, c = {} as typeof b) => { }; # output @@ -27,7 +27,7 @@ export function a(b: string): typeof b { "modules": [ { "kind": "esm", - "size": 65, + "size": 144, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -36,19 +36,18 @@ export function a(b: string): typeof b { } Fast check file:///mod.ts: - error[unknown-return-type]: unknown return type for function in the public API - --> /mod.ts:1:17 - | - 1 | export function a () { - | - this function's return type could not be inferred + error[unknown-var-type]: unknown type for variable in the public API + --> /mod.ts:6:14 | - 3 | return {} as typeof x; - | ------ because the value returned here - | - contains this reference to a local variable or type + 6 | export const a = (b: string, c = {} as typeof b): typeof b => { + | ^ this variable's type could not be inferred because its initializer + | - contains this arrow expression + | - which has this parameter, which is missing an explicit type annotation + | - contains this reference to a local variable or type | - = hint: add an explicit return type to the function + = hint: add an explicit type annotation to the arrow expression parameter in the variable declaration initializer - info: all functions in the public API must have an known return type - info: local variables or types can not be referenced because they are not visible at the top level of the module - docs: https://jsr.io/go/slow-type-unknown-return-type + info: all variables in the public API must have a known type + info: local variables or types can not be referenced in the public API because they are not visible at the top level of the module + docs: https://jsr.io/go/slow-type-unknown-var-type From 9cb0a4f8323f6b4774545c228509cde3cce9bbd9 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 20 May 2024 13:57:12 +0200 Subject: [PATCH 4/4] wip --- src/fast_check/mod.rs | 3 +- src/fast_check/transform.rs | 13 ++++++- src/fast_check/type_infer.rs | 34 +++++++++++++++++-- src/swc_helpers.rs | 32 +++++++++++++++++ src/symbols/analyzer.rs | 2 +- src/symbols/dep_analyzer.rs | 4 +-- src/symbols/mod.rs | 1 - src/symbols/swc_helpers.rs | 34 ------------------- .../specs/graph/fast_check/test_test_test.txt | 32 +++++++---------- 9 files changed, 93 insertions(+), 62 deletions(-) delete mode 100644 src/symbols/swc_helpers.rs diff --git a/src/fast_check/mod.rs b/src/fast_check/mod.rs index 975d2a9ed..50d9ea5e9 100644 --- a/src/fast_check/mod.rs +++ b/src/fast_check/mod.rs @@ -709,7 +709,8 @@ fn transform_package( .module_from_specifier(&specifier) .unwrap_or_else(|| panic!("module not found: {}", specifier)); if let Some(module_info) = module_info.esm() { - transform::transform(graph, module_info, &ranges, options).map(Some) + transform::transform(graph, module_info, root_symbol, &ranges, options) + .map(Some) } else { Ok(None) // nothing to transform } diff --git a/src/fast_check/transform.rs b/src/fast_check/transform.rs index 918310a37..b2583b20a 100644 --- a/src/fast_check/transform.rs +++ b/src/fast_check/transform.rs @@ -28,6 +28,7 @@ use indexmap::IndexMap; use crate::swc_helpers::FunctionKind; use crate::symbols::EsModuleInfo; use crate::symbols::ExpandoPropertyRef; +use crate::symbols::RootSymbol; use crate::symbols::Symbol; use crate::ModuleGraph; use crate::ModuleInfo; @@ -111,12 +112,14 @@ pub struct TransformOptions<'a> { pub fn transform( graph: &ModuleGraph, es_module_info: &EsModuleInfo, + root_symbol: &RootSymbol, public_ranges: &ModulePublicRanges, options: &TransformOptions, ) -> Result> { let mut transformer = FastCheckTransformer::new( graph, es_module_info, + root_symbol, public_ranges, options.should_error_on_first_diagnostic, ); @@ -210,6 +213,7 @@ struct FastCheckTransformer<'a> { graph: &'a ModuleGraph, specifier: &'a ModuleSpecifier, es_module_info: &'a EsModuleInfo, + root_symbol: &'a RootSymbol<'a>, public_ranges: &'a ModulePublicRanges, parsed_source: &'a ParsedSource, should_error_on_first_diagnostic: bool, @@ -221,6 +225,7 @@ impl<'a> FastCheckTransformer<'a> { pub fn new( graph: &'a ModuleGraph, es_module_info: &'a EsModuleInfo, + root_symbol: &'a RootSymbol<'a>, public_ranges: &'a ModulePublicRanges, should_error_on_first_diagnostic: bool, ) -> Self { @@ -228,6 +233,7 @@ impl<'a> FastCheckTransformer<'a> { graph, specifier: es_module_info.specifier(), es_module_info, + root_symbol, public_ranges, parsed_source: es_module_info.source(), should_error_on_first_diagnostic, @@ -1688,7 +1694,12 @@ impl<'a> FastCheckTransformer<'a> { } fn infer(&self) -> TypeInferrer<'_> { - TypeInferrer::new(self.specifier, self.parsed_source, self.es_module_info) + TypeInferrer::new( + self.specifier, + self.parsed_source, + self.es_module_info, + self.root_symbol, + ) } fn maybe_transform_expr_if_leavable( diff --git a/src/fast_check/type_infer.rs b/src/fast_check/type_infer.rs index 2a19ed475..1a3922085 100644 --- a/src/fast_check/type_infer.rs +++ b/src/fast_check/type_infer.rs @@ -17,6 +17,7 @@ use deno_ast::swc::ast::Prop; use deno_ast::swc::ast::PropOrSpread; use deno_ast::swc::ast::Str; use deno_ast::swc::ast::Tpl; +use deno_ast::swc::ast::TsEntityName; use deno_ast::swc::ast::TsFnOrConstructorType; use deno_ast::swc::ast::TsFnParam; use deno_ast::swc::ast::TsFnType; @@ -45,8 +46,14 @@ use deno_ast::SourceRangedForSpanned; use crate::swc_helpers::analyze_return_stmts_in_function_body; use crate::swc_helpers::FunctionKind; +use crate::swc_helpers::ts_entity_name_to_parts; use crate::swc_helpers::ReturnStatementAnalysis; +use crate::symbols::DefinitionPathNode; use crate::symbols::EsModuleInfo; +use crate::symbols::ModuleInfoRef; +use crate::symbols::ResolvedSymbolDepEntry; +use crate::symbols::RootSymbol; +use crate::symbols::SymbolNodeDep; use crate::FastCheckDiagnosticRange; use super::swc_helpers::is_call_expr_symbol_create; @@ -494,6 +501,7 @@ pub struct TypeInferrer<'a> { specifier: &'a ModuleSpecifier, parsed_source: &'a ParsedSource, module_info: &'a EsModuleInfo, + root_symbol: &'a RootSymbol<'a>, } impl<'a> TypeInferrer<'a> { @@ -501,11 +509,13 @@ impl<'a> TypeInferrer<'a> { specifier: &'a ModuleSpecifier, parsed_source: &'a ParsedSource, module_info: &'a EsModuleInfo, + root_symbol: &'a RootSymbol<'a>, ) -> Self { Self { specifier, parsed_source, module_info, + root_symbol, } } @@ -1125,20 +1135,38 @@ impl<'a> TypeInferrer<'a> { ty: &TsType, ) -> Result<(), ExprInferFailCause> { struct Visitor<'a> { + root_symbol: &'a RootSymbol<'a>, es_module_info: &'a EsModuleInfo, invalid_range: Option, } impl Visit for Visitor<'_> { - fn visit_ident(&mut self, n: &Ident) { - if self.es_module_info.symbol_from_swc(&n.to_id()).is_none() { - self.invalid_range = Some(n.range()); + fn visit_ts_entity_name(&mut self, n: &TsEntityName) { + let (id, parts) = ts_entity_name_to_parts(n); + + let dep = SymbolNodeDep::QualifiedId(id, parts); + + let entries = self + .root_symbol + .resolve_symbol_dep(ModuleInfoRef::Esm(self.es_module_info), &dep); + + for entry in entries { + match entry { + ResolvedSymbolDepEntry::Path(DefinitionPathNode::Resolved(_)) + | ResolvedSymbolDepEntry::ImportType(_) => { + // valid + } + ResolvedSymbolDepEntry::Path(DefinitionPathNode::Unresolved(_)) => { + self.invalid_range = Some(n.range()); + } + } } } } let mut visitor = Visitor { es_module_info: self.module_info, + root_symbol: self.root_symbol, invalid_range: None, }; ty.visit_with(&mut visitor); diff --git a/src/swc_helpers.rs b/src/swc_helpers.rs index 9e1757076..2564b0772 100644 --- a/src/swc_helpers.rs +++ b/src/swc_helpers.rs @@ -3,8 +3,11 @@ use std::ops::ControlFlow; use deno_ast::swc::ast::BlockStmt; +use deno_ast::swc::ast::Id; use deno_ast::swc::ast::ReturnStmt; use deno_ast::swc::ast::Stmt; +use deno_ast::swc::ast::TsEntityName; +use deno_ast::swc::ast::TsQualifiedName; pub enum FunctionKind { /// function declarations, class method declarations (both class decl and class expr) @@ -111,3 +114,32 @@ fn analyze_return_stmts_from_stmt( | Stmt::Empty(_) => ControlFlow::Continue(()), } } + +pub fn ts_entity_name_to_parts( + entity_name: &TsEntityName, +) -> (Id, Vec) { + match entity_name { + TsEntityName::TsQualifiedName(qualified_name) => { + ts_qualified_name_parts(qualified_name) + } + TsEntityName::Ident(ident) => (ident.to_id(), Vec::new()), + } +} + +pub fn ts_qualified_name_parts( + mut qualified_name: &TsQualifiedName, +) -> (Id, Vec) { + let mut parts = Vec::new(); + loop { + parts.push(qualified_name.right.sym.to_string()); + match &qualified_name.left { + TsEntityName::TsQualifiedName(n) => { + qualified_name = n; + } + TsEntityName::Ident(n) => { + parts.reverse(); + return (n.to_id(), parts); + } + } + } +} diff --git a/src/symbols/analyzer.rs b/src/symbols/analyzer.rs index f327ad1bd..3ce0ce19a 100644 --- a/src/symbols/analyzer.rs +++ b/src/symbols/analyzer.rs @@ -21,6 +21,7 @@ use indexmap::IndexMap; use indexmap::IndexSet; use crate::swc_helpers::analyze_return_stmts_in_function_body; +use crate::swc_helpers::ts_entity_name_to_parts; use crate::swc_helpers::FunctionKind; use crate::swc_helpers::ReturnStatementAnalysis; use crate::JsModule; @@ -39,7 +40,6 @@ use super::cross_module::DefinitionOrUnresolved; use super::cross_module::DefinitionPathNode; use super::cross_module::ModuleExports; use super::dep_analyzer::ResolveDepsMode; -use super::swc_helpers::ts_entity_name_to_parts; use super::ResolvedSymbolDepEntry; use super::SymbolNodeDep; diff --git a/src/symbols/dep_analyzer.rs b/src/symbols/dep_analyzer.rs index b86789819..b1db0492a 100644 --- a/src/symbols/dep_analyzer.rs +++ b/src/symbols/dep_analyzer.rs @@ -43,9 +43,9 @@ use deno_ast::swc::visit::Visit; use deno_ast::swc::visit::VisitWith; use crate::swc_helpers::analyze_return_stmts_in_function_body; +use crate::swc_helpers::ts_entity_name_to_parts; +use crate::swc_helpers::ts_qualified_name_parts; -use super::swc_helpers::ts_entity_name_to_parts; -use super::swc_helpers::ts_qualified_name_parts; use super::ExportDeclRef; use super::SymbolNodeRef; diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs index 38361f7ef..7f6749697 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -35,4 +35,3 @@ mod analyzer; mod collections; mod cross_module; mod dep_analyzer; -mod swc_helpers; diff --git a/src/symbols/swc_helpers.rs b/src/symbols/swc_helpers.rs deleted file mode 100644 index 10d1685f7..000000000 --- a/src/symbols/swc_helpers.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018-2024 the Deno authors. MIT license. - -use deno_ast::swc::ast::Id; -use deno_ast::swc::ast::TsEntityName; -use deno_ast::swc::ast::TsQualifiedName; - -pub fn ts_entity_name_to_parts( - entity_name: &TsEntityName, -) -> (Id, Vec) { - match entity_name { - TsEntityName::TsQualifiedName(qualified_name) => { - ts_qualified_name_parts(qualified_name) - } - TsEntityName::Ident(ident) => (ident.to_id(), Vec::new()), - } -} - -pub fn ts_qualified_name_parts( - mut qualified_name: &TsQualifiedName, -) -> (Id, Vec) { - let mut parts = Vec::new(); - loop { - parts.push(qualified_name.right.sym.to_string()); - match &qualified_name.left { - TsEntityName::TsQualifiedName(n) => { - qualified_name = n; - } - TsEntityName::Ident(n) => { - parts.reverse(); - return (n.to_id(), parts); - } - } - } -} diff --git a/tests/specs/graph/fast_check/test_test_test.txt b/tests/specs/graph/fast_check/test_test_test.txt index 92c4257dc..ba6d4d6e2 100644 --- a/tests/specs/graph/fast_check/test_test_test.txt +++ b/tests/specs/graph/fast_check/test_test_test.txt @@ -16,9 +16,6 @@ // return {} as typeof x; // }; -export const a = (b: string, c = {} as typeof b) => { -}; - # output { "roots": [ @@ -27,7 +24,7 @@ export const a = (b: string, c = {} as typeof b) => { "modules": [ { "kind": "esm", - "size": 144, + "size": 179, "mediaType": "TypeScript", "specifier": "file:///mod.ts" } @@ -36,18 +33,15 @@ export const a = (b: string, c = {} as typeof b) => { } Fast check file:///mod.ts: - error[unknown-var-type]: unknown type for variable in the public API - --> /mod.ts:6:14 - | - 6 | export const a = (b: string, c = {} as typeof b): typeof b => { - | ^ this variable's type could not be inferred because its initializer - | - contains this arrow expression - | - which has this parameter, which is missing an explicit type annotation - | - contains this reference to a local variable or type - | - = hint: add an explicit type annotation to the arrow expression parameter in the variable declaration initializer - - info: all variables in the public API must have a known type - info: local variables or types can not be referenced in the public API because they are not visible at the top level of the module - docs: https://jsr.io/go/slow-type-unknown-var-type - + {} + declare const Y: X; + interface X { + y: string; + } + export declare const a: (b: string, c?: typeof Y.z) => void; + --- DTS --- + declare const Y: X; + interface X { + y: string; + } + export declare const a: (b: string, c?: typeof Y.z) => void;