1use crate::query_boundaries::constructor_checker::{
9 AbstractConstructorAnchor, ConstructorAccessKind, ConstructorReturnMergeKind, InstanceTypeKind,
10 classify_for_constructor_access, classify_for_constructor_return_merge,
11 classify_for_instance_type, construct_signatures_for_type, has_construct_signatures,
12 resolve_abstract_constructor_anchor,
13};
14use crate::state::{CheckerState, MAX_TREE_WALK_ITERATIONS, MemberAccessLevel};
15use rustc_hash::FxHashSet;
16use tsz_binder::{SymbolId, symbol_flags};
17use tsz_common::interner::Atom;
18use tsz_parser::parser::NodeIndex;
19use tsz_scanner::SyntaxKind;
20use tsz_solver::TypeId;
21
22impl<'a> CheckerState<'a> {
27 pub fn is_abstract_ctor(&self, type_id: TypeId) -> bool {
35 self.ctx.abstract_constructor_types.contains(&type_id)
36 }
37
38 pub fn is_private_ctor(&self, type_id: TypeId) -> bool {
42 self.ctx.private_constructor_types.contains(&type_id)
43 }
44
45 pub fn is_protected_ctor(&self, type_id: TypeId) -> bool {
49 self.ctx.protected_constructor_types.contains(&type_id)
50 }
51
52 pub fn is_public_ctor(&self, type_id: TypeId) -> bool {
56 !self.is_private_ctor(type_id) && !self.is_protected_ctor(type_id)
57 }
58
59 pub fn has_construct_sig(&self, type_id: TypeId) -> bool {
67 has_construct_signatures(self.ctx.types, type_id)
68 }
69
70 pub fn construct_signature_count(&self, type_id: TypeId) -> usize {
74 construct_signatures_for_type(self.ctx.types, type_id).map_or(0, |sigs| sigs.len())
75 }
76
77 pub fn can_instantiate(&self, constructor_type: TypeId) -> bool {
85 !self.is_abstract_ctor(constructor_type)
86 }
87
88 pub fn can_use_new(&self, type_id: TypeId) -> bool {
93 self.has_construct_sig(type_id) && self.can_instantiate(type_id)
94 }
95
96 pub fn is_class_constructor_type(&self, type_id: TypeId) -> bool {
101 self.has_construct_sig(type_id)
103 && !crate::query_boundaries::class_type::has_call_signatures(self.ctx.types, type_id)
104 }
105
106 pub fn ctor_access_compatible(&self, source: TypeId, target: TypeId) -> bool {
113 if !self.is_private_ctor(source) && !self.is_protected_ctor(source) {
115 return true;
116 }
117
118 if self.is_private_ctor(source) {
120 if self.is_private_ctor(target) {
121 source == target
122 } else {
123 false
124 }
125 } else {
126 !self.is_private_ctor(target)
128 }
129 }
130
131 pub fn is_newable(&self, type_id: TypeId) -> bool {
135 self.has_construct_sig(type_id)
136 }
137
138 pub(crate) fn refine_mixin_call_return_type(
148 &mut self,
149 callee_idx: NodeIndex,
150 arg_types: &[TypeId],
151 return_type: TypeId,
152 ) -> TypeId {
153 if return_type == TypeId::ANY || return_type == TypeId::ERROR {
154 return return_type;
155 }
156
157 let Some(func_decl_idx) = self.function_decl_from_callee(callee_idx) else {
158 return return_type;
159 };
160 let Some(func_node) = self.ctx.arena.get(func_decl_idx) else {
161 return return_type;
162 };
163 let Some(func) = self.ctx.arena.get_function(func_node) else {
164 return return_type;
165 };
166 let Some(class_expr_idx) = self.returned_class_expression(func.body) else {
167 return return_type;
168 };
169 let Some(base_param_index) = self.mixin_base_param_index(class_expr_idx, func) else {
170 return return_type;
171 };
172 let Some(&base_arg_type) = arg_types.get(base_param_index) else {
173 return return_type;
174 };
175 if matches!(base_arg_type, TypeId::ANY | TypeId::ERROR) {
176 return return_type;
177 }
178
179 let factory = self.ctx.types.factory();
180 let mut refined_return = factory.intersection(vec![return_type, base_arg_type]);
181
182 if let Some(base_instance_type) = self.instance_type_from_constructor_type(base_arg_type) {
183 refined_return = self
184 .merge_base_instance_into_constructor_return(refined_return, base_instance_type);
185 }
186
187 let base_props = self.static_properties_from_type(base_arg_type);
188 if !base_props.is_empty() {
189 refined_return = self.merge_base_constructor_properties_into_constructor_return(
190 refined_return,
191 &base_props,
192 );
193 }
194
195 refined_return
196 }
197
198 fn mixin_base_param_index(
199 &self,
200 class_expr_idx: NodeIndex,
201 func: &tsz_parser::parser::node::FunctionData,
202 ) -> Option<usize> {
203 let class_data = self.ctx.arena.get_class_at(class_expr_idx)?;
204 let heritage_clauses = class_data.heritage_clauses.as_ref()?;
205
206 let mut base_name = None;
207 for &clause_idx in &heritage_clauses.nodes {
208 let heritage = self.ctx.arena.get_heritage_clause_at(clause_idx)?;
209 if heritage.token != SyntaxKind::ExtendsKeyword as u16 {
210 continue;
211 }
212 let &type_idx = heritage.types.nodes.first()?;
213 let type_node = self.ctx.arena.get(type_idx)?;
214 let expr_idx =
215 if let Some(expr_type_args) = self.ctx.arena.get_expr_type_args(type_node) {
216 expr_type_args.expression
217 } else {
218 type_idx
219 };
220 let expr_node = self.ctx.arena.get(expr_idx)?;
221 if expr_node.kind != SyntaxKind::Identifier as u16 {
222 return None;
223 }
224 let ident = self.ctx.arena.get_identifier(expr_node)?;
225 base_name = Some(ident.escaped_text.clone());
226 break;
227 }
228
229 let base_name = base_name?;
230 let mut arg_index = 0usize;
231 for ¶m_idx in &func.parameters.nodes {
232 let param = self.ctx.arena.get_parameter_at(param_idx)?;
233 let ident = self.ctx.arena.get_identifier_at(param.name)?;
234 if ident.escaped_text == "this" {
235 continue;
236 }
237 if ident.escaped_text == base_name {
238 return Some(arg_index);
239 }
240 arg_index += 1;
241 }
242
243 None
244 }
245
246 pub(crate) fn instance_type_from_constructor_type(
251 &mut self,
252 ctor_type: TypeId,
253 ) -> Option<TypeId> {
254 let mut visited = FxHashSet::default();
255 self.instance_type_from_constructor_type_inner(ctor_type, &mut visited)
256 }
257
258 fn instance_type_from_constructor_type_inner(
259 &mut self,
260 ctor_type: TypeId,
261 visited: &mut FxHashSet<TypeId>,
262 ) -> Option<TypeId> {
263 if ctor_type == TypeId::NULL {
264 return Some(TypeId::NULL);
265 }
266 if ctor_type == TypeId::ERROR {
267 return None;
268 }
269 if ctor_type == TypeId::ANY {
270 return Some(TypeId::ANY);
271 }
272
273 let mut current = ctor_type;
274 let mut iterations = 0;
275 loop {
276 iterations += 1;
277 if iterations > MAX_TREE_WALK_ITERATIONS {
278 return None;
279 }
280 if !visited.insert(current) {
281 return None;
282 }
283 current = self.evaluate_application_type(current);
284 let resolved = self.resolve_lazy_type(current);
286 if resolved != current {
287 current = resolved;
288 }
289 match classify_for_instance_type(self.ctx.types, current) {
290 InstanceTypeKind::Callable(shape_id) => {
291 let instance_type = tsz_solver::type_queries::get_construct_return_type_union(
292 self.ctx.types,
293 shape_id,
294 )?;
295 return Some(self.resolve_type_for_property_access(instance_type));
296 }
297 InstanceTypeKind::Function(shape_id) => {
298 let shape = self.ctx.types.function_shape(shape_id);
299 if !shape.is_constructor {
300 return None;
301 }
302 return Some(self.resolve_type_for_property_access(shape.return_type));
303 }
304 InstanceTypeKind::Intersection(members) => {
305 let instance_types: Vec<TypeId> = members
306 .into_iter()
307 .filter_map(|m| self.instance_type_from_constructor_type_inner(m, visited))
308 .collect();
309 if instance_types.is_empty() {
310 return None;
311 }
312 let instance_type =
313 tsz_solver::utils::intersection_or_single(self.ctx.types, instance_types);
314 return Some(self.resolve_type_for_property_access(instance_type));
315 }
316 InstanceTypeKind::Union(members) => {
317 let instance_types: Vec<TypeId> = members
318 .into_iter()
319 .filter_map(|m| self.instance_type_from_constructor_type_inner(m, visited))
320 .collect();
321 if instance_types.is_empty() {
322 return None;
323 }
324 let instance_type =
325 tsz_solver::utils::union_or_single(self.ctx.types, instance_types);
326 return Some(self.resolve_type_for_property_access(instance_type));
327 }
328 InstanceTypeKind::Readonly(inner) => {
329 return self.instance_type_from_constructor_type_inner(inner, visited);
330 }
331 InstanceTypeKind::TypeParameter { constraint } => {
332 let constraint = constraint?;
333 current = constraint;
334 }
335 InstanceTypeKind::SymbolRef(sym_ref) => {
336 use tsz_binder::SymbolId;
339 let sym_id = SymbolId(sym_ref.0);
340 if let Some(instance_type) = self.class_instance_type_from_symbol(sym_id) {
341 return Some(self.resolve_type_for_property_access(instance_type));
342 }
343 let var_type = self.get_type_of_symbol(sym_id);
346 if var_type != TypeId::ERROR && var_type != current {
347 current = var_type;
348 } else {
349 return None;
350 }
351 }
352 InstanceTypeKind::NeedsEvaluation => {
353 let evaluated = self.evaluate_type_with_env(current);
354 if evaluated == current {
355 return None;
356 }
357 current = evaluated;
358 }
359 InstanceTypeKind::NotConstructor => return None,
360 }
361 }
362 }
363
364 fn merge_base_instance_into_constructor_return(
369 &mut self,
370 ctor_type: TypeId,
371 base_instance_type: TypeId,
372 ) -> TypeId {
373 let ctor_type = {
375 let resolved = self.resolve_lazy_type(ctor_type);
376 if resolved != ctor_type {
377 resolved
378 } else {
379 ctor_type
380 }
381 };
382 match classify_for_constructor_return_merge(self.ctx.types, ctor_type) {
383 ConstructorReturnMergeKind::Callable(shape_id) => {
384 let shape = self.ctx.types.callable_shape(shape_id);
385 if shape.construct_signatures.is_empty() {
386 return ctor_type;
387 }
388 let mut new_shape = (*shape).clone();
389 new_shape.construct_signatures = shape
390 .construct_signatures
391 .iter()
392 .map(|sig| {
393 let mut updated = sig.clone();
394 updated.return_type = self
395 .ctx
396 .types
397 .factory()
398 .intersection(vec![updated.return_type, base_instance_type]);
399 updated
400 })
401 .collect();
402 self.ctx.types.factory().callable(new_shape)
403 }
404 ConstructorReturnMergeKind::Function(shape_id) => {
405 let shape = self.ctx.types.function_shape(shape_id);
406 if !shape.is_constructor {
407 return ctor_type;
408 }
409 let mut new_shape = (*shape).clone();
410 new_shape.return_type = self
411 .ctx
412 .types
413 .factory()
414 .intersection(vec![new_shape.return_type, base_instance_type]);
415 self.ctx.types.factory().function(new_shape)
416 }
417 ConstructorReturnMergeKind::Intersection(members) => {
418 let mut updated_members = Vec::with_capacity(members.len());
419 let mut changed = false;
420 for member in members {
421 let updated = self
422 .merge_base_instance_into_constructor_return(member, base_instance_type);
423 if updated != member {
424 changed = true;
425 }
426 updated_members.push(updated);
427 }
428 if changed {
429 self.ctx.types.factory().intersection(updated_members)
430 } else {
431 ctor_type
432 }
433 }
434 ConstructorReturnMergeKind::Other => ctor_type,
435 }
436 }
437
438 fn merge_base_constructor_properties_into_constructor_return(
439 &mut self,
440 ctor_type: TypeId,
441 base_props: &rustc_hash::FxHashMap<Atom, tsz_solver::PropertyInfo>,
442 ) -> TypeId {
443 use rustc_hash::FxHashMap;
444 if base_props.is_empty() {
445 return ctor_type;
446 }
447
448 let ctor_type = {
450 let resolved = self.resolve_lazy_type(ctor_type);
451 if resolved != ctor_type {
452 resolved
453 } else {
454 ctor_type
455 }
456 };
457 match classify_for_constructor_return_merge(self.ctx.types, ctor_type) {
458 ConstructorReturnMergeKind::Callable(shape_id) => {
459 let shape = self.ctx.types.callable_shape(shape_id);
460 let mut prop_map: FxHashMap<Atom, tsz_solver::PropertyInfo> = shape
461 .properties
462 .iter()
463 .map(|prop| (prop.name, prop.clone()))
464 .collect();
465 for (name, prop) in base_props {
466 prop_map.entry(*name).or_insert_with(|| prop.clone());
467 }
468 let mut new_shape = (*shape).clone();
469 new_shape.properties = prop_map.into_values().collect();
470 self.ctx.types.factory().callable(new_shape)
471 }
472 ConstructorReturnMergeKind::Intersection(members) => {
473 let mut updated_members = Vec::with_capacity(members.len());
474 let mut changed = false;
475 for member in members {
476 let updated = self.merge_base_constructor_properties_into_constructor_return(
477 member, base_props,
478 );
479 if updated != member {
480 changed = true;
481 }
482 updated_members.push(updated);
483 }
484 if changed {
485 self.ctx.types.factory().intersection(updated_members)
486 } else {
487 ctor_type
488 }
489 }
490 ConstructorReturnMergeKind::Function(_) | ConstructorReturnMergeKind::Other => {
491 ctor_type
492 }
493 }
494 }
495
496 pub(crate) fn abstract_constructor_assignability_override(
501 &self,
502 source: TypeId,
503 target: TypeId,
504 _env: Option<&tsz_solver::TypeEnvironment>,
505 ) -> Option<bool> {
506 let is_abstract_type = |type_id: TypeId| -> bool {
509 if self.is_abstract_ctor(type_id) {
511 return true;
512 }
513
514 match resolve_abstract_constructor_anchor(self.ctx.types, type_id) {
516 AbstractConstructorAnchor::TypeQuery(sym_ref) => {
517 if let Some(symbol) =
518 self.ctx.binder.get_symbol(tsz_binder::SymbolId(sym_ref.0))
519 {
520 symbol.flags & symbol_flags::ABSTRACT != 0
521 } else {
522 false
523 }
524 }
525 AbstractConstructorAnchor::CallableType(callable_type) => {
526 self.is_abstract_ctor(callable_type)
527 }
528 _ => false,
529 }
530 };
531
532 let source_is_abstract = is_abstract_type(source);
533 let target_is_abstract = is_abstract_type(target);
534
535 if !source_is_abstract && target_is_abstract {
537 return None;
539 }
540
541 if source_is_abstract && target_is_abstract {
543 return None;
544 }
545
546 if source_is_abstract && !target_is_abstract {
548 let target_is_constructor = self.has_construct_sig(target);
549 if target_is_constructor {
550 return Some(false);
551 }
552 }
553
554 None
555 }
556
557 fn constructor_access_level(
562 &self,
563 type_id: TypeId,
564 env: Option<&tsz_solver::TypeEnvironment>,
565 visited: &mut FxHashSet<TypeId>,
566 ) -> Option<MemberAccessLevel> {
567 if !visited.insert(type_id) {
568 return None;
569 }
570
571 if self.is_private_ctor(type_id) {
572 return Some(MemberAccessLevel::Private);
573 }
574 if self.is_protected_ctor(type_id) {
575 return Some(MemberAccessLevel::Protected);
576 }
577
578 match classify_for_constructor_access(self.ctx.types, type_id) {
579 ConstructorAccessKind::SymbolRef(symbol) => self
580 .resolve_type_env_symbol(symbol, env)
581 .and_then(|resolved| {
582 if resolved != type_id {
583 self.constructor_access_level(resolved, env, visited)
584 } else {
585 None
586 }
587 }),
588 ConstructorAccessKind::Application(app_id) => {
589 let app = self.ctx.types.type_application(app_id);
590 if app.base != type_id {
591 self.constructor_access_level(app.base, env, visited)
592 } else {
593 None
594 }
595 }
596 ConstructorAccessKind::Other => None,
597 }
598 }
599
600 fn constructor_access_level_for_type(
601 &self,
602 type_id: TypeId,
603 env: Option<&tsz_solver::TypeEnvironment>,
604 ) -> Option<MemberAccessLevel> {
605 let mut visited = FxHashSet::default();
606 self.constructor_access_level(type_id, env, &mut visited)
607 }
608
609 pub(crate) fn constructor_accessibility_mismatch(
610 &self,
611 source: TypeId,
612 target: TypeId,
613 env: Option<&tsz_solver::TypeEnvironment>,
614 ) -> Option<(Option<MemberAccessLevel>, Option<MemberAccessLevel>)> {
615 let source_level = self.constructor_access_level_for_type(source, env);
616 let target_level = self.constructor_access_level_for_type(target, env);
617
618 if source_level.is_none() && target_level.is_none() {
619 return None;
620 }
621
622 let source_rank = Self::constructor_access_rank(source_level);
623 let target_rank = Self::constructor_access_rank(target_level);
624 if source_rank > target_rank {
625 return Some((source_level, target_level));
626 }
627 None
628 }
629
630 pub(crate) fn constructor_accessibility_override(
631 &self,
632 source: TypeId,
633 target: TypeId,
634 env: Option<&tsz_solver::TypeEnvironment>,
635 ) -> Option<bool> {
636 if self
637 .constructor_accessibility_mismatch(source, target, env)
638 .is_some()
639 {
640 return Some(false);
641 }
642 None
643 }
644
645 pub(crate) fn constructor_accessibility_mismatch_for_assignment(
646 &self,
647 left_idx: NodeIndex,
648 right_idx: NodeIndex,
649 ) -> Option<(Option<MemberAccessLevel>, Option<MemberAccessLevel>)> {
650 let source_sym = self.class_symbol_from_expression(right_idx)?;
651 let target_sym = self.assignment_target_class_symbol(left_idx)?;
652 let source_level = self.class_constructor_access_level(source_sym);
653 let target_level = self.class_constructor_access_level(target_sym);
654 if source_level.is_none() && target_level.is_none() {
655 return None;
656 }
657 if Self::constructor_access_rank(source_level) > Self::constructor_access_rank(target_level)
658 {
659 return Some((source_level, target_level));
660 }
661 None
662 }
663
664 pub(crate) fn constructor_accessibility_mismatch_for_var_decl(
665 &self,
666 var_decl: &tsz_parser::parser::node::VariableDeclarationData,
667 ) -> Option<(Option<MemberAccessLevel>, Option<MemberAccessLevel>)> {
668 if var_decl.initializer.is_none() {
669 return None;
670 }
671 let source_sym = self.class_symbol_from_expression(var_decl.initializer)?;
672 let target_sym = self.class_symbol_from_type_annotation(var_decl.type_annotation)?;
673 let source_level = self.class_constructor_access_level(source_sym);
674 let target_level = self.class_constructor_access_level(target_sym);
675 if source_level.is_none() && target_level.is_none() {
676 return None;
677 }
678 if Self::constructor_access_rank(source_level) > Self::constructor_access_rank(target_level)
679 {
680 return Some((source_level, target_level));
681 }
682 None
683 }
684
685 fn resolve_type_env_symbol(
690 &self,
691 symbol: tsz_solver::SymbolRef,
692 env: Option<&tsz_solver::TypeEnvironment>,
693 ) -> Option<TypeId> {
694 if let Some(env) = env {
695 return env.get(symbol);
696 }
697 let env_ref = self.ctx.type_env.borrow();
698 env_ref.get(symbol)
699 }
700
701 pub(crate) fn check_constructor_accessibility_for_new(
706 &mut self,
707 new_expr_idx: tsz_parser::parser::NodeIndex,
708 constructor_type: TypeId,
709 ) {
710 if constructor_type == TypeId::ANY || constructor_type == TypeId::ERROR {
712 return;
713 }
714
715 let is_private = self.is_private_ctor(constructor_type);
717 let is_protected = self.is_protected_ctor(constructor_type);
718
719 if !is_private && !is_protected {
720 return; }
722
723 let class_sym = match self.class_symbol_from_new_expr(new_expr_idx) {
725 Some(sym) => sym,
726 None => return, };
728
729 let enclosing_class_sym = match self.find_enclosing_class_for_new(new_expr_idx) {
731 Some(sym) => sym,
732 None => {
733 self.emit_constructor_access_error(new_expr_idx, class_sym, is_private);
736 return;
737 }
738 };
739
740 if enclosing_class_sym == class_sym {
742 return;
744 }
745
746 let is_subclass = self
748 .ctx
749 .inheritance_graph
750 .is_derived_from(enclosing_class_sym, class_sym);
751
752 if is_private {
753 if enclosing_class_sym != class_sym {
755 self.emit_constructor_access_error(new_expr_idx, class_sym, true);
756 }
757 } else if is_protected {
758 if !is_subclass {
760 self.emit_constructor_access_error(new_expr_idx, class_sym, false);
761 }
762 }
763 }
764
765 fn class_symbol_from_new_expr(&self, idx: tsz_parser::parser::NodeIndex) -> Option<SymbolId> {
767 use tsz_binder::symbol_flags;
768
769 let call_expr = self.ctx.arena.get_call_expr_at(idx)?;
770
771 let ident = self.ctx.arena.get_identifier_at(call_expr.expression)?;
773
774 let sym_id = self
776 .ctx
777 .binder
778 .get_node_symbol(call_expr.expression)
779 .or_else(|| self.ctx.binder.file_locals.get(&ident.escaped_text))
780 .or_else(|| {
781 self.ctx
782 .binder
783 .get_symbols()
784 .find_by_name(&ident.escaped_text)
785 })?;
786
787 let symbol = self.ctx.binder.get_symbol(sym_id)?;
788
789 (symbol.flags & symbol_flags::CLASS != 0).then_some(sym_id)
791 }
792
793 fn find_enclosing_class_for_new(&self, idx: tsz_parser::parser::NodeIndex) -> Option<SymbolId> {
797 use tsz_parser::parser::syntax_kind_ext;
798
799 let mut current = idx;
800
801 while let Some(ext) = self.ctx.arena.get_extended(current) {
802 let parent_idx = ext.parent;
804 if parent_idx.is_none() {
805 break;
806 }
807 let Some(parent_node) = self.ctx.arena.get(parent_idx) else {
808 break;
809 };
810
811 if parent_node.kind == syntax_kind_ext::CLASS_DECLARATION
813 || parent_node.kind == syntax_kind_ext::CLASS_EXPRESSION
814 {
815 let class_data = self.ctx.arena.get_class(parent_node)?;
816
817 let name_idx = class_data.name;
820 if let Some(sym_id) = self.ctx.binder.get_node_symbol(name_idx) {
821 return Some(sym_id);
822 }
823
824 if let Some(sym_id) = self.ctx.binder.get_node_symbol(parent_idx) {
829 return Some(sym_id);
830 }
831
832 return None;
835 }
836
837 current = parent_idx;
838 }
839
840 None
841 }
842
843 fn emit_constructor_access_error(
845 &mut self,
846 idx: tsz_parser::parser::NodeIndex,
847 class_sym: SymbolId,
848 is_private: bool,
849 ) {
850 use crate::diagnostics::diagnostic_codes;
851
852 let class_name = self.get_symbol_display_name(class_sym);
853
854 if is_private {
855 let message = format!(
857 "Constructor of class '{class_name}' is private and only accessible within the class declaration."
858 );
859 self.error_at_node(idx, &message, diagnostic_codes::CONSTRUCTOR_OF_CLASS_IS_PRIVATE_AND_ONLY_ACCESSIBLE_WITHIN_THE_CLASS_DECLARATION);
860 } else {
861 let message = format!(
863 "Constructor of class '{class_name}' is protected and only accessible within the class declaration."
864 );
865 self.error_at_node(idx, &message, diagnostic_codes::CONSTRUCTOR_OF_CLASS_IS_PROTECTED_AND_ONLY_ACCESSIBLE_WITHIN_THE_CLASS_DECLARATI);
866 }
867 }
868
869 fn get_symbol_display_name(&self, sym_id: SymbolId) -> String {
871 if let Some(symbol) = self.ctx.binder.get_symbol(sym_id) {
872 symbol.escaped_name.clone()
873 } else {
874 "<unknown>".to_string()
875 }
876 }
877}