use crate::ir;
use crate::ir::analysis::reference_checker::collect_imported_packages;
use crate::ir::ast::{Expression, Import, OpBinary};
use crate::ir::error::IrError;
use crate::ir::transform::constants::is_primitive_type;
use anyhow::Result;
use indexmap::{IndexMap, IndexSet};
use super::{ClassDict, EXTENDS_CHAIN_CACHE, is_cache_enabled};
pub(super) fn extract_extends_modifications(
modifications: &[Expression],
) -> IndexMap<String, Expression> {
let mut result = IndexMap::new();
for expr in modifications {
if let Expression::Binary { op, lhs, rhs } = expr
&& matches!(op, OpBinary::Eq(_))
&& let Expression::ComponentReference(comp_ref) = &**lhs
{
let param_name = comp_ref.to_string();
result.insert(param_name, (**rhs).clone());
}
}
result
}
pub(super) fn build_import_aliases(imports: &[Import]) -> IndexMap<String, String> {
let mut aliases = IndexMap::new();
for import in imports {
match import {
Import::Renamed { alias, path, .. } => {
aliases.insert(alias.text.clone(), path.to_string());
}
Import::Qualified { path, .. } => {
if let Some(last) = path.name.last() {
aliases.insert(last.text.clone(), path.to_string());
}
}
Import::Unqualified { .. } => {
}
Import::Selective { path, names, .. } => {
let base_path = path.to_string();
for name in names {
aliases.insert(name.text.clone(), format!("{}.{}", base_path, name.text));
}
}
}
}
aliases
}
pub(super) fn apply_import_aliases(name: &str, aliases: &IndexMap<String, String>) -> String {
let parts: Vec<&str> = name.split('.').collect();
if parts.is_empty() {
return name.to_string();
}
let first = parts[0];
if let Some(target) = aliases.get(first) {
if parts.len() == 1 {
target.clone()
} else {
format!("{}.{}", target, parts[1..].join("."))
}
} else {
name.to_string()
}
}
pub(super) fn build_import_aliases_for_class(
class_path: &str,
class_dict: &ClassDict,
) -> IndexMap<String, String> {
let mut all_aliases = IndexMap::new();
let parts: Vec<&str> = class_path.split('.').collect();
for i in (1..=parts.len()).rev() {
let path = parts[..i].join(".");
if let Some(class) = class_dict.get(&path) {
let aliases = build_import_aliases(&class.imports);
for (alias, target) in aliases {
all_aliases.entry(alias).or_insert(target);
}
let mut visited = IndexSet::new();
let pkg_aliases =
collect_package_aliases_recursive(class, &path, class_dict, &mut visited);
for (alias, target) in pkg_aliases {
all_aliases.entry(alias).or_insert(target);
}
}
}
all_aliases
}
pub(super) fn path_exists_in_dict(path: &str, class_dict: &ClassDict) -> bool {
if class_dict.contains_key(path) {
return true;
}
if let Some(dot_pos) = path.rfind('.') {
let parent_path = &path[..dot_pos];
let component_name = &path[dot_pos + 1..];
if let Some(parent_class) = class_dict.get(parent_path) {
if parent_class.components.contains_key(component_name) {
return true;
}
if parent_class.classes.contains_key(component_name) {
return true;
}
}
}
false
}
pub(super) fn validate_imports(imports: &[Import], class_dict: &ClassDict) -> Result<()> {
for import in imports {
match import {
Import::Renamed { path, .. } | Import::Qualified { path, .. } => {
let target = path.to_string();
if !path_exists_in_dict(&target, class_dict) {
return Err(IrError::ImportClassNotFound(target).into());
}
}
Import::Selective { path, names, .. } => {
let base_path = path.to_string();
for name in names {
let full_path = format!("{}.{}", base_path, name.text);
if !path_exists_in_dict(&full_path, class_dict) {
return Err(IrError::ImportClassNotFound(full_path).into());
}
}
}
Import::Unqualified { path, .. } => {
let target = path.to_string();
if !class_dict.contains_key(&target) {
return Err(IrError::ImportClassNotFound(target).into());
}
}
}
}
Ok(())
}
pub(super) fn build_package_aliases(class: &ir::ast::ClassDefinition) -> IndexMap<String, String> {
use ir::ast::ClassType;
let mut aliases = IndexMap::new();
for (name, nested_class) in &class.classes {
match nested_class.class_type {
ClassType::Type | ClassType::Connector => continue,
_ => {}
}
if nested_class.extends.len() == 1
&& nested_class.components.is_empty()
&& nested_class.equations.is_empty()
{
let target = nested_class.extends[0].comp.to_string();
aliases.insert(name.clone(), target);
}
}
aliases
}
pub(super) fn collect_package_aliases_recursive(
class: &ir::ast::ClassDefinition,
class_path: &str,
class_dict: &ClassDict,
visited: &mut IndexSet<String>,
) -> IndexMap<String, String> {
let mut aliases = IndexMap::new();
if visited.contains(class_path) {
return aliases;
}
visited.insert(class_path.to_string());
let pkg_aliases = build_package_aliases(class);
for (alias, target) in pkg_aliases {
let resolved_target =
resolve_relative_to_package(&target, class_path, class_dict).unwrap_or(target);
aliases.entry(alias).or_insert(resolved_target);
}
for extend in &class.extends {
let parent_name = extend.comp.to_string();
if is_primitive_type(&parent_name) {
continue;
}
let parent_parts: Vec<&str> = class_path.split('.').collect();
let mut resolved_parent = None;
for i in (0..=parent_parts.len()).rev() {
let prefix = parent_parts[..i].join(".");
let candidate = if prefix.is_empty() {
parent_name.clone()
} else {
format!("{}.{}", prefix, parent_name)
};
if class_dict.contains_key(&candidate) {
resolved_parent = Some(candidate);
break;
}
}
if let Some(parent_class) = resolved_parent.as_ref().and_then(|p| class_dict.get(p)) {
let parent_path = resolved_parent.unwrap();
let parent_aliases =
collect_package_aliases_recursive(parent_class, &parent_path, class_dict, visited);
for (alias, target) in parent_aliases {
aliases.entry(alias).or_insert(target);
}
}
}
aliases
}
pub(super) fn collect_imported_packages_for_class(
class_path: &str,
class_dict: &ClassDict,
) -> std::collections::HashSet<String> {
let mut globals = std::collections::HashSet::new();
let parts: Vec<&str> = class_path.split('.').collect();
for i in (1..=parts.len()).rev() {
let path = parts[..i].join(".");
if let Some(class) = class_dict.get(&path) {
let level_packages = collect_imported_packages(&class.imports);
globals.extend(level_packages);
for import in &class.imports {
match import {
Import::Renamed { alias, .. } => {
globals.insert(alias.text.clone());
}
Import::Qualified { path, .. } => {
if let Some(last) = path.name.last() {
globals.insert(last.text.clone());
}
}
Import::Selective { names, .. } => {
for name in names {
globals.insert(name.text.clone());
}
}
Import::Unqualified { .. } => {
}
}
}
for nested_name in class.classes.keys() {
globals.insert(nested_name.clone());
}
}
}
globals
}
pub(super) fn resolve_relative_to_package(
parent_name: &str,
package_context: &str,
class_dict: &ClassDict,
) -> Option<String> {
if class_dict.contains_key(parent_name) {
return Some(parent_name.to_string());
}
let parts: Vec<&str> = package_context.split('.').collect();
for i in (0..=parts.len()).rev() {
let prefix = parts[..i].join(".");
let candidate = if prefix.is_empty() {
parent_name.to_string()
} else {
format!("{}.{}", prefix, parent_name)
};
if class_dict.contains_key(&candidate) {
return Some(candidate);
}
}
None
}
pub(super) fn find_type_in_extends_chain(
type_name: &str,
class_path: &str,
class_dict: &ClassDict,
visited: &mut IndexSet<String>,
) -> Option<String> {
if visited.is_empty() && is_cache_enabled() {
let cache_key = (type_name.to_string(), class_path.to_string());
if let Some(result) = EXTENDS_CHAIN_CACHE
.read()
.ok()
.and_then(|cache| cache.get(&cache_key).cloned())
{
return result;
}
}
let result = find_type_in_extends_chain_uncached(type_name, class_path, class_dict, visited);
if visited.len() == 1 && is_cache_enabled() {
let cache_key = (type_name.to_string(), class_path.to_string());
if let Ok(mut cache) = EXTENDS_CHAIN_CACHE.write() {
cache.insert(cache_key, result.clone());
}
}
result
}
fn find_type_in_extends_chain_uncached(
type_name: &str,
class_path: &str,
class_dict: &ClassDict,
visited: &mut IndexSet<String>,
) -> Option<String> {
if visited.contains(class_path) {
return None;
}
visited.insert(class_path.to_string());
let candidate = format!("{}.{}", class_path, type_name);
if class_dict.contains_key(&candidate) {
return Some(candidate);
}
let parts: Vec<&str> = class_path.split('.').collect();
for i in (0..parts.len()).rev() {
let prefix = parts[..i].join(".");
let candidate = if prefix.is_empty() {
type_name.to_string()
} else {
format!("{}.{}", prefix, type_name)
};
if class_dict.contains_key(&candidate) {
return Some(candidate);
}
}
if let Some(class_def) = class_dict.get(class_path) {
for extend in &class_def.extends {
let parent_name = extend.comp.to_string();
if let Some(found) = resolve_relative_to_package(&parent_name, class_path, class_dict)
.and_then(|parent_path| {
find_type_in_extends_chain_uncached(
type_name,
&parent_path,
class_dict,
visited,
)
})
{
return Some(found);
}
}
}
None
}
pub(super) fn resolve_class_name_with_imports(
name: &str,
current_class_path: &str,
class_dict: &ClassDict,
import_aliases: &IndexMap<String, String>,
) -> Option<String> {
let resolved_name = apply_import_aliases(name, import_aliases);
if class_dict.contains_key(&resolved_name) {
return Some(resolved_name);
}
let name_parts: Vec<&str> = name.split('.').collect();
if name_parts.len() >= 2 {
let first = name_parts[0];
if let Some(target) = import_aliases.get(first) {
let remainder = &name_parts[1..].join(".");
let mut visited = IndexSet::new();
if let Some(found) =
find_type_in_extends_chain(remainder, target, class_dict, &mut visited)
{
return Some(found);
}
let target_parts: Vec<&str> = target.split('.').collect();
for i in (0..target_parts.len()).rev() {
let prefix = target_parts[..i].join(".");
let candidate = if prefix.is_empty() {
remainder.to_string()
} else {
format!("{}.{}", prefix, remainder)
};
if class_dict.contains_key(&candidate) {
return Some(candidate);
}
}
}
}
let parts: Vec<&str> = current_class_path.split('.').collect();
for i in (0..=parts.len()).rev() {
let prefix = parts[..i].join(".");
let candidate = if prefix.is_empty() {
resolved_name.clone()
} else {
format!("{}.{}", prefix, resolved_name)
};
if class_dict.contains_key(&candidate) {
return Some(candidate);
}
}
for i in (1..=parts.len()).rev() {
let class_path = parts[..i].join(".");
let mut visited = IndexSet::new();
if let Some(found) =
find_type_in_extends_chain(&resolved_name, &class_path, class_dict, &mut visited)
{
return Some(found);
}
}
if resolved_name != name {
if class_dict.contains_key(name) {
return Some(name.to_string());
}
for i in (0..=parts.len()).rev() {
let prefix = parts[..i].join(".");
let candidate = if prefix.is_empty() {
name.to_string()
} else {
format!("{}.{}", prefix, name)
};
if class_dict.contains_key(&candidate) {
return Some(candidate);
}
}
}
None
}