diff --git a/src/analyzer/expr/binop/and_analyzer.rs b/src/analyzer/expr/binop/and_analyzer.rs index e0cae23d..a9966fe5 100644 --- a/src/analyzer/expr/binop/and_analyzer.rs +++ b/src/analyzer/expr/binop/and_analyzer.rs @@ -2,7 +2,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::rc::Rc; use crate::function_analysis_data::FunctionAnalysisData; -use crate::reconciler::reconciler; +use crate::reconciler; use crate::scope_context::ScopeContext; use crate::statements_analyzer::StatementsAnalyzer; use crate::stmt::if_conditional_analyzer::handle_paradoxical_condition; diff --git a/src/analyzer/expr/binop/assignment_analyzer.rs b/src/analyzer/expr/binop/assignment_analyzer.rs index df8d5972..32b3d19f 100644 --- a/src/analyzer/expr/binop/assignment_analyzer.rs +++ b/src/analyzer/expr/binop/assignment_analyzer.rs @@ -270,11 +270,7 @@ pub(crate) fn analyze( analysis_data, ); } else { - let root_var_id = get_root_var_id( - assign_var, - context.function_context.calling_class.as_ref(), - Some(statements_analyzer.get_file_analyzer().get_file_source()), - ); + let root_var_id = get_root_var_id(assign_var); if let Some(root_var_id) = root_var_id { if let Some(existing_root_type) = context.vars_in_scope.get(&root_var_id).cloned() { @@ -288,12 +284,6 @@ pub(crate) fn analyze( } } - if assign_value_type.is_mixed() { - // we don't really need to know about MixedAssignment, but in Psalm we trigger an issue here - } else { - // todo increment non-mixed count - } - analysis_data.expr_effects.insert( (pos.start_offset() as u32, pos.end_offset() as u32), EFFECT_WRITE_LOCAL, diff --git a/src/analyzer/expr/binop/or_analyzer.rs b/src/analyzer/expr/binop/or_analyzer.rs index 69d47d05..cb1100e1 100644 --- a/src/analyzer/expr/binop/or_analyzer.rs +++ b/src/analyzer/expr/binop/or_analyzer.rs @@ -1,7 +1,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::rc::Rc; -use crate::reconciler::reconciler; +use crate::reconciler; use crate::scope_analyzer::ScopeAnalyzer; use crate::scope_context::ScopeContext; use crate::statements_analyzer::StatementsAnalyzer; diff --git a/src/analyzer/expr/call/function_call_analyzer.rs b/src/analyzer/expr/call/function_call_analyzer.rs index 2e7a6f1c..52a8b8b6 100644 --- a/src/analyzer/expr/call/function_call_analyzer.rs +++ b/src/analyzer/expr/call/function_call_analyzer.rs @@ -12,7 +12,7 @@ use crate::expr::call::arguments_analyzer; use crate::expr::call_analyzer::{apply_effects, check_template_result}; use crate::expr::{echo_analyzer, exit_analyzer, expression_identifier, isset_analyzer}; use crate::function_analysis_data::FunctionAnalysisData; -use crate::reconciler::reconciler; +use crate::reconciler; use crate::scope_analyzer::ScopeAnalyzer; use crate::scope_context::ScopeContext; use crate::statements_analyzer::StatementsAnalyzer; diff --git a/src/analyzer/expr/expression_identifier.rs b/src/analyzer/expr/expression_identifier.rs index ef3caff0..cc9b2e8e 100644 --- a/src/analyzer/expr/expression_identifier.rs +++ b/src/analyzer/expr/expression_identifier.rs @@ -4,7 +4,6 @@ use hakana_reflection_info::{ }; use rustc_hash::{FxHashMap, FxHashSet}; -use hakana_reflection_info::FileSource; use oxidized::{aast, ast_defs}; // gets a var id from a simple variable @@ -97,15 +96,11 @@ pub fn get_var_id( } // gets a the beginning var id from a chain -pub(crate) fn get_root_var_id( - conditional: &aast::Expr<(), ()>, - this_class_name: Option<&StrId>, - source: Option<&FileSource>, -) -> Option { +pub(crate) fn get_root_var_id(conditional: &aast::Expr<(), ()>) -> Option { match &conditional.2 { aast::Expr_::Lvar(var_expr) => Some(var_expr.1 .1.clone()), - aast::Expr_::ArrayGet(boxed) => get_root_var_id(&boxed.0, this_class_name, source), - aast::Expr_::ObjGet(boxed) => get_root_var_id(&boxed.0, this_class_name, source), + aast::Expr_::ArrayGet(boxed) => get_root_var_id(&boxed.0), + aast::Expr_::ObjGet(boxed) => get_root_var_id(&boxed.0), _ => None, } } diff --git a/src/analyzer/expr/ternary_analyzer.rs b/src/analyzer/expr/ternary_analyzer.rs index 228936e7..0121497f 100644 --- a/src/analyzer/expr/ternary_analyzer.rs +++ b/src/analyzer/expr/ternary_analyzer.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::rc::Rc; use crate::function_analysis_data::FunctionAnalysisData; -use crate::reconciler::{assertion_reconciler, reconciler}; +use crate::reconciler::{self, assertion_reconciler}; use crate::scope_analyzer::ScopeAnalyzer; use crate::scope_context::if_scope::IfScope; use crate::scope_context::{var_has_root, ScopeContext}; @@ -92,9 +92,7 @@ pub(crate) fn analyze( if_clauses = if_clauses .into_iter() .map(|c| { - let keys = &c - .possibilities.keys() - .collect::>(); + let keys = &c.possibilities.keys().collect::>(); let mut new_mixed_var_ids = vec![]; for i in mixed_var_ids.clone() { @@ -340,10 +338,12 @@ pub(crate) fn analyze( let mut removed_vars = FxHashSet::default(); let redef_var_ifs = if_context - .get_redefined_vars(&context.vars_in_scope, false, &mut removed_vars).into_keys() + .get_redefined_vars(&context.vars_in_scope, false, &mut removed_vars) + .into_keys() .collect::>(); let redef_var_else = temp_else_context - .get_redefined_vars(&context.vars_in_scope, false, &mut removed_vars).into_keys() + .get_redefined_vars(&context.vars_in_scope, false, &mut removed_vars) + .into_keys() .collect::>(); let redef_all = redef_var_ifs diff --git a/src/analyzer/expression_analyzer.rs b/src/analyzer/expression_analyzer.rs index 9d8fd17b..d524acbb 100644 --- a/src/analyzer/expression_analyzer.rs +++ b/src/analyzer/expression_analyzer.rs @@ -15,7 +15,7 @@ use crate::expr::{ unop_analyzer, variable_fetch_analyzer, xml_analyzer, yield_analyzer, }; use crate::function_analysis_data::FunctionAnalysisData; -use crate::reconciler::reconciler; +use crate::reconciler; use crate::scope_analyzer::ScopeAnalyzer; use crate::scope_context::{var_has_root, ScopeContext}; use crate::statements_analyzer::StatementsAnalyzer; diff --git a/src/analyzer/reconciler/assertion_reconciler.rs b/src/analyzer/reconciler/assertion_reconciler.rs index e7a1ae89..a7d15f48 100644 --- a/src/analyzer/reconciler/assertion_reconciler.rs +++ b/src/analyzer/reconciler/assertion_reconciler.rs @@ -1,8 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; use super::{ - negated_assertion_reconciler, reconciler::trigger_issue_for_impossible, - simple_assertion_reconciler, + negated_assertion_reconciler, simple_assertion_reconciler, trigger_issue_for_impossible, }; use crate::{ function_analysis_data::FunctionAnalysisData, scope_analyzer::ScopeAnalyzer, diff --git a/src/analyzer/reconciler/mod.rs b/src/analyzer/reconciler/mod.rs index f4667f02..6e9113d8 100644 --- a/src/analyzer/reconciler/mod.rs +++ b/src/analyzer/reconciler/mod.rs @@ -1,6 +1,1277 @@ pub mod assertion_reconciler; pub mod macros; pub mod negated_assertion_reconciler; -pub mod reconciler; pub mod simple_assertion_reconciler; pub mod simple_negated_assertion_reconciler; + +use crate::{ + function_analysis_data::FunctionAnalysisData, + scope_analyzer::ScopeAnalyzer, + scope_context::{var_has_root, ScopeContext}, + statements_analyzer::StatementsAnalyzer, +}; +use hakana_reflection_info::{ + assertion::Assertion, + codebase_info::CodebaseInfo, + data_flow::{graph::GraphKind, node::DataFlowNode, path::PathKind}, + functionlike_identifier::FunctionLikeIdentifier, + issue::{Issue, IssueKind}, + t_atomic::{DictKey, TAtomic}, + t_union::TUnion, + Interner, StrId, +}; +use hakana_type::{ + add_union_type, get_mixed_any, get_null, get_value_param, + type_expander::{self, StaticClassType, TypeExpansionOptions}, + wrap_atomic, +}; +use lazy_static::lazy_static; +use oxidized::ast_defs::Pos; +use regex::Regex; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::{collections::BTreeMap, rc::Rc, sync::Arc}; + +pub(crate) fn reconcile_keyed_types( + new_types: &BTreeMap>>, + // types we can complain about + mut active_new_types: BTreeMap>, + context: &mut ScopeContext, + changed_var_ids: &mut FxHashSet, + referenced_var_ids: &FxHashSet, + statements_analyzer: &StatementsAnalyzer, + analysis_data: &mut FunctionAnalysisData, + pos: &Pos, + can_report_issues: bool, + negated: bool, + suppressed_issues: &FxHashMap, +) { + if new_types.is_empty() { + return; + } + + let inside_loop = context.inside_loop; + + let old_new_types = new_types.clone(); + + let mut new_types = new_types.clone(); + + add_nested_assertions(&mut new_types, &mut active_new_types, context); + + let codebase = statements_analyzer.get_codebase(); + + // we want to remove any + let mut added_var_ids = FxHashSet::default(); + + for (key, new_type_parts) in &new_types { + if key.contains("::") && !key.contains('$') && !key.contains('[') { + continue; + } + + let mut has_negation = false; + let mut has_isset = false; + let mut has_inverted_isset = false; + let mut has_falsyish = false; + let mut has_count_check = false; + let is_real = old_new_types + .get(key) + .unwrap_or(&Vec::new()) + .eq(new_type_parts); + + let mut is_equality = false; + + for new_type_part_parts in new_type_parts { + for assertion in new_type_part_parts { + if key == "hakana taints" { + match assertion { + Assertion::RemoveTaints(key, taints) => { + if let Some(existing_var_type) = context.vars_in_scope.get_mut(key) { + let new_parent_node = DataFlowNode::get_for_assignment( + key.clone(), + statements_analyzer.get_hpos(pos), + ); + + for old_parent_node in &existing_var_type.parent_nodes { + analysis_data.data_flow_graph.add_path( + old_parent_node, + &new_parent_node, + PathKind::Default, + None, + Some(taints.clone()), + ); + } + + let mut existing_var_type_inner = (**existing_var_type).clone(); + + existing_var_type_inner.parent_nodes = + FxHashSet::from_iter([new_parent_node.clone()]); + + *existing_var_type = Rc::new(existing_var_type_inner); + + analysis_data.data_flow_graph.add_node(new_parent_node); + } + } + Assertion::IgnoreTaints => { + context.allow_taints = false; + } + Assertion::DontIgnoreTaints => { + context.allow_taints = true; + } + _ => (), + } + + continue; + } + + if assertion.has_negation() { + has_negation = true; + } + + has_isset = has_isset || assertion.has_isset(); + + has_falsyish = has_falsyish || matches!(assertion, Assertion::Falsy); + + is_equality = is_equality || assertion.has_non_isset_equality(); + + has_inverted_isset = + has_inverted_isset || matches!(assertion, Assertion::IsNotIsset); + + has_count_check = + has_count_check || matches!(assertion, Assertion::NonEmptyCountable(_)); + } + } + + let did_type_exist = context.vars_in_scope.contains_key(key); + + let mut possibly_undefined = false; + + let mut result_type = if let Some(existing_type) = context.vars_in_scope.get(key) { + Some((**existing_type).clone()) + } else { + get_value_for_key( + codebase, + statements_analyzer.get_interner(), + key.clone(), + context, + &mut added_var_ids, + &new_types, + has_isset, + has_inverted_isset, + inside_loop, + &mut possibly_undefined, + analysis_data, + ) + }; + + if let Some(maybe_result_type) = &result_type { + if maybe_result_type.types.is_empty() { + panic!(); + } + } + + let before_adjustment = result_type.clone(); + + for (i, new_type_part_parts) in new_type_parts.iter().enumerate() { + let mut orred_type: Option = None; + + for assertion in new_type_part_parts { + let mut result_type_candidate = assertion_reconciler::reconcile( + assertion, + result_type.as_ref(), + possibly_undefined, + Some(key), + statements_analyzer, + analysis_data, + inside_loop, + Some(pos), + &context.function_context.calling_functionlike_id, + can_report_issues + && if referenced_var_ids.contains(key) && active_new_types.contains_key(key) + { + active_new_types.get(key).unwrap().get(&i).is_some() + } else { + false + }, + negated, + suppressed_issues, + ); + + if result_type_candidate.types.is_empty() { + result_type_candidate.types.push(TAtomic::TNothing); + } + + orred_type = if let Some(orred_type) = orred_type { + Some(add_union_type( + result_type_candidate, + &orred_type, + codebase, + false, + )) + } else { + Some(result_type_candidate.clone()) + }; + } + + result_type = orred_type; + } + + let mut result_type = result_type.unwrap(); + + if !did_type_exist && result_type.is_nothing() { + continue; + } + + let type_changed = if let Some(before_adjustment) = &before_adjustment { + &result_type != before_adjustment + } else { + true + }; + + if let Some(before_adjustment) = &before_adjustment { + if let GraphKind::WholeProgram(_) = &analysis_data.data_flow_graph.kind { + let mut has_scalar_restriction = false; + + for new_type_part_parts in new_type_parts { + if new_type_part_parts.len() == 1 { + let assertion = &new_type_part_parts[0]; + + if let Assertion::IsType(t) | Assertion::IsEqual(t) = assertion { + if t.is_some_scalar() { + has_scalar_restriction = true; + } + } + } + } + + if has_scalar_restriction { + let scalar_check_node = DataFlowNode::get_for_assignment( + key.clone(), + statements_analyzer.get_hpos(pos), + ); + + for parent_node in &before_adjustment.parent_nodes { + analysis_data.data_flow_graph.add_path( + parent_node, + &scalar_check_node, + PathKind::ScalarTypeGuard, + None, + None, + ); + } + + result_type.parent_nodes = FxHashSet::from_iter([scalar_check_node.clone()]); + + analysis_data.data_flow_graph.add_node(scalar_check_node); + } else { + let narrowed_symbol = if type_changed { + if result_type.is_single() { + if let TAtomic::TNamedObject { name, .. } = result_type.get_single() { + Some(name) + } else { + None + } + } else { + None + } + } else { + None + }; + if let Some(narrowed_symbol) = narrowed_symbol { + let narrowing_node = DataFlowNode::get_for_assignment( + key.clone() + + " narrowed to " + + statements_analyzer.get_interner().lookup(narrowed_symbol), + statements_analyzer.get_hpos(pos), + ); + + for parent_node in &before_adjustment.parent_nodes { + analysis_data.data_flow_graph.add_path( + parent_node, + &narrowing_node, + PathKind::RefineSymbol(*narrowed_symbol), + None, + None, + ); + } + + result_type.parent_nodes = FxHashSet::from_iter([narrowing_node.clone()]); + + analysis_data.data_flow_graph.add_node(narrowing_node); + } else { + result_type.parent_nodes = before_adjustment.parent_nodes.clone(); + } + } + } else { + result_type.parent_nodes = before_adjustment.parent_nodes.clone(); + } + } + + if key.ends_with(']') + && (type_changed || !did_type_exist) + && !has_inverted_isset + && !is_equality + { + let key_parts = break_up_path_into_parts(key); + + adjust_array_type(key_parts, context, changed_var_ids, &result_type); + } + + if type_changed { + changed_var_ids.insert(key.clone()); + + if key != "$this" && !key.ends_with(']') { + let mut removable_keys = Vec::new(); + for (new_key, _) in context.vars_in_scope.iter() { + if new_key.eq(key) { + continue; + } + + if is_real && !new_types.contains_key(new_key) && var_has_root(new_key, key) { + removable_keys.push(new_key.clone()); + } + } + + for new_key in removable_keys { + context.vars_in_scope.remove(&new_key); + } + } + } else if !has_negation && !has_falsyish && !has_isset { + changed_var_ids.insert(key.clone()); + } + + context + .vars_in_scope + .insert(key.clone(), Rc::new(result_type)); + } + + context + .vars_in_scope + .retain(|var_id, _| !added_var_ids.contains(var_id)); +} + +fn adjust_array_type( + mut key_parts: Vec, + context: &mut ScopeContext, + changed_var_ids: &mut FxHashSet, + result_type: &TUnion, +) { + key_parts.pop(); + let array_key = key_parts.pop().unwrap(); + key_parts.pop(); + + if array_key.starts_with('$') { + return; + } + + let mut has_string_offset = false; + + let arraykey_offset = if array_key.starts_with('\'') || array_key.starts_with('\"') { + has_string_offset = true; + array_key[1..(array_key.len() - 1)].to_string() + } else { + array_key.clone() + }; + + let base_key = key_parts.join(""); + + let mut existing_type = if let Some(existing_type) = context.vars_in_scope.get(&base_key) { + (**existing_type).clone() + } else { + return; + }; + + for base_atomic_type in existing_type.types.iter_mut() { + if let TAtomic::TTypeAlias { + as_type: Some(as_type), + .. + } = base_atomic_type + { + *base_atomic_type = as_type.get_single().clone(); + } + + match base_atomic_type { + TAtomic::TDict { + ref mut known_items, + .. + } => { + let dictkey = if has_string_offset { + DictKey::String(arraykey_offset.clone()) + } else if let Ok(arraykey_value) = arraykey_offset.parse::() { + DictKey::Int(arraykey_value) + } else { + println!("bad int key {}", arraykey_offset); + continue; + }; + + if let Some(known_items) = known_items { + known_items.insert(dictkey, (false, Arc::new(result_type.clone()))); + } else { + *known_items = Some(BTreeMap::from([( + dictkey, + (false, Arc::new(result_type.clone())), + )])); + } + } + TAtomic::TVec { + ref mut known_items, + .. + } => { + if let Ok(arraykey_offset) = arraykey_offset.parse::() { + if let Some(known_items) = known_items { + known_items.insert(arraykey_offset, (false, result_type.clone())); + } else { + *known_items = Some(BTreeMap::from([( + arraykey_offset, + (false, result_type.clone()), + )])); + } + } + } + _ => { + continue; + } + } + + changed_var_ids.insert(format!("{}[{}]", base_key, array_key.clone())); + + if let Some(last_part) = key_parts.last() { + if last_part == "]" { + adjust_array_type( + key_parts.clone(), + context, + changed_var_ids, + &wrap_atomic(base_atomic_type.clone()), + ); + } + } + } + + context + .vars_in_scope + .insert(base_key, Rc::new(existing_type)); +} + +fn add_nested_assertions( + new_types: &mut BTreeMap>>, + active_new_types: &mut BTreeMap>, + context: &mut ScopeContext, +) { + lazy_static! { + static ref INTEGER_REGEX: Regex = Regex::new("^[0-9]+$").unwrap(); + } + + let mut keys_to_remove = vec![]; + + 'outer: for (nk, new_type) in new_types.clone() { + if (nk.contains('[') || nk.contains("->")) + && (new_type[0][0] == Assertion::IsEqualIsset || new_type[0][0] == Assertion::IsIsset) + { + let mut key_parts = break_up_path_into_parts(&nk); + key_parts.reverse(); + + let mut nesting = 0; + + let mut base_key = key_parts.pop().unwrap(); + + if !&base_key.starts_with('$') + && key_parts.len() > 2 + && key_parts.last().unwrap() == "::$" + { + base_key += key_parts.pop().unwrap().as_str(); + base_key += key_parts.pop().unwrap().as_str(); + } + + let base_key_set = if let Some(base_key_type) = context.vars_in_scope.get(&base_key) { + !base_key_type.is_nullable() + } else { + false + }; + + if !base_key_set { + if !new_types.contains_key(&base_key) { + new_types.insert(base_key.clone(), vec![vec![Assertion::IsEqualIsset]]); + } else { + let mut existing_entry = new_types.get(&base_key).unwrap().clone(); + existing_entry.push(vec![Assertion::IsEqualIsset]); + new_types.insert(base_key.clone(), existing_entry); + } + } + + while let Some(divider) = key_parts.pop() { + if divider == "[" { + let array_key = key_parts.pop().unwrap(); + key_parts.pop(); + + let new_base_key = base_key.clone() + "[" + array_key.as_str() + "]"; + + let entry = new_types.entry(base_key.clone()).or_default(); + + let new_key = if array_key.starts_with('\'') { + Some(DictKey::String( + array_key[1..(array_key.len() - 1)].to_string(), + )) + } else if array_key.starts_with('$') { + None + } else if let Ok(arraykey_value) = array_key.parse::() { + Some(DictKey::Int(arraykey_value)) + } else { + println!("bad int key {}", array_key); + panic!() + }; + + if let Some(new_key) = new_key { + entry.push(vec![Assertion::HasNonnullEntryForKey(new_key)]); + + if key_parts.is_empty() { + keys_to_remove.push(nk.clone()); + + if nesting == 0 + && base_key_set + && active_new_types.remove(&nk).is_some() + { + active_new_types + .entry(base_key.clone()) + .or_default() + .insert(entry.len() - 1); + } + + break 'outer; + } + } else { + entry.push(vec![if array_key.contains('\'') { + Assertion::HasStringArrayAccess + } else { + Assertion::HasIntOrStringArrayAccess + }]); + } + + base_key = new_base_key; + nesting += 1; + continue; + } + + if divider == "->" { + let property_name = key_parts.pop().unwrap(); + + let new_base_key = base_key.clone() + "->" + property_name.as_str(); + + if !new_types.contains_key(&base_key) { + new_types.insert(base_key.clone(), vec![vec![Assertion::IsIsset]]); + } + + base_key = new_base_key; + } else { + break; + } + + if key_parts.is_empty() { + break; + } + } + } + } + + new_types.retain(|k, _| !keys_to_remove.contains(k)); +} + +fn break_up_path_into_parts(path: &str) -> Vec { + let chars: Vec = path.chars().collect(); + + let mut string_char: Option = None; + + let mut escape_char = false; + let mut brackets = 0; + + let mut parts = BTreeMap::new(); + parts.insert(0, "".to_string()); + let mut parts_offset = 0; + + let mut i = 0; + let char_count = chars.len(); + + while i < char_count { + let ichar = *chars.get(i).unwrap(); + + if let Some(string_char_inner) = string_char { + if ichar == string_char_inner && !escape_char { + string_char = None; + } + + if ichar == '\\' { + escape_char = !escape_char; + } + + parts.insert( + parts_offset, + parts.get(&parts_offset).unwrap().clone() + ichar.to_string().as_str(), + ); + + i += 1; + continue; + } + + match ichar { + '[' | ']' => { + parts_offset += 1; + parts.insert(parts_offset, ichar.to_string()); + parts_offset += 1; + + brackets += if ichar == '[' { 1 } else { -1 }; + + i += 1; + continue; + } + + '\'' | '"' => { + parts.entry(parts_offset).or_insert_with(|| "".to_string()); + parts.insert( + parts_offset, + parts.get(&parts_offset).unwrap().clone() + ichar.to_string().as_str(), + ); + string_char = Some(ichar); + + i += 1; + continue; + } + + ':' => { + if brackets == 0 + && i < char_count - 2 + && *chars.get(i + 1).unwrap() == ':' + && *chars.get(i + 2).unwrap() == '$' + { + parts_offset += 1; + parts.insert(parts_offset, "::$".to_string()); + parts_offset += 1; + + i += 3; + continue; + } + } + + '-' => { + if brackets == 0 && i < char_count - 1 && *chars.get(i + 1).unwrap() == '>' { + parts_offset += 1; + parts.insert(parts_offset, "->".to_string()); + parts_offset += 1; + + i += 2; + continue; + } + } + + _ => {} + } + + parts.entry(parts_offset).or_insert_with(|| "".to_string()); + + parts.insert( + parts_offset, + parts.get(&parts_offset).unwrap().clone() + ichar.to_string().as_str(), + ); + + i += 1; + } + + parts.values().cloned().collect() +} + +fn get_value_for_key( + codebase: &CodebaseInfo, + interner: &Interner, + key: String, + context: &mut ScopeContext, + added_var_ids: &mut FxHashSet, + new_assertions: &BTreeMap>>, + has_isset: bool, + has_inverted_isset: bool, + inside_loop: bool, + possibly_undefined: &mut bool, + analysis_data: &mut FunctionAnalysisData, +) -> Option { + lazy_static! { + static ref INTEGER_REGEX: Regex = Regex::new("^[0-9]+$").unwrap(); + } + + let mut key_parts = break_up_path_into_parts(&key); + + if key_parts.len() == 1 { + if let Some(t) = context.vars_in_scope.get(&key) { + return Some((**t).clone()); + } + + return None; + } + + key_parts.reverse(); + + let mut base_key = key_parts.pop().unwrap(); + + if !base_key.starts_with('$') + && key_parts.len() > 2 + && key_parts.last().unwrap().starts_with("::$") + { + base_key += key_parts.pop().unwrap().as_str(); + base_key += key_parts.pop().unwrap().as_str(); + } + + if !context.vars_in_scope.contains_key(&base_key) { + if base_key.contains("::") { + let base_key_parts = &base_key.split("::").collect::>(); + let fq_class_name = base_key_parts[0].to_string(); + let const_name = base_key_parts[1].to_string(); + + let fq_class_name = &interner.get(fq_class_name.as_str()).unwrap(); + + if !codebase.class_or_interface_exists(fq_class_name) { + return None; + } + + let class_constant = if let Some(const_name) = interner.get(&const_name) { + codebase.get_class_constant_type( + fq_class_name, + false, + &const_name, + FxHashSet::default(), + ) + } else { + None + }; + + if let Some(class_constant) = class_constant { + context + .vars_in_scope + .insert(base_key.clone(), Rc::new(class_constant)); + } else { + return None; + } + } else { + return None; + } + } + + while let Some(divider) = key_parts.pop() { + if divider == "[" { + let array_key = key_parts.pop().unwrap(); + key_parts.pop(); + + let new_base_key = base_key.clone() + "[" + array_key.as_str() + "]"; + + if !context.vars_in_scope.contains_key(&new_base_key) { + let mut new_base_type: Option = None; + + let mut atomic_types = context.vars_in_scope.get(&base_key).unwrap().types.clone(); + + atomic_types.reverse(); + + while let Some(mut existing_key_type_part) = atomic_types.pop() { + if let TAtomic::TGenericParam { as_type, .. } = existing_key_type_part { + atomic_types.extend(as_type.types.clone()); + continue; + } + + if let TAtomic::TTypeAlias { + as_type: Some(as_type), + .. + } = existing_key_type_part + { + existing_key_type_part = as_type.get_single().clone(); + } + + let mut new_base_type_candidate; + + if let TAtomic::TDict { known_items, .. } = &existing_key_type_part { + let known_item = if !array_key.starts_with('$') { + if let Some(known_items) = known_items { + let key_parts_key = array_key.replace('\'', ""); + known_items.get(&DictKey::String(key_parts_key)) + } else { + None + } + } else { + None + }; + + if let Some(known_item) = known_item { + let known_item = known_item.clone(); + + new_base_type_candidate = (*known_item.1).clone(); + + if known_item.0 { + *possibly_undefined = true; + } + } else { + new_base_type_candidate = + get_value_param(&existing_key_type_part, codebase).unwrap(); + + if new_base_type_candidate.is_mixed() + && !has_isset + && !has_inverted_isset + { + return Some(new_base_type_candidate); + } + + if (has_isset || has_inverted_isset) + && new_assertions.contains_key(&new_base_key) + { + if has_inverted_isset && new_base_key.eq(&key) { + new_base_type_candidate = add_union_type( + new_base_type_candidate, + &get_null(), + codebase, + false, + ); + } + + *possibly_undefined = true; + } + } + } else if let TAtomic::TVec { known_items, .. } = &existing_key_type_part { + let known_item = if INTEGER_REGEX.is_match(&array_key) { + if let Some(known_items) = known_items { + let key_parts_key = array_key.parse::().unwrap(); + known_items.get(&key_parts_key) + } else { + None + } + } else { + None + }; + + if let Some(known_item) = known_item { + new_base_type_candidate = known_item.1.clone(); + + if known_item.0 { + *possibly_undefined = true; + } + } else { + new_base_type_candidate = + get_value_param(&existing_key_type_part, codebase).unwrap(); + + if (has_isset || has_inverted_isset) + && new_assertions.contains_key(&new_base_key) + { + if has_inverted_isset && new_base_key.eq(&key) { + new_base_type_candidate = add_union_type( + new_base_type_candidate, + &get_null(), + codebase, + false, + ); + } + + *possibly_undefined = true; + } + } + } else if matches!( + existing_key_type_part, + TAtomic::TString + | TAtomic::TLiteralString { .. } + | TAtomic::TStringWithFlags(..) + ) { + return Some(hakana_type::get_string()); + } else if matches!( + existing_key_type_part, + TAtomic::TNothing | TAtomic::TMixedFromLoopIsset + ) { + return Some(hakana_type::get_mixed_maybe_from_loop(inside_loop)); + } else if let TAtomic::TNamedObject { + name, + type_params: Some(type_params), + .. + } = &existing_key_type_part + { + match name { + &StrId::KEYED_CONTAINER | &StrId::CONTAINER => { + new_base_type_candidate = if name == &StrId::KEYED_CONTAINER { + type_params[1].clone() + } else { + type_params[0].clone() + }; + + if (has_isset || has_inverted_isset) + && new_assertions.contains_key(&new_base_key) + { + if has_inverted_isset && new_base_key.eq(&key) { + new_base_type_candidate = add_union_type( + new_base_type_candidate, + &get_null(), + codebase, + false, + ); + } + + *possibly_undefined = true; + } + } + _ => { + return Some(hakana_type::get_mixed_any()); + } + } + } else { + return Some(hakana_type::get_mixed_any()); + } + + new_base_type = if let Some(new_base_type) = new_base_type { + Some(hakana_type::add_union_type( + new_base_type, + &new_base_type_candidate, + codebase, + false, + )) + } else { + Some(new_base_type_candidate.clone()) + }; + + if !array_key.starts_with('$') { + added_var_ids.insert(new_base_key.clone()); + } + + context.vars_in_scope.insert( + new_base_key.clone(), + Rc::new(new_base_type.clone().unwrap()), + ); + } + } + + base_key = new_base_key; + } else if divider == "->" || divider == "::$" { + let property_name = key_parts.pop().unwrap(); + + let new_base_key = base_key.clone() + "->" + property_name.as_str(); + + if !context.vars_in_scope.contains_key(&new_base_key) { + let mut new_base_type: Option = None; + + let base_type = context.vars_in_scope.get(&base_key).unwrap(); + + let mut atomic_types = base_type.types.clone(); + + while let Some(existing_key_type_part) = atomic_types.pop() { + if let TAtomic::TGenericParam { as_type, .. } = existing_key_type_part { + atomic_types.extend(as_type.types.clone()); + continue; + } + + let class_property_type: TUnion; + + if let TAtomic::TNull { .. } = existing_key_type_part { + class_property_type = get_null(); + } else if let TAtomic::TMixed + | TAtomic::TMixedWithFlags(..) + | TAtomic::TGenericParam { .. } + | TAtomic::TObject { .. } = existing_key_type_part + { + class_property_type = get_mixed_any(); + } else if let TAtomic::TNamedObject { + name: fq_class_name, + .. + } = existing_key_type_part + { + if fq_class_name == StrId::STDCLASS + || !codebase.class_or_interface_exists(&fq_class_name) + { + class_property_type = get_mixed_any(); + } else if property_name.ends_with("()") { + // MAYBE TODO deal with memoisable method call memoisation + panic!(); + } else { + let maybe_class_property_type = get_property_type( + codebase, + interner, + &fq_class_name, + &interner.get(&property_name)?, + analysis_data, + ); + + if let Some(maybe_class_property_type) = maybe_class_property_type { + class_property_type = maybe_class_property_type; + } else { + return None; + } + } + } else { + class_property_type = get_mixed_any(); + } + + new_base_type = if let Some(new_base_type) = new_base_type { + Some(hakana_type::add_union_type( + new_base_type, + &class_property_type, + codebase, + false, + )) + } else { + Some(class_property_type) + }; + + context.vars_in_scope.insert( + new_base_key.clone(), + Rc::new(new_base_type.clone().unwrap()), + ); + } + } + + base_key = new_base_key; + } else { + return None; + } + } + + context.vars_in_scope.get(&base_key).map(|t| (**t).clone()) +} + +fn get_property_type( + codebase: &CodebaseInfo, + interner: &Interner, + classlike_name: &StrId, + property_name: &StrId, + analysis_data: &mut FunctionAnalysisData, +) -> Option { + if !codebase.property_exists(classlike_name, property_name) { + return None; + } + + let declaring_property_class = + codebase.get_declaring_class_for_property(classlike_name, property_name); + + let declaring_property_class = declaring_property_class?; + + let class_property_type = codebase.get_property_type(classlike_name, property_name); + + if let Some(mut class_property_type) = class_property_type { + type_expander::expand_union( + codebase, + &Some(interner), + &mut class_property_type, + &TypeExpansionOptions { + self_class: Some(declaring_property_class), + static_class_type: StaticClassType::Name(declaring_property_class), + ..Default::default() + }, + &mut analysis_data.data_flow_graph, + ); + return Some(class_property_type); + } + + Some(get_mixed_any()) +} + +pub(crate) fn trigger_issue_for_impossible( + analysis_data: &mut FunctionAnalysisData, + statements_analyzer: &StatementsAnalyzer, + old_var_type_string: &String, + key: &String, + assertion: &Assertion, + redundant: bool, + negated: bool, + pos: &Pos, + calling_functionlike_id: &Option, + _suppressed_issues: &FxHashMap, +) { + let mut assertion_string = assertion.to_string(Some(statements_analyzer.get_interner())); + let mut not_operator = assertion_string.starts_with('!'); + + if not_operator { + assertion_string = assertion_string[1..].to_string(); + } + + let mut redundant = redundant; + + if negated { + not_operator = !not_operator; + redundant = !redundant; + } + + if redundant { + if not_operator { + if assertion_string == "falsy" { + not_operator = false; + assertion_string = "truthy".to_string(); + } else if assertion_string == "truthy" { + not_operator = false; + assertion_string = "falsy".to_string(); + } + } + + analysis_data.maybe_add_issue( + if not_operator { + get_impossible_issue( + assertion, + &assertion_string, + key, + statements_analyzer, + pos, + calling_functionlike_id, + old_var_type_string, + ) + } else { + get_redundant_issue( + assertion, + &assertion_string, + key, + statements_analyzer, + pos, + calling_functionlike_id, + old_var_type_string, + ) + }, + statements_analyzer.get_config(), + statements_analyzer.get_file_path_actual(), + ); + } else { + analysis_data.maybe_add_issue( + if not_operator { + get_redundant_issue( + assertion, + &assertion_string, + key, + statements_analyzer, + pos, + calling_functionlike_id, + old_var_type_string, + ) + } else { + get_impossible_issue( + assertion, + &assertion_string, + key, + statements_analyzer, + pos, + calling_functionlike_id, + old_var_type_string, + ) + }, + statements_analyzer.get_config(), + statements_analyzer.get_file_path_actual(), + ); + } +} + +fn get_impossible_issue( + assertion: &Assertion, + assertion_string: &String, + key: &String, + statements_analyzer: &StatementsAnalyzer, + pos: &Pos, + calling_functionlike_id: &Option, + old_var_type_string: &String, +) -> Issue { + let old_var_type_string = if old_var_type_string.len() > 50 { + if key.contains("tmp_coalesce_var") { + "".to_string() + } else { + format!("of {} ", key) + } + } else { + format!("{} ", old_var_type_string) + }; + + match assertion { + Assertion::Truthy | Assertion::Falsy => Issue::new( + IssueKind::ImpossibleTruthinessCheck, + format!("Type {}is never {}", old_var_type_string, assertion_string), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::IsType(TAtomic::TNull) | Assertion::IsNotType(TAtomic::TNull) => Issue::new( + IssueKind::ImpossibleNullTypeComparison, + format!("{} is never null", key), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::HasArrayKey(key) | Assertion::DoesNotHaveArrayKey(key) => Issue::new( + IssueKind::ImpossibleKeyCheck, + format!( + "Type {}never has key {}", + old_var_type_string, + key.to_string(Some(statements_analyzer.get_interner())) + ), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::HasNonnullEntryForKey(dict_key) => Issue::new( + IssueKind::ImpossibleNonnullEntryCheck, + format!( + "Type {}does not have a nonnull entry for {}", + old_var_type_string, + dict_key.to_string(Some(statements_analyzer.get_interner())) + ), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + _ => Issue::new( + IssueKind::ImpossibleTypeComparison, + format!("Type {}is never {}", old_var_type_string, &assertion_string), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + } +} + +fn get_redundant_issue( + assertion: &Assertion, + assertion_string: &String, + key: &String, + statements_analyzer: &StatementsAnalyzer, + pos: &Pos, + calling_functionlike_id: &Option, + old_var_type_string: &String, +) -> Issue { + let old_var_type_string = if old_var_type_string.len() > 50 { + if key.contains("tmp_coalesce_var") { + "".to_string() + } else { + format!("of {} ", key) + } + } else { + format!("{} ", old_var_type_string) + }; + + match assertion { + Assertion::IsIsset | Assertion::IsEqualIsset => Issue::new( + IssueKind::RedundantIssetCheck, + "Unnecessary isset check".to_string(), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::Truthy | Assertion::Falsy => Issue::new( + IssueKind::RedundantTruthinessCheck, + format!("Type {}is always {}", old_var_type_string, assertion_string), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::HasArrayKey(key) | Assertion::DoesNotHaveArrayKey(key) => Issue::new( + IssueKind::RedundantKeyCheck, + format!( + "Type {}always has entry {}", + old_var_type_string, + key.to_string(Some(statements_analyzer.get_interner())) + ), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::HasNonnullEntryForKey(key) => Issue::new( + IssueKind::RedundantNonnullEntryCheck, + format!( + "Type {}always has entry {}", + old_var_type_string, + key.to_string(Some(statements_analyzer.get_interner())) + ), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + Assertion::IsType(TAtomic::TMixedWithFlags(_, _, _, true)) + | Assertion::IsNotType(TAtomic::TMixedWithFlags(_, _, _, true)) => Issue::new( + IssueKind::RedundantNonnullTypeComparison, + format!("{} is always nonnull", key), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + _ => Issue::new( + IssueKind::RedundantTypeComparison, + format!("Type {}is always {}", old_var_type_string, assertion_string), + statements_analyzer.get_hpos(pos), + calling_functionlike_id, + ), + } +} diff --git a/src/analyzer/reconciler/negated_assertion_reconciler.rs b/src/analyzer/reconciler/negated_assertion_reconciler.rs index cd0aa52e..5e432fba 100644 --- a/src/analyzer/reconciler/negated_assertion_reconciler.rs +++ b/src/analyzer/reconciler/negated_assertion_reconciler.rs @@ -1,6 +1,6 @@ use super::{ - assertion_reconciler::intersect_atomic_with_atomic, reconciler::trigger_issue_for_impossible, - simple_negated_assertion_reconciler, + assertion_reconciler::intersect_atomic_with_atomic, simple_negated_assertion_reconciler, + trigger_issue_for_impossible, }; use crate::function_analysis_data::FunctionAnalysisData; use crate::{scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer}; diff --git a/src/analyzer/reconciler/reconciler.rs b/src/analyzer/reconciler/reconciler.rs index d0e79c96..e69de29b 100644 --- a/src/analyzer/reconciler/reconciler.rs +++ b/src/analyzer/reconciler/reconciler.rs @@ -1,1276 +0,0 @@ -use crate::{ - function_analysis_data::FunctionAnalysisData, - scope_analyzer::ScopeAnalyzer, - scope_context::{var_has_root, ScopeContext}, - statements_analyzer::StatementsAnalyzer, -}; -use hakana_reflection_info::{ - assertion::Assertion, - codebase_info::CodebaseInfo, - data_flow::{graph::GraphKind, node::DataFlowNode, path::PathKind}, - functionlike_identifier::FunctionLikeIdentifier, - issue::{Issue, IssueKind}, - t_atomic::{DictKey, TAtomic}, - t_union::TUnion, - Interner, StrId, -}; -use hakana_type::{ - add_union_type, get_mixed_any, get_null, get_value_param, - type_expander::{self, StaticClassType, TypeExpansionOptions}, - wrap_atomic, -}; -use lazy_static::lazy_static; -use oxidized::ast_defs::Pos; -use regex::Regex; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::{collections::BTreeMap, rc::Rc, sync::Arc}; - -pub(crate) fn reconcile_keyed_types( - new_types: &BTreeMap>>, - // types we can complain about - mut active_new_types: BTreeMap>, - context: &mut ScopeContext, - changed_var_ids: &mut FxHashSet, - referenced_var_ids: &FxHashSet, - statements_analyzer: &StatementsAnalyzer, - analysis_data: &mut FunctionAnalysisData, - pos: &Pos, - can_report_issues: bool, - negated: bool, - suppressed_issues: &FxHashMap, -) { - if new_types.is_empty() { - return; - } - - let inside_loop = context.inside_loop; - - let old_new_types = new_types.clone(); - - let mut new_types = new_types.clone(); - - add_nested_assertions(&mut new_types, &mut active_new_types, context); - - let codebase = statements_analyzer.get_codebase(); - - // we want to remove any - let mut added_var_ids = FxHashSet::default(); - - for (key, new_type_parts) in &new_types { - if key.contains("::") && !key.contains('$') && !key.contains('[') { - continue; - } - - let mut has_negation = false; - let mut has_isset = false; - let mut has_inverted_isset = false; - let mut has_falsyish = false; - let mut has_count_check = false; - let is_real = old_new_types - .get(key) - .unwrap_or(&Vec::new()) - .eq(new_type_parts); - - let mut is_equality = false; - - for new_type_part_parts in new_type_parts { - for assertion in new_type_part_parts { - if key == "hakana taints" { - match assertion { - Assertion::RemoveTaints(key, taints) => { - if let Some(existing_var_type) = context.vars_in_scope.get_mut(key) { - let new_parent_node = DataFlowNode::get_for_assignment( - key.clone(), - statements_analyzer.get_hpos(pos), - ); - - for old_parent_node in &existing_var_type.parent_nodes { - analysis_data.data_flow_graph.add_path( - old_parent_node, - &new_parent_node, - PathKind::Default, - None, - Some(taints.clone()), - ); - } - - let mut existing_var_type_inner = (**existing_var_type).clone(); - - existing_var_type_inner.parent_nodes = - FxHashSet::from_iter([new_parent_node.clone()]); - - *existing_var_type = Rc::new(existing_var_type_inner); - - analysis_data.data_flow_graph.add_node(new_parent_node); - } - } - Assertion::IgnoreTaints => { - context.allow_taints = false; - } - Assertion::DontIgnoreTaints => { - context.allow_taints = true; - } - _ => (), - } - - continue; - } - - if assertion.has_negation() { - has_negation = true; - } - - has_isset = has_isset || assertion.has_isset(); - - has_falsyish = has_falsyish || matches!(assertion, Assertion::Falsy); - - is_equality = is_equality || assertion.has_non_isset_equality(); - - has_inverted_isset = - has_inverted_isset || matches!(assertion, Assertion::IsNotIsset); - - has_count_check = - has_count_check || matches!(assertion, Assertion::NonEmptyCountable(_)); - } - } - - let did_type_exist = context.vars_in_scope.contains_key(key); - - let mut possibly_undefined = false; - - let mut result_type = if let Some(existing_type) = context.vars_in_scope.get(key) { - Some((**existing_type).clone()) - } else { - get_value_for_key( - codebase, - statements_analyzer.get_interner(), - key.clone(), - context, - &mut added_var_ids, - &new_types, - has_isset, - has_inverted_isset, - inside_loop, - &mut possibly_undefined, - analysis_data, - ) - }; - - if let Some(maybe_result_type) = &result_type { - if maybe_result_type.types.is_empty() { - panic!(); - } - } - - let before_adjustment = result_type.clone(); - - for (i, new_type_part_parts) in new_type_parts.iter().enumerate() { - let mut orred_type: Option = None; - - for assertion in new_type_part_parts { - let mut result_type_candidate = super::assertion_reconciler::reconcile( - assertion, - result_type.as_ref(), - possibly_undefined, - Some(key), - statements_analyzer, - analysis_data, - inside_loop, - Some(pos), - &context.function_context.calling_functionlike_id, - can_report_issues - && if referenced_var_ids.contains(key) && active_new_types.contains_key(key) - { - active_new_types.get(key).unwrap().get(&i).is_some() - } else { - false - }, - negated, - suppressed_issues, - ); - - if result_type_candidate.types.is_empty() { - result_type_candidate.types.push(TAtomic::TNothing); - } - - orred_type = if let Some(orred_type) = orred_type { - Some(add_union_type( - result_type_candidate, - &orred_type, - codebase, - false, - )) - } else { - Some(result_type_candidate.clone()) - }; - } - - result_type = orred_type; - } - - let mut result_type = result_type.unwrap(); - - if !did_type_exist && result_type.is_nothing() { - continue; - } - - let type_changed = if let Some(before_adjustment) = &before_adjustment { - &result_type != before_adjustment - } else { - true - }; - - if let Some(before_adjustment) = &before_adjustment { - if let GraphKind::WholeProgram(_) = &analysis_data.data_flow_graph.kind { - let mut has_scalar_restriction = false; - - for new_type_part_parts in new_type_parts { - if new_type_part_parts.len() == 1 { - let assertion = &new_type_part_parts[0]; - - if let Assertion::IsType(t) | Assertion::IsEqual(t) = assertion { - if t.is_some_scalar() { - has_scalar_restriction = true; - } - } - } - } - - if has_scalar_restriction { - let scalar_check_node = DataFlowNode::get_for_assignment( - key.clone(), - statements_analyzer.get_hpos(pos), - ); - - for parent_node in &before_adjustment.parent_nodes { - analysis_data.data_flow_graph.add_path( - parent_node, - &scalar_check_node, - PathKind::ScalarTypeGuard, - None, - None, - ); - } - - result_type.parent_nodes = FxHashSet::from_iter([scalar_check_node.clone()]); - - analysis_data.data_flow_graph.add_node(scalar_check_node); - } else { - let narrowed_symbol = if type_changed { - if result_type.is_single() { - if let TAtomic::TNamedObject { name, .. } = result_type.get_single() { - Some(name) - } else { - None - } - } else { - None - } - } else { - None - }; - if let Some(narrowed_symbol) = narrowed_symbol { - let narrowing_node = DataFlowNode::get_for_assignment( - key.clone() - + " narrowed to " - + statements_analyzer.get_interner().lookup(narrowed_symbol), - statements_analyzer.get_hpos(pos), - ); - - for parent_node in &before_adjustment.parent_nodes { - analysis_data.data_flow_graph.add_path( - parent_node, - &narrowing_node, - PathKind::RefineSymbol(*narrowed_symbol), - None, - None, - ); - } - - result_type.parent_nodes = FxHashSet::from_iter([narrowing_node.clone()]); - - analysis_data.data_flow_graph.add_node(narrowing_node); - } else { - result_type.parent_nodes = before_adjustment.parent_nodes.clone(); - } - } - } else { - result_type.parent_nodes = before_adjustment.parent_nodes.clone(); - } - } - - if key.ends_with(']') - && (type_changed || !did_type_exist) - && !has_inverted_isset - && !is_equality - { - let key_parts = break_up_path_into_parts(key); - - adjust_array_type( - key_parts, - context, - changed_var_ids, - &result_type, - ); - } - - if type_changed { - changed_var_ids.insert(key.clone()); - - if key != "$this" && !key.ends_with(']') { - let mut removable_keys = Vec::new(); - for (new_key, _) in context.vars_in_scope.iter() { - if new_key.eq(key) { - continue; - } - - if is_real && !new_types.contains_key(new_key) && var_has_root(new_key, key) { - removable_keys.push(new_key.clone()); - } - } - - for new_key in removable_keys { - context.vars_in_scope.remove(&new_key); - } - } - } else if !has_negation && !has_falsyish && !has_isset { - changed_var_ids.insert(key.clone()); - } - - context - .vars_in_scope - .insert(key.clone(), Rc::new(result_type)); - } - - context - .vars_in_scope - .retain(|var_id, _| !added_var_ids.contains(var_id)); -} - -fn adjust_array_type( - mut key_parts: Vec, - context: &mut ScopeContext, - changed_var_ids: &mut FxHashSet, - result_type: &TUnion, -) { - key_parts.pop(); - let array_key = key_parts.pop().unwrap(); - key_parts.pop(); - - if array_key.starts_with('$') { - return; - } - - let mut has_string_offset = false; - - let arraykey_offset = if array_key.starts_with('\'') || array_key.starts_with('\"') { - has_string_offset = true; - array_key[1..(array_key.len() - 1)].to_string() - } else { - array_key.clone() - }; - - let base_key = key_parts.join(""); - - let mut existing_type = if let Some(existing_type) = context.vars_in_scope.get(&base_key) { - (**existing_type).clone() - } else { - return; - }; - - for base_atomic_type in existing_type.types.iter_mut() { - if let TAtomic::TTypeAlias { - as_type: Some(as_type), - .. - } = base_atomic_type - { - *base_atomic_type = as_type.get_single().clone(); - } - - match base_atomic_type { - TAtomic::TDict { - ref mut known_items, - .. - } => { - let dictkey = if has_string_offset { - DictKey::String(arraykey_offset.clone()) - } else if let Ok(arraykey_value) = arraykey_offset.parse::() { - DictKey::Int(arraykey_value) - } else { - println!("bad int key {}", arraykey_offset); - continue; - }; - - if let Some(known_items) = known_items { - known_items.insert(dictkey, (false, Arc::new(result_type.clone()))); - } else { - *known_items = Some(BTreeMap::from([( - dictkey, - (false, Arc::new(result_type.clone())), - )])); - } - } - TAtomic::TVec { - ref mut known_items, - .. - } => { - if let Ok(arraykey_offset) = arraykey_offset.parse::() { - if let Some(known_items) = known_items { - known_items.insert(arraykey_offset, (false, result_type.clone())); - } else { - *known_items = Some(BTreeMap::from([( - arraykey_offset, - (false, result_type.clone()), - )])); - } - } - } - _ => { - continue; - } - } - - changed_var_ids.insert(format!("{}[{}]", base_key, array_key.clone())); - - if let Some(last_part) = key_parts.last() { - if last_part == "]" { - adjust_array_type( - key_parts.clone(), - context, - changed_var_ids, - &wrap_atomic(base_atomic_type.clone()), - ); - } - } - } - - context - .vars_in_scope - .insert(base_key, Rc::new(existing_type)); -} - -fn add_nested_assertions( - new_types: &mut BTreeMap>>, - active_new_types: &mut BTreeMap>, - context: &mut ScopeContext, -) { - lazy_static! { - static ref INTEGER_REGEX: Regex = Regex::new("^[0-9]+$").unwrap(); - } - - let mut keys_to_remove = vec![]; - - 'outer: for (nk, new_type) in new_types.clone() { - if (nk.contains('[') || nk.contains("->")) - && (new_type[0][0] == Assertion::IsEqualIsset || new_type[0][0] == Assertion::IsIsset) - { - let mut key_parts = break_up_path_into_parts(&nk); - key_parts.reverse(); - - let mut nesting = 0; - - let mut base_key = key_parts.pop().unwrap(); - - if !&base_key.starts_with('$') - && key_parts.len() > 2 - && key_parts.last().unwrap() == "::$" - { - base_key += key_parts.pop().unwrap().as_str(); - base_key += key_parts.pop().unwrap().as_str(); - } - - let base_key_set = if let Some(base_key_type) = context.vars_in_scope.get(&base_key) { - !base_key_type.is_nullable() - } else { - false - }; - - if !base_key_set { - if !new_types.contains_key(&base_key) { - new_types.insert(base_key.clone(), vec![vec![Assertion::IsEqualIsset]]); - } else { - let mut existing_entry = new_types.get(&base_key).unwrap().clone(); - existing_entry.push(vec![Assertion::IsEqualIsset]); - new_types.insert(base_key.clone(), existing_entry); - } - } - - while let Some(divider) = key_parts.pop() { - if divider == "[" { - let array_key = key_parts.pop().unwrap(); - key_parts.pop(); - - let new_base_key = base_key.clone() + "[" + array_key.as_str() + "]"; - - let entry = new_types.entry(base_key.clone()).or_default(); - - let new_key = if array_key.starts_with('\'') { - Some(DictKey::String( - array_key[1..(array_key.len() - 1)].to_string(), - )) - } else if array_key.starts_with('$') { - None - } else if let Ok(arraykey_value) = array_key.parse::() { - Some(DictKey::Int(arraykey_value)) - } else { - println!("bad int key {}", array_key); - panic!() - }; - - if let Some(new_key) = new_key { - entry.push(vec![Assertion::HasNonnullEntryForKey(new_key)]); - - if key_parts.is_empty() { - keys_to_remove.push(nk.clone()); - - if nesting == 0 - && base_key_set - && active_new_types.remove(&nk).is_some() - { - active_new_types - .entry(base_key.clone()) - .or_default() - .insert(entry.len() - 1); - } - - break 'outer; - } - } else { - entry.push(vec![if array_key.contains('\'') { - Assertion::HasStringArrayAccess - } else { - Assertion::HasIntOrStringArrayAccess - }]); - } - - base_key = new_base_key; - nesting += 1; - continue; - } - - if divider == "->" { - let property_name = key_parts.pop().unwrap(); - - let new_base_key = base_key.clone() + "->" + property_name.as_str(); - - if !new_types.contains_key(&base_key) { - new_types.insert(base_key.clone(), vec![vec![Assertion::IsIsset]]); - } - - base_key = new_base_key; - } else { - break; - } - - if key_parts.is_empty() { - break; - } - } - } - } - - new_types.retain(|k, _| !keys_to_remove.contains(k)); -} - -fn break_up_path_into_parts(path: &str) -> Vec { - let chars: Vec = path.chars().collect(); - - let mut string_char: Option = None; - - let mut escape_char = false; - let mut brackets = 0; - - let mut parts = BTreeMap::new(); - parts.insert(0, "".to_string()); - let mut parts_offset = 0; - - let mut i = 0; - let char_count = chars.len(); - - while i < char_count { - let ichar = *chars.get(i).unwrap(); - - if let Some(string_char_inner) = string_char { - if ichar == string_char_inner && !escape_char { - string_char = None; - } - - if ichar == '\\' { - escape_char = !escape_char; - } - - parts.insert( - parts_offset, - parts.get(&parts_offset).unwrap().clone() + ichar.to_string().as_str(), - ); - - i += 1; - continue; - } - - match ichar { - '[' | ']' => { - parts_offset += 1; - parts.insert(parts_offset, ichar.to_string()); - parts_offset += 1; - - brackets += if ichar == '[' { 1 } else { -1 }; - - i += 1; - continue; - } - - '\'' | '"' => { - parts.entry(parts_offset).or_insert_with(|| "".to_string()); - parts.insert( - parts_offset, - parts.get(&parts_offset).unwrap().clone() + ichar.to_string().as_str(), - ); - string_char = Some(ichar); - - i += 1; - continue; - } - - ':' => { - if brackets == 0 - && i < char_count - 2 - && *chars.get(i + 1).unwrap() == ':' - && *chars.get(i + 2).unwrap() == '$' - { - parts_offset += 1; - parts.insert(parts_offset, "::$".to_string()); - parts_offset += 1; - - i += 3; - continue; - } - } - - '-' => { - if brackets == 0 && i < char_count - 1 && *chars.get(i + 1).unwrap() == '>' { - parts_offset += 1; - parts.insert(parts_offset, "->".to_string()); - parts_offset += 1; - - i += 2; - continue; - } - } - - _ => {} - } - - parts.entry(parts_offset).or_insert_with(|| "".to_string()); - - parts.insert( - parts_offset, - parts.get(&parts_offset).unwrap().clone() + ichar.to_string().as_str(), - ); - - i += 1; - } - - parts.values().cloned().collect() -} - -fn get_value_for_key( - codebase: &CodebaseInfo, - interner: &Interner, - key: String, - context: &mut ScopeContext, - added_var_ids: &mut FxHashSet, - new_assertions: &BTreeMap>>, - has_isset: bool, - has_inverted_isset: bool, - inside_loop: bool, - possibly_undefined: &mut bool, - analysis_data: &mut FunctionAnalysisData, -) -> Option { - lazy_static! { - static ref INTEGER_REGEX: Regex = Regex::new("^[0-9]+$").unwrap(); - } - - let mut key_parts = break_up_path_into_parts(&key); - - if key_parts.len() == 1 { - if let Some(t) = context.vars_in_scope.get(&key) { - return Some((**t).clone()); - } - - return None; - } - - key_parts.reverse(); - - let mut base_key = key_parts.pop().unwrap(); - - if !base_key.starts_with('$') - && key_parts.len() > 2 - && key_parts.last().unwrap().starts_with("::$") - { - base_key += key_parts.pop().unwrap().as_str(); - base_key += key_parts.pop().unwrap().as_str(); - } - - if !context.vars_in_scope.contains_key(&base_key) { - if base_key.contains("::") { - let base_key_parts = &base_key.split("::").collect::>(); - let fq_class_name = base_key_parts[0].to_string(); - let const_name = base_key_parts[1].to_string(); - - let fq_class_name = &interner.get(fq_class_name.as_str()).unwrap(); - - if !codebase.class_or_interface_exists(fq_class_name) { - return None; - } - - let class_constant = if let Some(const_name) = interner.get(&const_name) { - codebase.get_class_constant_type( - fq_class_name, - false, - &const_name, - FxHashSet::default(), - ) - } else { - None - }; - - if let Some(class_constant) = class_constant { - context - .vars_in_scope - .insert(base_key.clone(), Rc::new(class_constant)); - } else { - return None; - } - } else { - return None; - } - } - - while let Some(divider) = key_parts.pop() { - if divider == "[" { - let array_key = key_parts.pop().unwrap(); - key_parts.pop(); - - let new_base_key = base_key.clone() + "[" + array_key.as_str() + "]"; - - if !context.vars_in_scope.contains_key(&new_base_key) { - let mut new_base_type: Option = None; - - let mut atomic_types = context.vars_in_scope.get(&base_key).unwrap().types.clone(); - - atomic_types.reverse(); - - while let Some(mut existing_key_type_part) = atomic_types.pop() { - if let TAtomic::TGenericParam { as_type, .. } = existing_key_type_part { - atomic_types.extend(as_type.types.clone()); - continue; - } - - if let TAtomic::TTypeAlias { - as_type: Some(as_type), - .. - } = existing_key_type_part - { - existing_key_type_part = as_type.get_single().clone(); - } - - let mut new_base_type_candidate; - - if let TAtomic::TDict { known_items, .. } = &existing_key_type_part { - let known_item = if !array_key.starts_with('$') { - if let Some(known_items) = known_items { - let key_parts_key = array_key.replace('\'', ""); - known_items.get(&DictKey::String(key_parts_key)) - } else { - None - } - } else { - None - }; - - if let Some(known_item) = known_item { - let known_item = known_item.clone(); - - new_base_type_candidate = (*known_item.1).clone(); - - if known_item.0 { - *possibly_undefined = true; - } - } else { - new_base_type_candidate = - get_value_param(&existing_key_type_part, codebase).unwrap(); - - if new_base_type_candidate.is_mixed() - && !has_isset - && !has_inverted_isset - { - return Some(new_base_type_candidate); - } - - if (has_isset || has_inverted_isset) - && new_assertions.contains_key(&new_base_key) - { - if has_inverted_isset && new_base_key.eq(&key) { - new_base_type_candidate = add_union_type( - new_base_type_candidate, - &get_null(), - codebase, - false, - ); - } - - *possibly_undefined = true; - } - } - } else if let TAtomic::TVec { known_items, .. } = &existing_key_type_part { - let known_item = if INTEGER_REGEX.is_match(&array_key) { - if let Some(known_items) = known_items { - let key_parts_key = array_key.parse::().unwrap(); - known_items.get(&key_parts_key) - } else { - None - } - } else { - None - }; - - if let Some(known_item) = known_item { - new_base_type_candidate = known_item.1.clone(); - - if known_item.0 { - *possibly_undefined = true; - } - } else { - new_base_type_candidate = - get_value_param(&existing_key_type_part, codebase).unwrap(); - - if (has_isset || has_inverted_isset) - && new_assertions.contains_key(&new_base_key) - { - if has_inverted_isset && new_base_key.eq(&key) { - new_base_type_candidate = add_union_type( - new_base_type_candidate, - &get_null(), - codebase, - false, - ); - } - - *possibly_undefined = true; - } - } - } else if matches!( - existing_key_type_part, - TAtomic::TString - | TAtomic::TLiteralString { .. } - | TAtomic::TStringWithFlags(..) - ) { - return Some(hakana_type::get_string()); - } else if matches!( - existing_key_type_part, - TAtomic::TNothing | TAtomic::TMixedFromLoopIsset - ) { - return Some(hakana_type::get_mixed_maybe_from_loop(inside_loop)); - } else if let TAtomic::TNamedObject { - name, - type_params: Some(type_params), - .. - } = &existing_key_type_part - { - match name { - &StrId::KEYED_CONTAINER | &StrId::CONTAINER => { - new_base_type_candidate = if name == &StrId::KEYED_CONTAINER { - type_params[1].clone() - } else { - type_params[0].clone() - }; - - if (has_isset || has_inverted_isset) - && new_assertions.contains_key(&new_base_key) - { - if has_inverted_isset && new_base_key.eq(&key) { - new_base_type_candidate = add_union_type( - new_base_type_candidate, - &get_null(), - codebase, - false, - ); - } - - *possibly_undefined = true; - } - } - _ => { - return Some(hakana_type::get_mixed_any()); - } - } - } else { - return Some(hakana_type::get_mixed_any()); - } - - new_base_type = if let Some(new_base_type) = new_base_type { - Some(hakana_type::add_union_type( - new_base_type, - &new_base_type_candidate, - codebase, - false, - )) - } else { - Some(new_base_type_candidate.clone()) - }; - - if !array_key.starts_with('$') { - added_var_ids.insert(new_base_key.clone()); - } - - context.vars_in_scope.insert( - new_base_key.clone(), - Rc::new(new_base_type.clone().unwrap()), - ); - } - } - - base_key = new_base_key; - } else if divider == "->" || divider == "::$" { - let property_name = key_parts.pop().unwrap(); - - let new_base_key = base_key.clone() + "->" + property_name.as_str(); - - if !context.vars_in_scope.contains_key(&new_base_key) { - let mut new_base_type: Option = None; - - let base_type = context.vars_in_scope.get(&base_key).unwrap(); - - let mut atomic_types = base_type.types.clone(); - - while let Some(existing_key_type_part) = atomic_types.pop() { - if let TAtomic::TGenericParam { as_type, .. } = existing_key_type_part { - atomic_types.extend(as_type.types.clone()); - continue; - } - - let class_property_type: TUnion; - - if let TAtomic::TNull { .. } = existing_key_type_part { - class_property_type = get_null(); - } else if let TAtomic::TMixed - | TAtomic::TMixedWithFlags(..) - | TAtomic::TGenericParam { .. } - | TAtomic::TObject { .. } = existing_key_type_part - { - class_property_type = get_mixed_any(); - } else if let TAtomic::TNamedObject { - name: fq_class_name, - .. - } = existing_key_type_part - { - if fq_class_name == StrId::STDCLASS - || !codebase.class_or_interface_exists(&fq_class_name) - { - class_property_type = get_mixed_any(); - } else if property_name.ends_with("()") { - // MAYBE TODO deal with memoisable method call memoisation - panic!(); - } else { - let maybe_class_property_type = get_property_type( - codebase, - interner, - &fq_class_name, - &interner.get(&property_name)?, - analysis_data, - ); - - if let Some(maybe_class_property_type) = maybe_class_property_type { - class_property_type = maybe_class_property_type; - } else { - return None; - } - } - } else { - class_property_type = get_mixed_any(); - } - - new_base_type = if let Some(new_base_type) = new_base_type { - Some(hakana_type::add_union_type( - new_base_type, - &class_property_type, - codebase, - false, - )) - } else { - Some(class_property_type) - }; - - context.vars_in_scope.insert( - new_base_key.clone(), - Rc::new(new_base_type.clone().unwrap()), - ); - } - } - - base_key = new_base_key; - } else { - return None; - } - } - - context.vars_in_scope.get(&base_key).map(|t| (**t).clone()) -} - -fn get_property_type( - codebase: &CodebaseInfo, - interner: &Interner, - classlike_name: &StrId, - property_name: &StrId, - analysis_data: &mut FunctionAnalysisData, -) -> Option { - if !codebase.property_exists(classlike_name, property_name) { - return None; - } - - let declaring_property_class = - codebase.get_declaring_class_for_property(classlike_name, property_name); - - let declaring_property_class = declaring_property_class?; - - let class_property_type = codebase.get_property_type(classlike_name, property_name); - - if let Some(mut class_property_type) = class_property_type { - type_expander::expand_union( - codebase, - &Some(interner), - &mut class_property_type, - &TypeExpansionOptions { - self_class: Some(declaring_property_class), - static_class_type: StaticClassType::Name(declaring_property_class), - ..Default::default() - }, - &mut analysis_data.data_flow_graph, - ); - return Some(class_property_type); - } - - Some(get_mixed_any()) -} - -pub(crate) fn trigger_issue_for_impossible( - analysis_data: &mut FunctionAnalysisData, - statements_analyzer: &StatementsAnalyzer, - old_var_type_string: &String, - key: &String, - assertion: &Assertion, - redundant: bool, - negated: bool, - pos: &Pos, - calling_functionlike_id: &Option, - _suppressed_issues: &FxHashMap, -) { - let mut assertion_string = assertion.to_string(Some(statements_analyzer.get_interner())); - let mut not_operator = assertion_string.starts_with('!'); - - if not_operator { - assertion_string = assertion_string[1..].to_string(); - } - - let mut redundant = redundant; - - if negated { - not_operator = !not_operator; - redundant = !redundant; - } - - if redundant { - if not_operator { - if assertion_string == "falsy" { - not_operator = false; - assertion_string = "truthy".to_string(); - } else if assertion_string == "truthy" { - not_operator = false; - assertion_string = "falsy".to_string(); - } - } - - analysis_data.maybe_add_issue( - if not_operator { - get_impossible_issue( - assertion, - &assertion_string, - key, - statements_analyzer, - pos, - calling_functionlike_id, - old_var_type_string, - ) - } else { - get_redundant_issue( - assertion, - &assertion_string, - key, - statements_analyzer, - pos, - calling_functionlike_id, - old_var_type_string, - ) - }, - statements_analyzer.get_config(), - statements_analyzer.get_file_path_actual(), - ); - } else { - analysis_data.maybe_add_issue( - if not_operator { - get_redundant_issue( - assertion, - &assertion_string, - key, - statements_analyzer, - pos, - calling_functionlike_id, - old_var_type_string, - ) - } else { - get_impossible_issue( - assertion, - &assertion_string, - key, - statements_analyzer, - pos, - calling_functionlike_id, - old_var_type_string, - ) - }, - statements_analyzer.get_config(), - statements_analyzer.get_file_path_actual(), - ); - } -} - -fn get_impossible_issue( - assertion: &Assertion, - assertion_string: &String, - key: &String, - statements_analyzer: &StatementsAnalyzer, - pos: &Pos, - calling_functionlike_id: &Option, - old_var_type_string: &String, -) -> Issue { - let old_var_type_string = if old_var_type_string.len() > 50 { - if key.contains("tmp_coalesce_var") { - "".to_string() - } else { - format!("of {} ", key) - } - } else { - format!("{} ", old_var_type_string) - }; - - match assertion { - Assertion::Truthy | Assertion::Falsy => Issue::new( - IssueKind::ImpossibleTruthinessCheck, - format!("Type {}is never {}", old_var_type_string, assertion_string), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::IsType(TAtomic::TNull) | Assertion::IsNotType(TAtomic::TNull) => Issue::new( - IssueKind::ImpossibleNullTypeComparison, - format!("{} is never null", key), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::HasArrayKey(key) | Assertion::DoesNotHaveArrayKey(key) => Issue::new( - IssueKind::ImpossibleKeyCheck, - format!( - "Type {}never has key {}", - old_var_type_string, - key.to_string(Some(statements_analyzer.get_interner())) - ), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::HasNonnullEntryForKey(dict_key) => Issue::new( - IssueKind::ImpossibleNonnullEntryCheck, - format!( - "Type {}does not have a nonnull entry for {}", - old_var_type_string, - dict_key.to_string(Some(statements_analyzer.get_interner())) - ), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - _ => Issue::new( - IssueKind::ImpossibleTypeComparison, - format!("Type {}is never {}", old_var_type_string, &assertion_string), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - } -} - -fn get_redundant_issue( - assertion: &Assertion, - assertion_string: &String, - key: &String, - statements_analyzer: &StatementsAnalyzer, - pos: &Pos, - calling_functionlike_id: &Option, - old_var_type_string: &String, -) -> Issue { - let old_var_type_string = if old_var_type_string.len() > 50 { - if key.contains("tmp_coalesce_var") { - "".to_string() - } else { - format!("of {} ", key) - } - } else { - format!("{} ", old_var_type_string) - }; - - match assertion { - Assertion::IsIsset | Assertion::IsEqualIsset => Issue::new( - IssueKind::RedundantIssetCheck, - "Unnecessary isset check".to_string(), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::Truthy | Assertion::Falsy => Issue::new( - IssueKind::RedundantTruthinessCheck, - format!("Type {}is always {}", old_var_type_string, assertion_string), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::HasArrayKey(key) | Assertion::DoesNotHaveArrayKey(key) => Issue::new( - IssueKind::RedundantKeyCheck, - format!( - "Type {}always has entry {}", - old_var_type_string, - key.to_string(Some(statements_analyzer.get_interner())) - ), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::HasNonnullEntryForKey(key) => Issue::new( - IssueKind::RedundantNonnullEntryCheck, - format!( - "Type {}always has entry {}", - old_var_type_string, - key.to_string(Some(statements_analyzer.get_interner())) - ), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - Assertion::IsType(TAtomic::TMixedWithFlags(_, _, _, true)) - | Assertion::IsNotType(TAtomic::TMixedWithFlags(_, _, _, true)) => Issue::new( - IssueKind::RedundantNonnullTypeComparison, - format!("{} is always nonnull", key), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - _ => Issue::new( - IssueKind::RedundantTypeComparison, - format!("Type {}is always {}", old_var_type_string, assertion_string), - statements_analyzer.get_hpos(pos), - calling_functionlike_id, - ), - } -} diff --git a/src/analyzer/reconciler/simple_assertion_reconciler.rs b/src/analyzer/reconciler/simple_assertion_reconciler.rs index 3f15e92e..4108b537 100644 --- a/src/analyzer/reconciler/simple_assertion_reconciler.rs +++ b/src/analyzer/reconciler/simple_assertion_reconciler.rs @@ -1,8 +1,6 @@ use std::{collections::BTreeMap, sync::Arc}; -use super::{ - reconciler::trigger_issue_for_impossible, simple_negated_assertion_reconciler::subtract_null, -}; +use super::{simple_negated_assertion_reconciler::subtract_null, trigger_issue_for_impossible}; use crate::{ function_analysis_data::FunctionAnalysisData, intersect_simple, scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer, @@ -227,7 +225,6 @@ pub(crate) fn reconcile( } => { if params.0.is_placeholder() && params.1.is_placeholder() { return Some(intersect_dict( - codebase, assertion, existing_var_type, key, @@ -861,7 +858,6 @@ fn intersect_keyset( } fn intersect_dict( - codebase: &CodebaseInfo, assertion: &Assertion, existing_var_type: &TUnion, key: Option<&String>, @@ -892,7 +888,6 @@ fn intersect_dict( acceptable_types.push(atomic); } else { let atomic = atomic.replace_template_extends(intersect_dict( - codebase, assertion, as_type, None, diff --git a/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs b/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs index 630eb05f..30ca5fab 100644 --- a/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs +++ b/src/analyzer/reconciler/simple_negated_assertion_reconciler.rs @@ -1,7 +1,7 @@ use super::simple_assertion_reconciler::{get_acceptable_type, intersect_null}; use crate::{ function_analysis_data::FunctionAnalysisData, - reconciler::reconciler::trigger_issue_for_impossible, scope_analyzer::ScopeAnalyzer, + reconciler::trigger_issue_for_impossible, scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer, }; use hakana_reflection_info::{ @@ -262,7 +262,6 @@ pub(crate) fn reconcile( return Some(get_nothing()); } Assertion::DoesNotHaveArrayKey(key_name) => Some(reconcile_no_array_key( - statements_analyzer.get_codebase(), assertion, existing_var_type, key, @@ -1782,7 +1781,6 @@ fn reconcile_not_in_array( } fn reconcile_no_array_key( - codebase: &CodebaseInfo, assertion: &Assertion, existing_var_type: &TUnion, key: Option<&String>, @@ -1881,7 +1879,6 @@ fn reconcile_no_array_key( acceptable_types.push(atomic); } else { let atomic = atomic.replace_template_extends(reconcile_no_array_key( - codebase, assertion, as_type, None, diff --git a/src/analyzer/stmt/continue_analyzer.rs b/src/analyzer/stmt/continue_analyzer.rs index 8f6df233..6d4b3df3 100644 --- a/src/analyzer/stmt/continue_analyzer.rs +++ b/src/analyzer/stmt/continue_analyzer.rs @@ -41,22 +41,18 @@ pub(crate) fn analyze( ); } - if loop_scope.iteration_count == 0 { - for (_var_id, _var_type) in &context.vars_in_scope { - // todo populate finally scope - } - } + // if loop_scope.iteration_count == 0 { + // for (_var_id, _var_type) in &context.vars_in_scope { + // // todo populate finally scope + // } + // } if let Some(finally_scope) = context.finally_scope.clone() { let mut finally_scope = (*finally_scope).borrow_mut(); for (var_id, var_type) in &context.vars_in_scope { if let Some(finally_type) = finally_scope.vars_in_scope.get_mut(var_id) { - *finally_type = Rc::new(combine_union_types( - finally_type, - var_type, - codebase, - false, - )); + *finally_type = + Rc::new(combine_union_types(finally_type, var_type, codebase, false)); } else { finally_scope .vars_in_scope diff --git a/src/analyzer/stmt/do_analyzer.rs b/src/analyzer/stmt/do_analyzer.rs index 2457afab..8eb4bcaf 100644 --- a/src/analyzer/stmt/do_analyzer.rs +++ b/src/analyzer/stmt/do_analyzer.rs @@ -7,7 +7,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ formula_generator, function_analysis_data::FunctionAnalysisData, - reconciler::reconciler, + reconciler, scope_context::{loop_scope::LoopScope, ScopeContext}, statements_analyzer::StatementsAnalyzer, stmt_analyzer::AnalysisError, diff --git a/src/analyzer/stmt/else_analyzer.rs b/src/analyzer/stmt/else_analyzer.rs index e4675249..00579a93 100644 --- a/src/analyzer/stmt/else_analyzer.rs +++ b/src/analyzer/stmt/else_analyzer.rs @@ -1,5 +1,5 @@ use super::{control_analyzer, if_analyzer}; -use crate::reconciler::reconciler; +use crate::reconciler; use crate::scope_analyzer::ScopeAnalyzer; use crate::scope_context::control_action::ControlAction; use crate::scope_context::loop_scope::LoopScope; diff --git a/src/analyzer/stmt/if_analyzer.rs b/src/analyzer/stmt/if_analyzer.rs index 869852fb..9ddc1393 100644 --- a/src/analyzer/stmt/if_analyzer.rs +++ b/src/analyzer/stmt/if_analyzer.rs @@ -1,5 +1,5 @@ use super::control_analyzer; -use crate::reconciler::reconciler; +use crate::reconciler; use crate::scope_analyzer::ScopeAnalyzer; use crate::scope_context::control_action::ControlAction; use crate::scope_context::loop_scope::LoopScope; diff --git a/src/analyzer/stmt/if_conditional_analyzer.rs b/src/analyzer/stmt/if_conditional_analyzer.rs index f38e4be3..37fee1d2 100644 --- a/src/analyzer/stmt/if_conditional_analyzer.rs +++ b/src/analyzer/stmt/if_conditional_analyzer.rs @@ -16,7 +16,7 @@ use oxidized::{aast, ast, ast_defs::Pos}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - function_analysis_data::FunctionAnalysisData, reconciler::reconciler, + function_analysis_data::FunctionAnalysisData, reconciler, statements_analyzer::StatementsAnalyzer, }; diff --git a/src/analyzer/stmt/ifelse_analyzer.rs b/src/analyzer/stmt/ifelse_analyzer.rs index db108000..45e89400 100644 --- a/src/analyzer/stmt/ifelse_analyzer.rs +++ b/src/analyzer/stmt/ifelse_analyzer.rs @@ -17,7 +17,7 @@ use std::{collections::BTreeMap, rc::Rc}; use crate::{ algebra_analyzer, formula_generator, function_analysis_data::FunctionAnalysisData, - reconciler::reconciler, scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer, + reconciler, scope_analyzer::ScopeAnalyzer, statements_analyzer::StatementsAnalyzer, }; use super::{ diff --git a/src/analyzer/stmt/loop_/assignment_map_visitor.rs b/src/analyzer/stmt/loop_/assignment_map_visitor.rs index 7a9a3a5a..cfda9022 100644 --- a/src/analyzer/stmt/loop_/assignment_map_visitor.rs +++ b/src/analyzer/stmt/loop_/assignment_map_visitor.rs @@ -1,4 +1,3 @@ -use hakana_reflection_info::StrId; use oxidized::{ aast, aast_visitor::{visit, AstParams, Node, Visitor}, @@ -14,9 +13,7 @@ struct Scanner { pub first_var_id: Option, } -struct Context { - this_class_name: Option, -} +struct Context {} impl Scanner { fn new() -> Self { @@ -38,19 +35,11 @@ impl<'ast> Visitor<'ast> for Scanner { match &expr.2 { aast::Expr_::Binop(boxed) => { if let ast_defs::Bop::Eq(_) = boxed.bop { - let right_var_id = expression_identifier::get_root_var_id( - &boxed.rhs, - c.this_class_name.as_ref(), - None, - ); + let right_var_id = expression_identifier::get_root_var_id(&boxed.rhs); if let aast::Expr_::List(contents) = &boxed.lhs.2 { for list_expr in contents { - let left_var_id = expression_identifier::get_root_var_id( - list_expr, - c.this_class_name.as_ref(), - None, - ); + let left_var_id = expression_identifier::get_root_var_id(list_expr); if let Some(left_var_id) = &left_var_id { if self.first_var_id.is_none() { @@ -63,11 +52,7 @@ impl<'ast> Visitor<'ast> for Scanner { } } } else { - let left_var_id = expression_identifier::get_root_var_id( - &boxed.lhs, - c.this_class_name.as_ref(), - None, - ); + let left_var_id = expression_identifier::get_root_var_id(&boxed.lhs); if let Some(left_var_id) = &left_var_id { if self.first_var_id.is_none() { @@ -86,11 +71,7 @@ impl<'ast> Visitor<'ast> for Scanner { | ast_defs::Uop::Uincr | ast_defs::Uop::Updecr | ast_defs::Uop::Upincr => { - let var_id = expression_identifier::get_root_var_id( - &boxed.1, - c.this_class_name.as_ref(), - None, - ); + let var_id = expression_identifier::get_root_var_id(&boxed.1); if let Some(var_id) = &var_id { if self.first_var_id.is_none() { @@ -107,11 +88,7 @@ impl<'ast> Visitor<'ast> for Scanner { aast::Expr_::Call(boxed) => { for arg_expr in &boxed.args { if let ParamKind::Pinout(..) = arg_expr.0 { - let arg_var_id = expression_identifier::get_root_var_id( - &arg_expr.1, - c.this_class_name.as_ref(), - None, - ); + let arg_var_id = expression_identifier::get_root_var_id(&arg_expr.1); if let Some(arg_var_id) = &arg_var_id { if self.first_var_id.is_none() { @@ -135,9 +112,8 @@ impl<'ast> Visitor<'ast> for Scanner { match prop_or_method { ast_defs::PropOrMethod::IsMethod => { - let lhs_var_id = expression_identifier::get_root_var_id( - lhs_expr, None, None, - ); + let lhs_var_id = + expression_identifier::get_root_var_id(lhs_expr); if let Some(lhs_var_id) = lhs_var_id { if self.first_var_id.is_none() { @@ -174,10 +150,9 @@ pub fn get_assignment_map( pre_conditions: &Vec<&aast::Expr<(), ()>>, post_expressions: &Vec<&aast::Expr<(), ()>>, stmts: &Vec>, - this_class_name: Option, ) -> (FxHashMap>, Option) { let mut scanner = Scanner::new(); - let mut context = Context { this_class_name }; + let mut context = Context {}; for pre_condition in pre_conditions { visit(&mut scanner, &mut context, pre_condition).unwrap(); diff --git a/src/analyzer/stmt/loop_analyzer.rs b/src/analyzer/stmt/loop_analyzer.rs index 05505257..88582e38 100644 --- a/src/analyzer/stmt/loop_analyzer.rs +++ b/src/analyzer/stmt/loop_analyzer.rs @@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ expression_analyzer, formula_generator, function_analysis_data::FunctionAnalysisData, - reconciler::reconciler, + reconciler, scope_analyzer::ScopeAnalyzer, scope_context::{control_action::ControlAction, loop_scope::LoopScope, ScopeContext}, statements_analyzer::StatementsAnalyzer, @@ -35,12 +35,8 @@ pub(crate) fn analyze<'a>( is_do: bool, always_enters_loop: bool, ) -> Result { - let (assignment_map, first_var_id) = get_assignment_map( - &pre_conditions, - &post_expressions, - stmts, - loop_context.function_context.calling_class, - ); + let (assignment_map, first_var_id) = + get_assignment_map(&pre_conditions, &post_expressions, stmts); let assignment_depth = if let Some(first_var_id) = first_var_id { get_assignment_map_depth(&first_var_id, &mut assignment_map.clone()) diff --git a/src/analyzer/stmt/switch_case_analyzer.rs b/src/analyzer/stmt/switch_case_analyzer.rs index 05b46117..06774d5f 100644 --- a/src/analyzer/stmt/switch_case_analyzer.rs +++ b/src/analyzer/stmt/switch_case_analyzer.rs @@ -22,7 +22,7 @@ use rustc_hash::FxHashMap; use std::collections::BTreeMap; use std::sync::Arc; -use crate::reconciler::reconciler; +use crate::reconciler; use std::rc::Rc; diff --git a/src/cli/lib.rs b/src/cli/lib.rs index ef32cdf2..8697cb3e 100644 --- a/src/cli/lib.rs +++ b/src/cli/lib.rs @@ -9,7 +9,7 @@ use hakana_reflection_info::Interner; use indexmap::IndexMap; use rand::Rng; use rustc_hash::FxHashSet; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use std::env; use std::fs::{self, File}; use std::io::Write; @@ -1492,7 +1492,7 @@ fn replace_contents( } Replacement::Substitute(string) => { file_contents = file_contents[..start as usize].to_string() - + string + + &string + &*file_contents[end as usize..].to_string(); } } diff --git a/src/cli/test_runners/test_runner.rs b/src/cli/test_runners/test_runner.rs index 1308cfa9..561cb63e 100644 --- a/src/cli/test_runners/test_runner.rs +++ b/src/cli/test_runners/test_runner.rs @@ -325,7 +325,7 @@ impl TestRunner { .map(String::from) .collect::>(); - if unexpected_candidates.len() > 0 { + if !unexpected_candidates.is_empty() { test_diagnostics.push(( dir.clone(), format!( @@ -334,7 +334,7 @@ impl TestRunner { ), )); } - if missing_candidates.len() > 0 { + if !missing_candidates.is_empty() { test_diagnostics.push(( dir.clone(), format!( @@ -344,7 +344,7 @@ impl TestRunner { )); } - if unexpected_candidates.len() > 0 || missing_candidates.len() > 0 { + if !unexpected_candidates.is_empty() || !missing_candidates.is_empty() { ("F".to_string(), Some(result.1), Some(result.0)) } else { (".".to_string(), Some(result.1), Some(result.0)) diff --git a/src/code_info/t_atomic.rs b/src/code_info/t_atomic.rs index 2e5ba0ff..b2d2341c 100644 --- a/src/code_info/t_atomic.rs +++ b/src/code_info/t_atomic.rs @@ -239,11 +239,7 @@ impl TAtomic { property_type.get_id_with_refs( interner, refs, - if let Some(indent) = indent { - Some(indent + 1) - } else { - None - } + indent.map(|indent| indent + 1) ) ) }) diff --git a/src/code_info_builder/classlike_scanner.rs b/src/code_info_builder/classlike_scanner.rs index b9e0cfee..a791a1b2 100644 --- a/src/code_info_builder/classlike_scanner.rs +++ b/src/code_info_builder/classlike_scanner.rs @@ -517,7 +517,6 @@ pub(crate) fn scan( resolved_names, &mut storage, file_source, - codebase, interner, &mut def_signature_node.children, all_uses, @@ -558,12 +557,8 @@ pub(crate) fn scan( let mut child_classlikes = FxHashSet::default(); for attribute_param_expr in &user_attribute.params { - let attribute_param_type = simple_type_inferer::infer( - codebase, - &mut FxHashMap::default(), - attribute_param_expr, - resolved_names, - ); + let attribute_param_type = + 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() { @@ -773,7 +768,6 @@ fn visit_class_const_declaration( resolved_names: &FxHashMap, classlike_storage: &mut ClassLikeInfo, file_source: &FileSource, - codebase: &CodebaseInfo, interner: &mut ThreadedInterner, def_child_signature_nodes: &mut Vec, all_uses: &Uses, @@ -831,12 +825,7 @@ fn visit_class_const_declaration( inferred_type: if let ClassConstKind::CCAbstract(Some(const_expr)) | ClassConstKind::CCConcrete(const_expr) = &const_node.kind { - simple_type_inferer::infer( - codebase, - &mut FxHashMap::default(), - const_expr, - resolved_names, - ) + simple_type_inferer::infer(const_expr, resolved_names) } else { None }, diff --git a/src/code_info_builder/functionlike_scanner.rs b/src/code_info_builder/functionlike_scanner.rs index 91074936..1efc6067 100644 --- a/src/code_info_builder/functionlike_scanner.rs +++ b/src/code_info_builder/functionlike_scanner.rs @@ -63,7 +63,6 @@ pub(crate) fn scan_method( }; let mut functionlike_info = get_functionlike( - codebase, interner, all_custom_issues, method_name, @@ -144,7 +143,6 @@ fn add_promoted_param_property( } pub(crate) fn get_functionlike( - codebase: &CodebaseInfo, interner: &mut ThreadedInterner, all_custom_issues: &FxHashSet, name: StrId, @@ -286,7 +284,6 @@ pub(crate) fn get_functionlike( if !params.is_empty() { functionlike_info.params = convert_param_nodes( - codebase, interner, params, resolved_names, @@ -328,12 +325,8 @@ pub(crate) fn get_functionlike( let mut source_types = FxHashSet::default(); for attribute_param_expr in &user_attribute.params { - let attribute_param_type = simple_type_inferer::infer( - codebase, - &mut FxHashMap::default(), - attribute_param_expr, - resolved_names, - ); + let attribute_param_type = + 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() { @@ -359,12 +352,8 @@ pub(crate) fn get_functionlike( let mut removed_types = FxHashSet::default(); for attribute_param_expr in &user_attribute.params { - let attribute_param_type = simple_type_inferer::infer( - codebase, - &mut FxHashMap::default(), - attribute_param_expr, - resolved_names, - ); + 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() @@ -586,7 +575,6 @@ pub(crate) fn adjust_location_from_comments( } fn convert_param_nodes( - codebase: &CodebaseInfo, interner: &mut ThreadedInterner, param_nodes: &[aast::FunParam<(), ()>], resolved_names: &FxHashMap, @@ -664,12 +652,8 @@ fn convert_param_nodes( let mut sink_types = FxHashSet::default(); for attribute_param_expr in &user_attribute.params { - let attribute_param_type = simple_type_inferer::infer( - codebase, - &mut FxHashMap::default(), - attribute_param_expr, - resolved_names, - ); + let attribute_param_type = + simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { if let Some(str) = @@ -686,12 +670,8 @@ fn convert_param_nodes( let mut removed_taints = FxHashSet::default(); for attribute_param_expr in &user_attribute.params { - let attribute_param_type = simple_type_inferer::infer( - codebase, - &mut FxHashMap::default(), - attribute_param_expr, - resolved_names, - ); + let attribute_param_type = + simple_type_inferer::infer(attribute_param_expr, resolved_names); if let Some(attribute_param_type) = attribute_param_type { if let Some(str) = diff --git a/src/code_info_builder/lib.rs b/src/code_info_builder/lib.rs index f68a3979..66d8d127 100644 --- a/src/code_info_builder/lib.rs +++ b/src/code_info_builder/lib.rs @@ -170,12 +170,7 @@ impl<'ast> Visitor<'ast> for Scanner<'_> { } else { None }, - inferred_type: simple_type_inferer::infer( - self.codebase, - &mut FxHashMap::default(), - &gc.value, - self.resolved_names, - ), + inferred_type: simple_type_inferer::infer(&gc.value, self.resolved_names), unresolved_value: None, is_abstract: false, }, @@ -355,12 +350,8 @@ impl<'ast> Visitor<'ast> for Scanner<'_> { let attribute_param_expr = &shape_source_attribute.params[0]; - let attribute_param_type = simple_type_inferer::infer( - self.codebase, - &mut FxHashMap::default(), - attribute_param_expr, - self.resolved_names, - ); + 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(); @@ -676,7 +667,6 @@ impl<'a> Scanner<'a> { }; let mut functionlike_storage = functionlike_scanner::get_functionlike( - self.codebase, self.interner, self.all_custom_issues, name, diff --git a/src/code_info_builder/simple_type_inferer.rs b/src/code_info_builder/simple_type_inferer.rs index b52f6cb6..ad62d104 100644 --- a/src/code_info_builder/simple_type_inferer.rs +++ b/src/code_info_builder/simple_type_inferer.rs @@ -1,5 +1,4 @@ use hakana_reflection_info::{ - codebase_info::CodebaseInfo, t_atomic::{DictKey, TAtomic}, t_union::TUnion, StrId, @@ -8,16 +7,11 @@ use hakana_type::{ get_false, get_float, get_int, get_literal_int, get_literal_string, get_nothing, get_null, get_true, wrap_atomic, }; -use oxidized::{ - aast, - ast_defs::{self, Pos}, -}; +use oxidized::{aast, ast_defs}; use rustc_hash::FxHashMap; use std::{collections::BTreeMap, num::ParseIntError, sync::Arc}; pub fn infer( - codebase: &CodebaseInfo, - expr_types: &mut FxHashMap, expr: &aast::Expr<(), ()>, resolved_names: &FxHashMap, ) -> Option { @@ -57,7 +51,7 @@ pub fn infer( for (shape_field_name, field_expr) in shape_fields { if let ast_defs::ShapeFieldName::SFlitStr((_, str)) = shape_field_name { - let field_type = infer(codebase, expr_types, field_expr, resolved_names); + let field_type = infer(field_expr, resolved_names); if let Some(field_type) = field_type { known_items.insert( @@ -83,7 +77,7 @@ pub fn infer( let mut entries = BTreeMap::new(); for (i, entry_expr) in boxed.2.iter().enumerate() { - let entry_type = infer(codebase, expr_types, entry_expr, resolved_names); + let entry_type = infer(entry_expr, resolved_names); if let Some(entry_type) = entry_type { entries.insert(i, (false, entry_type)); @@ -108,7 +102,7 @@ pub fn infer( for entry_field in &boxed.2 { if let aast::Expr_::String(key_value) = &entry_field.0 .2 { - let value_type = infer(codebase, expr_types, &entry_field.1, resolved_names); + let value_type = infer(&entry_field.1, resolved_names); if let Some(value_type) = value_type { known_items.insert( @@ -151,7 +145,7 @@ pub fn infer( let mut entries = BTreeMap::new(); for (i, entry_expr) in values.iter().enumerate() { - let entry_type = infer(codebase, expr_types, entry_expr, resolved_names); + let entry_type = infer(entry_expr, resolved_names); if let Some(entry_type) = entry_type { entries.insert(i, (false, entry_type)); @@ -176,7 +170,7 @@ pub fn infer( } aast::Expr_::Unop(boxed) => { if let ast_defs::Uop::Uminus = boxed.0 { - let number_type = infer(codebase, expr_types, &boxed.1, resolved_names); + let number_type = infer(&boxed.1, resolved_names); if let Some(number_type) = number_type { if number_type.is_single() { @@ -216,7 +210,7 @@ pub fn infer( for (key_expr, value_expr) in &boxed.1 { if let aast::Expr_::String(key_value) = &key_expr.2 { - let value_type = infer(codebase, expr_types, value_expr, resolved_names); + let value_type = infer(value_expr, resolved_names); if let Some(value_type) = value_type { known_items.insert( @@ -242,7 +236,7 @@ pub fn infer( let mut entries = BTreeMap::new(); for (i, entry_expr) in boxed.1.iter().enumerate() { - let entry_type = infer(codebase, expr_types, entry_expr, resolved_names); + let entry_type = infer(entry_expr, resolved_names); if let Some(entry_type) = entry_type { entries.insert(i, (false, entry_type)); diff --git a/src/file_scanner_analyzer/analyzer.rs b/src/file_scanner_analyzer/analyzer.rs index 849a355d..4fa41928 100644 --- a/src/file_scanner_analyzer/analyzer.rs +++ b/src/file_scanner_analyzer/analyzer.rs @@ -273,7 +273,7 @@ fn analyze_loaded_ast( file_path, hh_fixmes: &aast.1.fixmes, comments: &aast.1.comments, - file_contents: if config.migration_symbols.len() > 0 { + file_contents: if !config.migration_symbols.is_empty() { match fs::read_to_string(str_path) { Ok(str_file) => str_file, Err(_) => panic!("Could not read {}", str_path), diff --git a/src/js_interop/lib.rs b/src/js_interop/lib.rs index 07b7c703..397fdfba 100644 --- a/src/js_interop/lib.rs +++ b/src/js_interop/lib.rs @@ -15,11 +15,7 @@ pub struct ScannerAndAnalyzer { impl ScannerAndAnalyzer { #[wasm_bindgen(constructor)] pub fn new() -> Self { - console_error_panic_hook::set_once(); - - let (codebase, interner, file_system) = get_single_file_codebase(vec![]); - - Self { codebase, interner } + Self::default() } pub fn get_results(&mut self, file_contents: String) -> String { @@ -35,7 +31,7 @@ impl ScannerAndAnalyzer { self.interner = interner; let mut issue_json_objects = vec![]; - for (file_path, issues) in analysis_result.get_all_issues(&self.interner, &"", true) + for (file_path, issues) in analysis_result.get_all_issues(&self.interner, "", true) { for issue in issues { issue_json_objects.push(json!({ @@ -74,3 +70,13 @@ impl ScannerAndAnalyzer { } } } + +impl Default for ScannerAndAnalyzer { + fn default() -> Self { + console_error_panic_hook::set_once(); + + let (codebase, interner, _) = get_single_file_codebase(vec![]); + + Self { codebase, interner } + } +} diff --git a/src/ttype/type_comparator/atomic_type_comparator.rs b/src/ttype/type_comparator/atomic_type_comparator.rs index 3cf18574..a244f341 100644 --- a/src/ttype/type_comparator/atomic_type_comparator.rs +++ b/src/ttype/type_comparator/atomic_type_comparator.rs @@ -1035,7 +1035,7 @@ fn dicts_can_be_identical( } } (Some(type_1_known_items), None) => { - for (_, type_1_entry) in type_1_known_items { + for type_1_entry in type_1_known_items.values() { if let Some(type_2_dict_params) = type_2_dict_params { if !union_type_comparator::can_expression_types_be_identical( codebase, @@ -1051,7 +1051,7 @@ fn dicts_can_be_identical( } } (None, Some(type_2_known_items)) => { - for (_, type_2_entry) in type_2_known_items { + for type_2_entry in type_2_known_items.values() { if let Some(type_1_dict_params) = type_1_dict_params { if !union_type_comparator::can_expression_types_be_identical( codebase,