diff --git a/src/aast_utils/naming_visitor.rs b/src/aast_utils/naming_visitor.rs index 48ff73d9..ad010d99 100644 --- a/src/aast_utils/naming_visitor.rs +++ b/src/aast_utils/naming_visitor.rs @@ -218,6 +218,16 @@ impl<'ast> Visitor<'ast> for Scanner<'_> { nc.in_member_id = false; result } + aast::Expr_::ClassConst(boxed) => { + let result = e.recurse(nc, self); + + self.resolved_names.insert( + boxed.1 .0.start_offset() as u32, + self.interner.intern(boxed.1 .1.clone()), + ); + + result + } _ => e.recurse(nc, self), }; diff --git a/src/analyzer/expr/const_fetch_analyzer.rs b/src/analyzer/expr/const_fetch_analyzer.rs index d3cb6811..b76e3f81 100644 --- a/src/analyzer/expr/const_fetch_analyzer.rs +++ b/src/analyzer/expr/const_fetch_analyzer.rs @@ -9,10 +9,11 @@ use hakana_type::get_mixed_any; use hakana_type::get_string; use hakana_type::type_expander; use hakana_type::type_expander::TypeExpansionOptions; +use hakana_type::wrap_atomic; use crate::function_analysis_data::FunctionAnalysisData; -use crate::scope_analyzer::ScopeAnalyzer; use crate::scope::BlockContext; +use crate::scope_analyzer::ScopeAnalyzer; use crate::stmt_analyzer::AnalysisError; use oxidized::ast_defs; @@ -53,7 +54,7 @@ pub(crate) fn analyze( } else if *name == StrId::FUNCTION_CONST { get_string() } else if let Some(t) = &constant_storage.inferred_type { - t.clone() + wrap_atomic(t.clone()) } else if let Some(t) = &constant_storage.provided_type { t.clone() } else { diff --git a/src/analyzer/reconciler/assertion_reconciler.rs b/src/analyzer/reconciler/assertion_reconciler.rs index 71e90bad..473b9c52 100644 --- a/src/analyzer/reconciler/assertion_reconciler.rs +++ b/src/analyzer/reconciler/assertion_reconciler.rs @@ -17,7 +17,10 @@ use hakana_reflection_info::{ use hakana_str::StrId; use hakana_type::{ get_arraykey, get_mixed_any, get_mixed_maybe_from_loop, get_nothing, type_combiner, - type_comparator::{atomic_type_comparator, type_comparison_result::TypeComparisonResult}, + type_comparator::{ + atomic_type_comparator::{self, expand_constant_value}, + type_comparison_result::TypeComparisonResult, + }, type_expander::{self, TypeExpansionOptions}, wrap_atomic, }; @@ -279,13 +282,9 @@ pub(crate) fn intersect_atomic_with_atomic( ) { for (_, c1) in &storage_1.constants { for (_, c2) in &storage_2.constants { - if let (Some(c1_type), Some(c2_type)) = - (&c1.inferred_type, &c2.inferred_type) - { - if c1_type == c2_type { - return Some(type_2_atomic.clone()); - } - } else { + let c1_value = expand_constant_value(c1, codebase); + let c2_value = expand_constant_value(c2, codebase); + if c1_value == c2_value { return Some(type_2_atomic.clone()); } } @@ -308,19 +307,42 @@ pub(crate) fn intersect_atomic_with_atomic( ) { if let Some(c1) = &storage_1.constants.get(member_name) { for (_, c2) in &storage_2.constants { - if let (Some(c1_type), Some(c2_type)) = - (&c1.inferred_type, &c2.inferred_type) - { - if c1_type == c2_type { - return Some(type_2_atomic.clone()); - } - } else { + let c1_value = expand_constant_value(c1, codebase); + let c2_value = expand_constant_value(c2, codebase); + + if c1_value == c2_value { return Some(type_2_atomic.clone()); } } } } } + ( + TAtomic::TEnum { + name: type_1_name, .. + }, + TAtomic::TEnumLiteralCase { + enum_name: type_2_name, + member_name, + .. + }, + ) => { + if let (Some(storage_1), Some(storage_2)) = ( + codebase.classlike_infos.get(type_1_name), + codebase.classlike_infos.get(type_2_name), + ) { + if let Some(c2) = &storage_2.constants.get(member_name) { + for (_, c1) in &storage_1.constants { + let c1_value = expand_constant_value(c1, codebase); + let c2_value = expand_constant_value(c2, codebase); + + if c1_value == c2_value { + return Some(type_1_atomic.clone()); + } + } + } + } + } ( TAtomic::TEnumLiteralCase { enum_name: type_1_name, @@ -872,9 +894,12 @@ fn intersect_enumcase_with_string( let enum_storage = codebase.classlike_infos.get(type_1_name).unwrap(); if let Some(member_storage) = enum_storage.constants.get(type_1_member_name) { if let Some(inferred_type) = &member_storage.inferred_type { - if let Some(inferred_value) = inferred_type.get_single_literal_string_value() { + if let TAtomic::TLiteralString { + value: inferred_value, + } = inferred_type + { return Some(TAtomic::TLiteralString { - value: inferred_value, + value: inferred_value.clone(), }); } } @@ -891,9 +916,12 @@ fn intersect_enum_case_with_int( let enum_storage = codebase.classlike_infos.get(type_1_name).unwrap(); if let Some(member_storage) = enum_storage.constants.get(type_1_member_name) { if let Some(inferred_type) = &member_storage.inferred_type { - if let Some(inferred_value) = inferred_type.get_single_literal_int_value() { + if let TAtomic::TLiteralInt { + value: inferred_value, + } = inferred_type + { return Some(TAtomic::TLiteralInt { - value: inferred_value, + value: *inferred_value, }); } } @@ -912,7 +940,7 @@ fn intersect_enum_with_literal( for (case_name, enum_case) in &enum_storage.constants { if let Some(inferred_type) = &enum_case.inferred_type { - if inferred_type.get_single() == type_2_atomic { + if inferred_type == type_2_atomic { return Some(TAtomic::TEnumLiteralCase { enum_name: *type_1_name, member_name: *case_name, diff --git a/src/analyzer/reconciler/negated_assertion_reconciler.rs b/src/analyzer/reconciler/negated_assertion_reconciler.rs index f505d5e2..d0f51018 100644 --- a/src/analyzer/reconciler/negated_assertion_reconciler.rs +++ b/src/analyzer/reconciler/negated_assertion_reconciler.rs @@ -484,10 +484,11 @@ fn handle_literal_negated_equality( let mut member_enum_literals = vec![]; for (cname, const_info) in &enum_storage.constants { if let Some(inferred_type) = &const_info.inferred_type { - if let Some(const_inferred_value) = - inferred_type.get_single_literal_string_value() + if let TAtomic::TLiteralString { + value: const_inferred_value, + } = inferred_type { - if &const_inferred_value != assertion_value { + if const_inferred_value != assertion_value { if let Some(constant_type) = codebase.get_class_constant_type( &existing_name, false, @@ -522,10 +523,11 @@ fn handle_literal_negated_equality( let mut member_enum_literals = vec![]; for (cname, const_info) in &enum_storage.constants { if let Some(inferred_type) = &const_info.inferred_type { - if let Some(const_inferred_value) = - inferred_type.get_single_literal_int_value() + if let TAtomic::TLiteralInt { + value: const_inferred_value, + } = inferred_type { - if &const_inferred_value != assertion_value { + if const_inferred_value != assertion_value { if let Some(constant_type) = codebase.get_class_constant_type( &existing_name, false, @@ -577,10 +579,11 @@ fn handle_literal_negated_equality( if let Some(const_info) = enum_storage.constants.get(&existing_member_name) { if let Some(const_inferred_type) = &const_info.inferred_type { - if let Some(const_inferred_value) = - const_inferred_type.get_single_literal_string_value() + if let TAtomic::TLiteralString { + value: const_inferred_value, + } = const_inferred_type { - if &const_inferred_value == value { + if const_inferred_value == value { matched_string = true; } } diff --git a/src/code_info/class_constant_info.rs b/src/code_info/class_constant_info.rs index bc9ef967..150c8e1f 100644 --- a/src/code_info/class_constant_info.rs +++ b/src/code_info/class_constant_info.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - code_location::HPos, functionlike_parameter::UnresolvedConstantComponent, t_union::TUnion, + code_location::HPos, functionlike_parameter::UnresolvedConstantComponent, t_atomic::TAtomic, + t_union::TUnion, }; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -12,7 +13,7 @@ pub struct ConstantInfo { pub provided_type: Option, - pub inferred_type: Option, + pub inferred_type: Option, pub unresolved_value: Option, diff --git a/src/code_info/codebase_info/mod.rs b/src/code_info/codebase_info/mod.rs index da37ed78..cf3be861 100644 --- a/src/code_info/codebase_info/mod.rs +++ b/src/code_info/codebase_info/mod.rs @@ -223,11 +223,11 @@ impl CodebaseInfo { }])) } else if let Some(constant_storage) = classlike_storage.constants.get(const_name) { if matches!(classlike_storage.kind, SymbolKind::EnumClass) { - return constant_storage.provided_type.as_ref().cloned(); + return constant_storage.provided_type.as_ref().map(|t| t.clone()); } else if let Some(provided_type) = &constant_storage.provided_type { if provided_type.types.iter().all(|v| v.is_boring_scalar()) && !is_this { if let Some(inferred_type) = &constant_storage.inferred_type { - Some(inferred_type.clone()) + Some(TUnion::new(vec![inferred_type.clone()])) } else { Some(provided_type.clone()) } @@ -236,7 +236,7 @@ impl CodebaseInfo { } } else if let Some(inferred_type) = &constant_storage.inferred_type { if !is_this { - Some(inferred_type.clone()) + Some(TUnion::new(vec![inferred_type.clone()])) } else { None } @@ -258,11 +258,7 @@ impl CodebaseInfo { ) -> Option<&TAtomic> { if let Some(classlike_storage) = self.classlike_infos.get(fq_class_name) { if let Some(constant_storage) = classlike_storage.constants.get(const_name) { - if let Some(inferred_type) = &constant_storage.inferred_type { - Some(inferred_type.get_single()) - } else { - None - } + constant_storage.inferred_type.as_ref() } else { None } diff --git a/src/code_info/t_atomic.rs b/src/code_info/t_atomic.rs index 5e31a913..6df0c7e1 100644 --- a/src/code_info/t_atomic.rs +++ b/src/code_info/t_atomic.rs @@ -87,6 +87,10 @@ pub enum TAtomic { member_name: StrId, constraint_type: Option>, }, + TMemberReference { + classlike_name: StrId, + member_name: StrId, + }, TLiteralInt { value: i64, }, @@ -397,6 +401,25 @@ impl TAtomic { } str } + TAtomic::TMemberReference { + classlike_name, + member_name, + .. + } => { + let mut str = String::new(); + if let Some(interner) = interner { + str += interner.lookup(classlike_name); + } else { + str += classlike_name.0.to_string().as_str(); + } + str += "::"; + if let Some(interner) = interner { + str += interner.lookup(member_name); + } else { + str += member_name.0.to_string().as_str(); + } + str + } TAtomic::TLiteralInt { value } => { let mut str = String::new(); str += "int("; @@ -706,6 +729,7 @@ impl TAtomic { | TAtomic::TLiteralClassname { .. } | TAtomic::TLiteralInt { .. } | TAtomic::TEnumLiteralCase { .. } + | TAtomic::TMemberReference { .. } | TAtomic::TClassTypeConstant { .. } | TAtomic::TLiteralString { .. } | TAtomic::TVoid @@ -1836,6 +1860,33 @@ pub fn populate_atomic_type( }; } } + TAtomic::TMemberReference { + ref classlike_name, + ref member_name, + } => { + match reference_source { + ReferenceSource::Symbol(in_signature, a) => symbol_references + .add_symbol_reference_to_class_member( + *a, + (*classlike_name, *member_name), + *in_signature, + ), + ReferenceSource::ClasslikeMember(in_signature, a, b) => symbol_references + .add_class_member_reference_to_class_member( + (*a, *b), + (*classlike_name, *member_name), + *in_signature, + ), + } + + if let Some(SymbolKind::Enum) = codebase_symbols.all.get(classlike_name) { + *t_atomic = TAtomic::TEnumLiteralCase { + enum_name: *classlike_name, + member_name: *member_name, + constraint_type: None, + }; + } + } TAtomic::TClassname { as_type } | TAtomic::TTypename { as_type } => { populate_atomic_type( as_type, diff --git a/src/code_info_builder/classlike_scanner.rs b/src/code_info_builder/classlike_scanner.rs index 4017caec..1b48c593 100644 --- a/src/code_info_builder/classlike_scanner.rs +++ b/src/code_info_builder/classlike_scanner.rs @@ -608,10 +608,8 @@ pub(crate) fn scan( simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { - for atomic in attribute_param_type.types.into_iter() { - if let TAtomic::TLiteralClassname { name: value } = atomic { - child_classlikes.insert(value); - } + if let TAtomic::TLiteralClassname { name: value } = attribute_param_type { + child_classlikes.insert(value); } } } diff --git a/src/code_info_builder/functionlike_scanner.rs b/src/code_info_builder/functionlike_scanner.rs index ee1c1fbe..a6d5967d 100644 --- a/src/code_info_builder/functionlike_scanner.rs +++ b/src/code_info_builder/functionlike_scanner.rs @@ -351,7 +351,7 @@ pub(crate) fn get_functionlike( simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { - if let Some(str) = attribute_param_type.get_single_literal_string_value() { + if let TAtomic::TLiteralString { value: str } = attribute_param_type { if let Some(source_type) = string_to_source_types(str) { source_types.push(source_type); } @@ -388,14 +388,9 @@ pub(crate) fn get_functionlike( let attribute_param_type = simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { - attribute_param_type - .get_literal_string_values() - .into_iter() - .for_each(|value| { - if let Some(str) = value { - removed_types.extend(string_to_sink_types(str)); - } - }) + if let TAtomic::TLiteralString { value } = attribute_param_type { + removed_types.extend(string_to_sink_types(value)); + } } } @@ -701,8 +696,7 @@ fn convert_param_nodes( simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { - if let Some(str) = - attribute_param_type.get_single_literal_string_value() + if let TAtomic::TLiteralString { value: str } = attribute_param_type { sink_types.extend(string_to_sink_types(str)); } @@ -719,8 +713,7 @@ fn convert_param_nodes( simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { - if let Some(str) = - attribute_param_type.get_single_literal_string_value() + if let TAtomic::TLiteralString { value: str } = attribute_param_type { removed_taints.extend(string_to_sink_types(str)); } diff --git a/src/code_info_builder/lib.rs b/src/code_info_builder/lib.rs index 39f72357..ee209843 100644 --- a/src/code_info_builder/lib.rs +++ b/src/code_info_builder/lib.rs @@ -19,6 +19,7 @@ use hakana_type::{get_bool, get_int, get_mixed_any, get_string}; use no_pos_hash::{position_insensitive_hash, Hasher}; use oxidized::ast::{FunParam, Tparam, TypeHint}; use oxidized::ast_defs::Id; +use oxidized::tast; use oxidized::{ aast, aast_visitor::{visit, AstParams, Node, Visitor}, @@ -376,9 +377,7 @@ impl<'ast> Visitor<'ast> for Scanner<'_> { let attribute_param_type = simple_type_inferer::infer(attribute_param_expr, self.resolved_names); - if let Some(attribute_param_type) = attribute_param_type { - let atomic_param_attribute = attribute_param_type.get_single(); - + if let Some(atomic_param_attribute) = attribute_param_type { if let TAtomic::TDict { known_items: Some(attribute_known_items), .. diff --git a/src/code_info_builder/simple_type_inferer.rs b/src/code_info_builder/simple_type_inferer.rs index 2edb6fab..24170496 100644 --- a/src/code_info_builder/simple_type_inferer.rs +++ b/src/code_info_builder/simple_type_inferer.rs @@ -1,47 +1,42 @@ -use hakana_reflection_info::{ - t_atomic::{DictKey, TAtomic}, - t_union::TUnion, -}; +use hakana_reflection_info::t_atomic::{DictKey, TAtomic}; use hakana_str::StrId; -use hakana_type::{ - get_false, get_float, get_int, get_literal_int, get_literal_string, get_nothing, get_null, - get_true, wrap_atomic, -}; +use hakana_type::{get_nothing, wrap_atomic}; use oxidized::{aast, ast_defs}; use rustc_hash::FxHashMap; use std::{collections::BTreeMap, num::ParseIntError, sync::Arc}; -pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) -> Option { +pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) -> Option { return match &expr.2 { aast::Expr_::ArrayGet(_) => None, - aast::Expr_::ClassConst(boxed) => { - if boxed.1 .1 == "class" { - match &boxed.0 .2 { - aast::ClassId_::CIexpr(lhs_expr) => { - if let aast::Expr_::Id(id) = &lhs_expr.2 { - match id.1.as_str() { - "self" | "parent" | "static" => None, - _ => { - let name_string = - *resolved_names.get(&(id.0.start_offset() as u32)).unwrap(); - - Some(wrap_atomic(TAtomic::TLiteralClassname { - name: name_string, - })) - } + aast::Expr_::ClassConst(boxed) => match &boxed.0 .2 { + aast::ClassId_::CIexpr(lhs_expr) => { + if let aast::Expr_::Id(id) = &lhs_expr.2 { + match id.1.as_str() { + "self" | "parent" | "static" => None, + _ => { + let name_string = + *resolved_names.get(&(id.0.start_offset() as u32)).unwrap(); + + if boxed.1 .1 == "class" { + Some(TAtomic::TLiteralClassname { name: name_string }) + } else { + Some(TAtomic::TMemberReference { + classlike_name: name_string, + member_name: *resolved_names + .get(&(boxed.1 .0.start_offset() as u32)) + .unwrap(), + }) } - } else { - None } } - _ => { - panic!() - } + } else { + None } - } else { - None } - } + _ => { + panic!() + } + }, aast::Expr_::FunctionPointer(_) => None, aast::Expr_::Shape(shape_fields) => { let mut known_items = BTreeMap::new(); @@ -53,7 +48,7 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) if let Some(field_type) = field_type { known_items.insert( DictKey::String(str.to_string()), - (false, Arc::new(field_type)), + (false, Arc::new(wrap_atomic(field_type))), ); } else { return None; @@ -63,12 +58,12 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) } } - Some(wrap_atomic(TAtomic::TDict { + Some(TAtomic::TDict { non_empty: !known_items.is_empty(), known_items: Some(known_items), params: None, shape_name: None, - })) + }) } aast::Expr_::ValCollection(boxed) => { let mut entries = BTreeMap::new(); @@ -77,19 +72,19 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) let entry_type = infer(entry_expr, resolved_names); if let Some(entry_type) = entry_type { - entries.insert(i, (false, entry_type)); + entries.insert(i, (false, wrap_atomic(entry_type))); } else { return None; } } match boxed.0 .1 { - oxidized::tast::VcKind::Vec => Some(wrap_atomic(TAtomic::TVec { + oxidized::tast::VcKind::Vec => Some(TAtomic::TVec { known_count: Some(entries.len()), known_items: Some(entries), type_param: Box::new(get_nothing()), non_empty: true, - })), + }), oxidized::tast::VcKind::Keyset => None, _ => panic!(), } @@ -104,7 +99,7 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) if let Some(value_type) = value_type { known_items.insert( DictKey::String(key_value.to_string()), - (false, Arc::new(value_type)), + (false, Arc::new(wrap_atomic(value_type))), ); } else { return None; @@ -116,27 +111,31 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) if known_items.len() < 100 { match boxed.0 .1 { - oxidized::tast::KvcKind::Dict => Some(wrap_atomic(TAtomic::TDict { + oxidized::tast::KvcKind::Dict => Some(TAtomic::TDict { non_empty: !known_items.is_empty(), known_items: Some(known_items), params: None, shape_name: None, - })), + }), _ => panic!(), } } else { None } } - aast::Expr_::Null => Some(get_null()), - aast::Expr_::True => Some(get_true()), - aast::Expr_::False => Some(get_false()), - aast::Expr_::Int(value) => Some(get_literal_int(int_from_string(value).unwrap())), - aast::Expr_::Float(_) => Some(get_float()), + aast::Expr_::Null => Some(TAtomic::TNull), + aast::Expr_::True => Some(TAtomic::TTrue), + aast::Expr_::False => Some(TAtomic::TFalse), + aast::Expr_::Int(value) => Some(TAtomic::TLiteralInt { + value: int_from_string(value).unwrap(), + }), + aast::Expr_::Float(_) => Some(TAtomic::TFloat), aast::Expr_::String(value) => Some(if value.len() < 200 { - get_literal_string(value.to_string()) + TAtomic::TLiteralString { + value: value.to_string(), + } } else { - wrap_atomic(TAtomic::TStringWithFlags(true, false, true)) + TAtomic::TStringWithFlags(true, false, true) }), aast::Expr_::Tuple(values) => { let mut entries = BTreeMap::new(); @@ -145,22 +144,22 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) let entry_type = infer(entry_expr, resolved_names); if let Some(entry_type) = entry_type { - entries.insert(i, (false, entry_type)); + entries.insert(i, (false, wrap_atomic(entry_type))); } else { return None; } } - Some(wrap_atomic(TAtomic::TVec { + Some(TAtomic::TVec { known_count: Some(entries.len()), known_items: Some(entries), type_param: Box::new(get_nothing()), non_empty: true, - })) + }) } aast::Expr_::Binop(boxed) => { if let ast_defs::Bop::Dot = boxed.bop { - Some(wrap_atomic(TAtomic::TStringWithFlags(true, false, true))) + Some(TAtomic::TStringWithFlags(true, false, true)) } else { None } @@ -170,22 +169,16 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) let number_type = infer(&boxed.1, resolved_names); if let Some(number_type) = number_type { - if number_type.is_single() { - let first = &number_type.types[0]; - - if let TAtomic::TLiteralInt { value, .. } = first { - Some(get_literal_int(-value)) - } else { - Some(number_type) - } + if let TAtomic::TLiteralInt { value, .. } = number_type { + Some(TAtomic::TLiteralInt { value: -value }) } else { - None + Some(number_type) } } else { None } } else if let ast_defs::Uop::Utild = boxed.0 { - Some(get_int()) + Some(TAtomic::TInt) } else { panic!() } @@ -193,9 +186,9 @@ pub fn infer(expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap) aast::Expr_::Id(name) => { if let Some(name_string) = resolved_names.get(&(name.0.start_offset() as u32)) { if *name_string == StrId::LIB_MATH_INT32_MAX { - return Some(wrap_atomic(TAtomic::TLiteralInt { + return Some(TAtomic::TLiteralInt { value: i32::MAX as i64, - })); + }); } } diff --git a/src/file_scanner_analyzer/populator.rs b/src/file_scanner_analyzer/populator.rs index dedb682f..42d1d996 100644 --- a/src/file_scanner_analyzer/populator.rs +++ b/src/file_scanner_analyzer/populator.rs @@ -112,6 +112,16 @@ pub fn populate_codebase( userland_force_repopulation, ); } + + if let Some(inferred_type) = constant.inferred_type.as_mut() { + populate_atomic_type( + inferred_type, + &codebase.symbols, + &ReferenceSource::Symbol(true, *name), + symbol_references, + userland_force_repopulation, + ); + } } for (_, type_constant_info) in storage.type_constants.iter_mut() { @@ -184,6 +194,16 @@ pub fn populate_codebase( !safe_symbols.contains(name), ); } + + if let Some(inferred_type) = constant.inferred_type.as_mut() { + populate_atomic_type( + inferred_type, + &codebase.symbols, + &ReferenceSource::Symbol(true, *name), + symbol_references, + !safe_symbols.contains(name), + ); + } } let mut direct_classlike_descendants = FxHashMap::default(); diff --git a/src/ttype/lib.rs b/src/ttype/lib.rs index 7aeb760a..23b82e99 100644 --- a/src/ttype/lib.rs +++ b/src/ttype/lib.rs @@ -656,6 +656,7 @@ pub fn get_atomic_syntax_type( "_".to_string() } TAtomic::TEnumLiteralCase { enum_name, .. } => interner.lookup(enum_name).to_string(), + TAtomic::TMemberReference { classlike_name, .. } => interner.lookup(classlike_name).to_string(), TAtomic::TLiteralInt { .. } => "int".to_string(), TAtomic::TLiteralString { .. } | TAtomic::TStringWithFlags(..) => "string".to_string(), TAtomic::TMixed | TAtomic::TMixedFromLoopIsset => "mixed".to_string(), diff --git a/src/ttype/type_comparator/atomic_type_comparator.rs b/src/ttype/type_comparator/atomic_type_comparator.rs index c47c5f1d..070dd763 100644 --- a/src/ttype/type_comparator/atomic_type_comparator.rs +++ b/src/ttype/type_comparator/atomic_type_comparator.rs @@ -1,8 +1,14 @@ use std::{collections::BTreeMap, sync::Arc}; -use crate::{get_arrayish_params, get_value_param, wrap_atomic}; +use crate::{ + get_arrayish_params, get_value_param, + type_expander::{expand_union, TypeExpansionOptions}, + wrap_atomic, +}; use hakana_reflection_info::{ + class_constant_info::ConstantInfo, codebase_info::CodebaseInfo, + data_flow::graph::DataFlowGraph, t_atomic::{DictKey, TAtomic}, t_union::TUnion, }; @@ -920,6 +926,141 @@ pub(crate) fn can_be_identical<'a>( } } + if let ( + TAtomic::TEnum { + name: enum_1_name, .. + }, + TAtomic::TEnum { + name: enum_2_name, .. + }, + ) = (type1_part, type2_part) + { + if enum_1_name == enum_2_name { + return true; + } + + if let (Some(enum_1_info), Some(enum_2_info)) = ( + codebase.classlike_infos.get(enum_1_name), + codebase.classlike_infos.get(enum_2_name), + ) { + let enum_1_members = enum_1_info + .constants + .iter() + .map(|(_, v)| expand_constant_value(v, codebase)) + .collect::>(); + + let enum_2_members = enum_2_info + .constants + .iter() + .map(|(_, v)| expand_constant_value(v, codebase)) + .collect::>(); + + for enum_1_member in &enum_1_members { + for enum_2_member in &enum_2_members { + if enum_1_member == enum_2_member { + return true; + } + } + } + + return false; + } + } + + if let ( + TAtomic::TEnumLiteralCase { + enum_name: enum_1_name, + member_name: enum_1_member_name, + .. + }, + TAtomic::TEnumLiteralCase { + enum_name: enum_2_name, + member_name: enum_2_member_name, + .. + }, + ) = (type1_part, type2_part) + { + if enum_1_name == enum_2_name && enum_1_member_name == enum_2_member_name { + return true; + } + } + + if let ( + TAtomic::TEnum { + name: enum_1_name, .. + }, + TAtomic::TEnumLiteralCase { + enum_name: enum_2_name, + member_name: enum_2_member_name, + .. + }, + ) = (type1_part, type2_part) + { + if enum_1_name == enum_2_name { + return true; + } + + if let (Some(enum_1_info), Some(enum_2_info)) = ( + codebase.classlike_infos.get(enum_1_name), + codebase.classlike_infos.get(enum_2_name), + ) { + let enum_1_members = enum_1_info + .constants + .iter() + .map(|(_, v)| expand_constant_value(v, codebase)) + .collect::>(); + + if let Some(enum_2_const_info) = enum_2_info.constants.get(enum_2_member_name) { + let enum_2_member = expand_constant_value(enum_2_const_info, codebase); + + for enum_1_member in enum_1_members { + if enum_1_member == enum_2_member { + return true; + } + } + } + + return false; + } + } else if let ( + TAtomic::TEnum { + name: enum_2_name, .. + }, + TAtomic::TEnumLiteralCase { + enum_name: enum_1_name, + member_name: enum_1_member_name, + .. + }, + ) = (type1_part, type2_part) + { + if enum_1_name == enum_2_name { + return true; + } + + if let (Some(enum_1_info), Some(enum_2_info)) = ( + codebase.classlike_infos.get(enum_1_name), + codebase.classlike_infos.get(enum_2_name), + ) { + let enum_2_members = enum_2_info + .constants + .iter() + .map(|(_, v)| expand_constant_value(v, codebase)) + .collect::>(); + + if let Some(enum_1_const_info) = enum_1_info.constants.get(enum_1_member_name) { + let enum_1_member = expand_constant_value(enum_1_const_info, codebase); + + for enum_2_member in enum_2_members { + if enum_1_member == enum_2_member { + return true; + } + } + } + + return false; + } + } + if (type1_part.is_vec() && type2_part.is_non_empty_vec()) || (type2_part.is_vec() && type1_part.is_non_empty_vec()) { @@ -977,6 +1118,28 @@ pub(crate) fn can_be_identical<'a>( && second_comparison_result.type_coerced.unwrap_or(false)) } +pub fn expand_constant_value(v: &ConstantInfo, codebase: &CodebaseInfo) -> TAtomic { + if let Some(TAtomic::TEnumLiteralCase { + enum_name, + member_name, + .. + }) = &v.inferred_type + { + if let Some(classlike_info) = codebase.classlike_infos.get(enum_name) { + if let Some(constant_info) = classlike_info.constants.get(member_name) { + return expand_constant_value(constant_info, codebase); + } + } + } + + v.inferred_type.clone().unwrap_or( + v.provided_type + .clone() + .map(|t| t.get_single_owned()) + .unwrap_or(TAtomic::TArraykey { from_any: true }), + ) +} + fn dicts_can_be_identical( type_1_dict_params: &Option<(Box, Box)>, type_2_dict_params: &Option<(Box, Box)>, diff --git a/src/ttype/type_comparator/scalar_type_comparator.rs b/src/ttype/type_comparator/scalar_type_comparator.rs index a61af73a..fca50d08 100644 --- a/src/ttype/type_comparator/scalar_type_comparator.rs +++ b/src/ttype/type_comparator/scalar_type_comparator.rs @@ -158,10 +158,11 @@ pub fn is_contained_by( if let Some(c) = codebase.classlike_infos.get(container_name) { for (_, const_storage) in &c.constants { if let Some(inferred_enum_type) = &const_storage.inferred_type { - if let Some(inferred_value) = - inferred_enum_type.get_single_literal_string_value() + if let TAtomic::TLiteralString { + value: inferred_value, + } = inferred_enum_type { - if &inferred_value == input_value { + if inferred_value == input_value { return true; } } @@ -197,6 +198,24 @@ pub fn is_contained_by( ); } + if let ( + TAtomic::TEnumLiteralCase { + enum_name: enum_1_name, + member_name: enum_1_member_name, + .. + }, + TAtomic::TEnumLiteralCase { + enum_name: enum_2_name, + member_name: enum_2_member_name, + .. + }, + ) = (input_type_part, container_type_part) + { + if enum_1_name == enum_2_name && enum_1_member_name == enum_2_member_name { + return true; + } + } + // handles newtypes (hopefully) if let TAtomic::TEnumLiteralCase { constraint_type, .. @@ -231,10 +250,11 @@ pub fn is_contained_by( if let Some(inferred_enum_type) = &c.constants.get(member_name).unwrap().inferred_type { - if let Some(inferred_value) = - inferred_enum_type.get_single_literal_string_value() + if let TAtomic::TLiteralString { + value: inferred_value, + } = inferred_enum_type { - if &inferred_value == input_value { + if inferred_value == input_value { return true; } } diff --git a/src/ttype/type_expander.rs b/src/ttype/type_expander.rs index a80aef82..f76ac6d0 100644 --- a/src/ttype/type_expander.rs +++ b/src/ttype/type_expander.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{mem, sync::Arc}; use hakana_reflection_info::{ classlike_info::ClassConstantType, @@ -20,7 +20,7 @@ use hakana_reflection_info::{ use hakana_str::{Interner, StrId}; use indexmap::IndexMap; use itertools::Itertools; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::{extend_dataflow_uniquely, get_nothing, template, type_combiner, wrap_atomic}; @@ -271,10 +271,17 @@ fn expand_atomic( return; } else if let TAtomic::TEnumLiteralCase { + ref enum_name, ref mut constraint_type, .. } = return_type_part { + if let None = constraint_type { + if let Some(classlike_storage) = codebase.classlike_infos.get(enum_name) { + *constraint_type = classlike_storage.enum_constraint.clone(); + } + } + if let Some(constraint_type) = constraint_type { let mut constraint_union = wrap_atomic((**constraint_type).clone()); expand_union( @@ -310,6 +317,46 @@ fn expand_atomic( } } + return; + } else if let TAtomic::TMemberReference { + ref classlike_name, + ref member_name, + } = return_type_part + { + *skip_key = true; + + if let Some(literal_value) = + codebase.get_classconst_literal_value(classlike_name, member_name) + { + let mut literal_value = literal_value.clone(); + + expand_atomic( + &mut literal_value, + codebase, + interner, + options, + data_flow_graph, + skip_key, + new_return_type_parts, + extra_data_flow_nodes, + ); + + new_return_type_parts.push(literal_value); + } else { + let const_type = codebase.get_class_constant_type( + classlike_name, + false, + member_name, + FxHashSet::default(), + ); + + if let Some(const_type) = const_type { + new_return_type_parts.extend(const_type.types); + } else { + new_return_type_parts.push(TAtomic::TMixed); + } + } + return; } else if let TAtomic::TTypeAlias { name: type_name,