diff --git a/src/analyzer/expr/call/atomic_static_call_analyzer.rs b/src/analyzer/expr/call/atomic_static_call_analyzer.rs index 6885bdf2..0c01cb9a 100644 --- a/src/analyzer/expr/call/atomic_static_call_analyzer.rs +++ b/src/analyzer/expr/call/atomic_static_call_analyzer.rs @@ -10,8 +10,8 @@ use oxidized::{ }; use crate::{ - function_analysis_data::FunctionAnalysisData, scope_analyzer::ScopeAnalyzer, - scope::BlockContext, statements_analyzer::StatementsAnalyzer, + function_analysis_data::FunctionAnalysisData, scope::BlockContext, + scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer, stmt_analyzer::AnalysisError, }; @@ -97,7 +97,8 @@ pub(crate) fn analyze( } } TAtomic::TLiteralClassname { name } => *name, - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { let classlike_name = if let TAtomic::TNamedObject { name, .. } = &as_type.types.first().unwrap() { name diff --git a/src/analyzer/expr/call/instance_call_analyzer.rs b/src/analyzer/expr/call/instance_call_analyzer.rs index a8ffb677..f1d8c9f7 100644 --- a/src/analyzer/expr/call/instance_call_analyzer.rs +++ b/src/analyzer/expr/call/instance_call_analyzer.rs @@ -3,8 +3,8 @@ use std::rc::Rc; use crate::expr::expression_identifier; use crate::expression_analyzer; use crate::function_analysis_data::FunctionAnalysisData; -use crate::scope_analyzer::ScopeAnalyzer; use crate::scope::BlockContext; +use crate::scope_analyzer::ScopeAnalyzer; use crate::statements_analyzer::StatementsAnalyzer; use crate::stmt_analyzer::AnalysisError; use hakana_reflection_info::issue::{Issue, IssueKind}; @@ -122,7 +122,8 @@ pub(crate) fn analyze( continue; } } - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { class_types.extend(&as_type.types); continue; } diff --git a/src/analyzer/expr/call/new_analyzer.rs b/src/analyzer/expr/call/new_analyzer.rs index ed528787..a0428a92 100644 --- a/src/analyzer/expr/call/new_analyzer.rs +++ b/src/analyzer/expr/call/new_analyzer.rs @@ -13,8 +13,8 @@ use rustc_hash::FxHashMap; use crate::expr::call_analyzer::{check_method_args, get_generic_param_for_offset}; use crate::expression_analyzer; use crate::function_analysis_data::FunctionAnalysisData; -use crate::scope_analyzer::ScopeAnalyzer; use crate::scope::BlockContext; +use crate::scope_analyzer::ScopeAnalyzer; use crate::statements_analyzer::StatementsAnalyzer; use crate::stmt_analyzer::AnalysisError; use hakana_reflection_info::data_flow::graph::GraphKind; @@ -200,7 +200,7 @@ fn analyze_atomic( } } TAtomic::TLiteralClassname { name } => *name, - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } | TAtomic::TClassTypeConstant { as_type, .. } => { let generic_param_type = &as_type.types[0]; if let TAtomic::TNamedObject { name, .. } = generic_param_type { *name diff --git a/src/analyzer/expr/fetch/array_fetch_analyzer.rs b/src/analyzer/expr/fetch/array_fetch_analyzer.rs index c03eacb6..53217fde 100644 --- a/src/analyzer/expr/fetch/array_fetch_analyzer.rs +++ b/src/analyzer/expr/fetch/array_fetch_analyzer.rs @@ -326,7 +326,9 @@ pub(crate) fn get_array_access_type_given_offset( } = atomic_var_type { atomic_var_type = as_type.get_single() - } else if let TAtomic::TGenericParam { as_type, .. } = atomic_var_type { + } else if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = atomic_var_type + { array_atomic_types.extend(&as_type.types); continue; } diff --git a/src/analyzer/expr/fetch/instance_property_fetch_analyzer.rs b/src/analyzer/expr/fetch/instance_property_fetch_analyzer.rs index 14d8a1fa..573b634d 100644 --- a/src/analyzer/expr/fetch/instance_property_fetch_analyzer.rs +++ b/src/analyzer/expr/fetch/instance_property_fetch_analyzer.rs @@ -7,6 +7,7 @@ use hakana_reflection_info::issue::{Issue, IssueKind}; use hakana_reflection_info::t_atomic::TAtomic; use hakana_reflection_info::EFFECT_READ_PROPS; use hakana_type::{add_union_type, get_mixed_any, get_null}; +use itertools::Itertools; use oxidized::{ aast::{self, Expr}, ast_defs::Pos, @@ -106,9 +107,24 @@ pub(crate) fn analyze( let mut has_nullsafe_null = false; if let Some(prop_name) = prop_name { - let var_atomic_types = &stmt_var_type.types; - for lhs_type_part in var_atomic_types { - if let TAtomic::TNull = lhs_type_part { + let mut var_atomic_types = stmt_var_type.types.iter().collect_vec(); + while let Some(mut var_atomic_type) = var_atomic_types.pop() { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = var_atomic_type + { + var_atomic_types.extend(&as_type.types); + continue; + } + + if let TAtomic::TTypeAlias { + as_type: Some(as_type), + .. + } = var_atomic_type + { + var_atomic_type = as_type.get_single(); + } + + if let TAtomic::TNull = var_atomic_type { if nullsafe { has_nullsafe_null = true; continue; @@ -136,7 +152,7 @@ pub(crate) fn analyze( analysis_data, context, in_assignment, - lhs_type_part.clone(), + var_atomic_type.clone(), &prop_name, &stmt_var_id, )?; diff --git a/src/analyzer/functionlike_analyzer.rs b/src/analyzer/functionlike_analyzer.rs index 5a66ef92..776496a5 100644 --- a/src/analyzer/functionlike_analyzer.rs +++ b/src/analyzer/functionlike_analyzer.rs @@ -960,6 +960,7 @@ impl<'a> FunctionLikeAnalyzer<'a> { TAtomic::TClassTypeConstant { class_type, member_name, + .. } => match class_type.as_ref() { TAtomic::TNamedObject { name, .. } | TAtomic::TReference { name, .. } => { diff --git a/src/analyzer/reconciler/simple_assertion_reconciler.rs b/src/analyzer/reconciler/simple_assertion_reconciler.rs index cca9407f..1fa3bb2a 100644 --- a/src/analyzer/reconciler/simple_assertion_reconciler.rs +++ b/src/analyzer/reconciler/simple_assertion_reconciler.rs @@ -15,8 +15,8 @@ use hakana_reflection_info::{ use hakana_str::StrId; use hakana_type::{ get_arraykey, get_bool, get_false, get_float, get_int, get_keyset, get_mixed_any, - get_mixed_dict, get_mixed_maybe_from_loop, get_mixed_vec, get_nothing, get_null, get_num, - get_object, get_scalar, get_string, get_true, intersect_union_types, + get_mixed_dict, get_mixed_keyset, get_mixed_maybe_from_loop, get_mixed_vec, get_nothing, + get_null, get_num, get_object, get_scalar, get_string, get_true, intersect_union_types, template::TemplateBound, type_comparator::{ atomic_type_comparator, type_comparison_result::TypeComparisonResult, union_type_comparator, @@ -466,13 +466,12 @@ pub(crate) fn intersect_null( TAtomic::TNull => { acceptable_types.push(TAtomic::TNull); } - TAtomic::TMixed - | TAtomic::TMixedWithFlags(_, false, _, false) - | TAtomic::TClassTypeConstant { .. } => { + TAtomic::TMixed | TAtomic::TMixedWithFlags(_, false, _, false) => { acceptable_types.push(TAtomic::TNull); did_remove_type = true; } - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { if as_type.is_mixed() { let atomic = atomic.replace_template_extends(get_null()); @@ -659,7 +658,8 @@ fn intersect_vec( TAtomic::TVec { .. } => { acceptable_types.push(atomic.clone()); } - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { if as_type.is_mixed() { let atomic = atomic.replace_template_extends(get_mixed_vec()); @@ -682,21 +682,12 @@ fn intersect_vec( did_remove_type = true; } - TAtomic::TClassTypeConstant { .. } => { - acceptable_types.push(TAtomic::TVec { - known_items: None, - type_param: Box::new(get_mixed_any()), - non_empty: false, - known_count: None, - }); - did_remove_type = true; - } TAtomic::TTypeVariable { name } => { if let Some(pos) = pos { if let Some((lower_bounds, _)) = analysis_data.type_variable_bounds.get_mut(name) { - let mut bound = TemplateBound::new(get_mixed_dict(), 0, None, None); + let mut bound = TemplateBound::new(get_mixed_vec(), 0, None, None); bound.pos = Some(statements_analyzer.get_hpos(pos)); lower_bounds.push(bound); } @@ -795,8 +786,28 @@ fn intersect_keyset( TAtomic::TKeyset { .. } => { acceptable_types.push(atomic.clone()); } - TAtomic::TClassTypeConstant { .. } => { - acceptable_types.push(atomic.clone()); + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { + if as_type.is_mixed() { + let atomic = atomic.replace_template_extends(get_mixed_keyset()); + + acceptable_types.push(atomic); + } else { + let atomic = atomic.replace_template_extends(intersect_keyset( + assertion, + as_type, + None, + false, + analysis_data, + statements_analyzer, + pos, + calling_functionlike_id, + is_equality, + suppressed_issues, + )); + acceptable_types.push(atomic); + } + did_remove_type = true; } TAtomic::TNamedObject { @@ -881,7 +892,8 @@ fn intersect_dict( TAtomic::TDict { .. } => { acceptable_types.push(atomic.clone()); } - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { if as_type.is_mixed() { let atomic = atomic.replace_template_extends(get_mixed_dict()); @@ -904,15 +916,6 @@ fn intersect_dict( did_remove_type = true; } - TAtomic::TClassTypeConstant { .. } => { - acceptable_types.push(TAtomic::TDict { - known_items: None, - params: Some((Box::new(get_arraykey(true)), Box::new(get_mixed_any()))), - non_empty: false, - shape_name: None, - }); - did_remove_type = true; - } TAtomic::TTypeVariable { name } => { if let Some(pos) = pos { if let Some((lower_bounds, _)) = @@ -1153,10 +1156,6 @@ fn intersect_string( | TAtomic::TArraykey { .. } => { return get_string(); } - TAtomic::TClassTypeConstant { .. } => { - acceptable_types.push(TAtomic::TString); - did_remove_type = true; - } TAtomic::TEnumLiteralCase { constraint_type, .. } => { @@ -1176,7 +1175,8 @@ fn intersect_string( return get_string(); } } - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { if as_type.is_mixed() { let atomic = atomic.replace_template_extends(get_string()); @@ -1303,7 +1303,8 @@ fn intersect_int( | TAtomic::TMixedFromLoopIsset => { return get_int(); } - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { if as_type.is_mixed() { let atomic = atomic.replace_template_extends(get_int()); @@ -1327,10 +1328,6 @@ fn intersect_int( did_remove_type = true; } - TAtomic::TClassTypeConstant { .. } => { - acceptable_types.push(TAtomic::TInt); - did_remove_type = true; - } TAtomic::TTypeVariable { name } => { if let Some(pos) = pos { if let Some((lower_bounds, _)) = @@ -2005,7 +2002,8 @@ fn reconcile_has_array_key( did_remove_type = true; } } - TAtomic::TGenericParam { ref as_type, .. } => { + TAtomic::TGenericParam { ref as_type, .. } + | TAtomic::TClassTypeConstant { ref as_type, .. } => { if as_type.is_mixed() { acceptable_types.push(atomic); } else { @@ -2034,8 +2032,7 @@ fn reconcile_has_array_key( TAtomic::TMixed | TAtomic::TMixedWithFlags(..) | TAtomic::TMixedFromLoopIsset - | TAtomic::TTypeAlias { .. } - | TAtomic::TClassTypeConstant { .. } => { + | TAtomic::TTypeAlias { .. } => { did_remove_type = true; acceptable_types.push(atomic); } @@ -2260,7 +2257,8 @@ fn reconcile_has_nonnull_entry_for_key( did_remove_type = true; } } - TAtomic::TGenericParam { ref as_type, .. } => { + TAtomic::TGenericParam { ref as_type, .. } + | TAtomic::TClassTypeConstant { ref as_type, .. } => { if as_type.is_mixed() { acceptable_types.push(atomic); } else { @@ -2290,8 +2288,7 @@ fn reconcile_has_nonnull_entry_for_key( TAtomic::TMixed | TAtomic::TMixedWithFlags(..) | TAtomic::TMixedFromLoopIsset - | TAtomic::TTypeAlias { .. } - | TAtomic::TClassTypeConstant { .. } => { + | TAtomic::TTypeAlias { .. } => { did_remove_type = true; acceptable_types.push(atomic); } diff --git a/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs b/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs index e2e348ca..17fd1897 100644 --- a/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs +++ b/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs @@ -341,7 +341,9 @@ fn subtract_object( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_object( assertion, @@ -415,7 +417,9 @@ fn subtract_vec( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_vec( assertion, @@ -488,7 +492,9 @@ fn subtract_keyset( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_keyset( assertion, @@ -561,7 +567,9 @@ fn subtract_dict( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_dict( assertion, @@ -634,7 +642,9 @@ fn subtract_string( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_string( assertion, @@ -738,7 +748,9 @@ fn subtract_int( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_int( assertion, @@ -851,7 +863,9 @@ fn subtract_float( let mut acceptable_types = vec![]; for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = &atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = &atomic + { if !is_equality && !as_type.is_mixed() { let new_atomic = atomic.replace_template_extends(subtract_float( assertion, @@ -948,7 +962,9 @@ fn subtract_num( let mut existing_var_type = existing_var_type.clone(); for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = atomic + { if !is_equality && !as_type.is_mixed() { let atomic = atomic.replace_template_extends(subtract_num( assertion, @@ -1043,7 +1059,9 @@ fn subtract_arraykey( let mut existing_var_type = existing_var_type.clone(); for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = atomic + { if !is_equality && !as_type.is_mixed() { let atomic = atomic.replace_template_extends(subtract_arraykey( assertion, @@ -1140,7 +1158,9 @@ fn subtract_bool( let mut existing_var_type = existing_var_type.clone(); for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = atomic + { if !is_equality && !as_type.is_mixed() { let atomic = atomic.replace_template_extends(subtract_bool( assertion, @@ -1225,7 +1245,8 @@ pub(crate) fn subtract_null( for atomic in existing_var_types { match atomic { - TAtomic::TGenericParam { ref as_type, .. } => { + TAtomic::TGenericParam { ref as_type, .. } + | TAtomic::TClassTypeConstant { ref as_type, .. } => { let new_atomic = atomic.replace_template_extends(subtract_null( assertion, as_type, @@ -1246,10 +1267,6 @@ pub(crate) fn subtract_null( did_remove_type = true; acceptable_types.push(atomic); } - TAtomic::TClassTypeConstant { .. } => { - acceptable_types.push(atomic); - did_remove_type = true; - } TAtomic::TMixed => { did_remove_type = true; acceptable_types.push(TAtomic::TMixedWithFlags(false, false, false, true)); @@ -1315,7 +1332,9 @@ fn subtract_false( let mut existing_var_type = existing_var_type.clone(); for atomic in existing_var_types { - if let TAtomic::TGenericParam { as_type, .. } = atomic { + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = atomic + { if !is_equality && !as_type.is_mixed() { let atomic = atomic.replace_template_extends(subtract_false( assertion, @@ -1397,7 +1416,8 @@ fn subtract_true( for atomic in existing_var_types { match atomic { - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } => { if !is_equality && !as_type.is_mixed() { let atomic = atomic.replace_template_extends(subtract_true( assertion, diff --git a/src/analyzer/stmt/foreach_analyzer.rs b/src/analyzer/stmt/foreach_analyzer.rs index 5763d44e..a2a77cff 100644 --- a/src/analyzer/stmt/foreach_analyzer.rs +++ b/src/analyzer/stmt/foreach_analyzer.rs @@ -6,8 +6,8 @@ use crate::{ }, expression_analyzer, function_analysis_data::FunctionAnalysisData, - scope_analyzer::ScopeAnalyzer, scope::{loop_scope::LoopScope, BlockContext}, + scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer, stmt_analyzer::AnalysisError, }; @@ -22,6 +22,7 @@ use hakana_type::{ add_optional_union_type, add_union_type, combine_optional_union_types, get_arraykey, get_int, get_literal_int, get_literal_string, get_mixed_any, get_nothing, }; +use itertools::Itertools; use oxidized::{aast, ast_defs}; pub(crate) fn analyze( @@ -210,7 +211,7 @@ fn check_iterator_type( //let mut invalid_iterator_types = vec![]; //let mut raw_object_types = vec![]; - let mut iterator_atomic_types = iterator_type.types.clone(); + let mut iterator_atomic_types = iterator_type.types.iter().collect_vec(); let mut key_type = None; let mut value_type = None; @@ -218,8 +219,10 @@ fn check_iterator_type( let codebase = statements_analyzer.get_codebase(); while let Some(mut iterator_atomic_type) = iterator_atomic_types.pop() { - if let TAtomic::TGenericParam { as_type, .. } = iterator_atomic_type { - iterator_atomic_types.extend(as_type.types); + if let TAtomic::TGenericParam { as_type, .. } + | TAtomic::TClassTypeConstant { as_type, .. } = iterator_atomic_type + { + iterator_atomic_types.extend(&as_type.types); continue; } @@ -228,7 +231,7 @@ fn check_iterator_type( .. } = iterator_atomic_type { - iterator_atomic_type = as_type.get_single().clone(); + iterator_atomic_type = as_type.get_single(); } match &iterator_atomic_type { @@ -315,7 +318,7 @@ fn check_iterator_type( DictKey::Int(var_id) => { key_param = add_union_type( key_param, - &get_literal_int(var_id as i64), + &get_literal_int(*var_id as i64), codebase, false, ); @@ -329,7 +332,7 @@ fn check_iterator_type( DictKey::String(var_id) => { key_param = add_union_type( key_param, - &get_literal_string(var_id), + &get_literal_string(var_id.clone()), codebase, false, ); @@ -398,13 +401,13 @@ fn check_iterator_type( } else { get_int() }; - let mut value_param = *type_param; + let mut value_param = (**type_param).clone(); if let Some(known_items) = known_items { for (offset, (_, known_item)) in known_items { key_param = add_union_type( key_param, - &get_literal_int(offset as i64), + &get_literal_int(*offset as i64), codebase, false, ); @@ -415,7 +418,9 @@ fn check_iterator_type( (key_param, value_param) } - TAtomic::TKeyset { type_param, .. } => ((*type_param).clone(), *type_param), + TAtomic::TKeyset { type_param, .. } => { + ((**type_param).clone(), (**type_param).clone()) + } _ => panic!(), }; @@ -458,7 +463,7 @@ fn check_iterator_type( .. } = iterator_atomic_type { - match name { + match *name { StrId::KEYED_CONTAINER | StrId::KEYED_ITERATOR | StrId::KEYED_TRAVERSABLE => { has_valid_iterator = true; key_type = Some(combine_optional_union_types( diff --git a/src/code_info/t_atomic.rs b/src/code_info/t_atomic.rs index 347fb361..be327ad0 100644 --- a/src/code_info/t_atomic.rs +++ b/src/code_info/t_atomic.rs @@ -160,6 +160,7 @@ pub enum TAtomic { TClassTypeConstant { class_type: Box, member_name: StrId, + as_type: Box, }, TEnumClassLabel { class_name: Option, @@ -185,9 +186,7 @@ impl TAtomic { refs.push(StrId::AWAITABLE); let mut str = String::new(); str += "Awaitable<"; - str += value - .get_id_with_refs(interner, refs, indent) - .as_str(); + str += value.get_id_with_refs(interner, refs, indent).as_str(); str += ">"; str } @@ -1020,22 +1019,33 @@ impl TAtomic { } pub fn replace_template_extends(&self, new_as_type: TUnion) -> TAtomic { - if let TAtomic::TGenericParam { - param_name, - defining_entity, - extra_types, - .. - } = self - { - return TAtomic::TGenericParam { - as_type: Box::new(new_as_type), - param_name: *param_name, - defining_entity: *defining_entity, - extra_types: extra_types.clone(), - }; + match self { + TAtomic::TGenericParam { + param_name, + defining_entity, + extra_types, + .. + } => { + return TAtomic::TGenericParam { + as_type: Box::new(new_as_type), + param_name: *param_name, + defining_entity: *defining_entity, + extra_types: extra_types.clone(), + }; + } + TAtomic::TClassTypeConstant { + class_type, + member_name, + .. + } => { + return TAtomic::TClassTypeConstant { + as_type: Box::new(new_as_type), + class_type: class_type.clone(), + member_name: *member_name, + }; + } + _ => panic!(), } - - panic!() } pub fn get_non_empty_vec(&self, known_count: Option) -> TAtomic { @@ -1494,7 +1504,7 @@ impl TAtomic { as_type: Some(as_type), .. } => as_type.is_json_compatible(banned_type_aliases), - TAtomic::TGenericParam { as_type, .. } => { + TAtomic::TGenericParam { as_type, .. } | TAtomic::TClassTypeConstant { as_type, .. } => { as_type.is_json_compatible(banned_type_aliases) } _ => false, diff --git a/src/code_info_builder/typehint_resolver.rs b/src/code_info_builder/typehint_resolver.rs index 8dbd69be..c51d3ae2 100644 --- a/src/code_info_builder/typehint_resolver.rs +++ b/src/code_info_builder/typehint_resolver.rs @@ -751,6 +751,7 @@ pub fn get_type_from_hint( } else { return None; }, + as_type: Box::new(get_mixed_any()), }; } diff --git a/src/ttype/lib.rs b/src/ttype/lib.rs index 0d48e8eb..7aeb760a 100644 --- a/src/ttype/lib.rs +++ b/src/ttype/lib.rs @@ -179,6 +179,12 @@ pub fn get_mixed_dict() -> TUnion { get_dict(get_arraykey(true), get_mixed_any()) } +pub fn get_mixed_keyset() -> TUnion { + wrap_atomic(TAtomic::TKeyset { + type_param: Box::new(get_arraykey(true)), + }) +} + #[inline] pub fn add_optional_union_type( base_type: TUnion, @@ -757,6 +763,7 @@ pub fn get_atomic_syntax_type( TAtomic::TClassTypeConstant { class_type, member_name, + .. } => { let lhs = get_atomic_syntax_type(class_type, codebase, interner, is_valid); format!("{}::{}", lhs, interner.lookup(member_name)) diff --git a/src/ttype/type_expander.rs b/src/ttype/type_expander.rs index eaa086b2..5cb59f56 100644 --- a/src/ttype/type_expander.rs +++ b/src/ttype/type_expander.rs @@ -487,6 +487,7 @@ fn expand_atomic( } else if let TAtomic::TClassTypeConstant { class_type, member_name, + as_type, } = return_type_part { let mut atomic_return_type_parts = vec![]; @@ -507,7 +508,9 @@ fn expand_atomic( match class_type.as_ref() { TAtomic::TNamedObject { - name: class_name, .. + name: class_name, + is_this, + .. } => { let classlike_storage = if let Some(c) = codebase.classlike_infos.get(class_name) { c @@ -527,9 +530,27 @@ fn expand_atomic( return; }; - match type_constant { - ClassConstantType::Abstract(Some(mut type_)) - | ClassConstantType::Concrete(mut type_) => { + let mut is_this = *is_this; + + if is_this { + if let StaticClassType::Object(obj) = options.static_class_type { + if let TAtomic::TNamedObject { + name: new_this_name, + .. + } = obj + { + if !codebase.class_extends_or_implements(new_this_name, class_name) { + is_this = false + } + } + } else { + is_this = false; + } + } + + match (is_this, type_constant) { + (_, ClassConstantType::Concrete(mut type_)) + | (false, ClassConstantType::Abstract(Some(mut type_))) => { expand_union(codebase, interner, &mut type_, options, data_flow_graph); *skip_key = true; @@ -545,6 +566,11 @@ fn expand_atomic( v })); } + (true, ClassConstantType::Abstract(Some(mut type_))) => { + expand_union(codebase, interner, &mut type_, options, data_flow_graph); + + *as_type = Box::new(type_); + } _ => {} }; } diff --git a/tests/inference/Generics/Class/classTypeConstantAssertion/input.hack b/tests/inference/Generics/Class/classTypeConstantAssertion/input.hack new file mode 100644 index 00000000..01fb9c57 --- /dev/null +++ b/tests/inference/Generics/Class/classTypeConstantAssertion/input.hack @@ -0,0 +1,13 @@ +abstract class A { + <<__Enforceable>> + abstract const type TClass as B; + + private static function getBillingItem(mixed $foo): ?this::TClass { + if ($foo is this::TClass) { + return $foo; + } + return null; + } +} + +abstract class B {} \ No newline at end of file