From bfb5c933c3f93a9b8144bdb6e46af1b524f3ace6 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Mon, 17 Aug 2026 04:49:52 +0200 Subject: [PATCH 1/2] fix: preserve aliased native constructor proofs --- crates/perry-codegen/src/type_analysis.rs | 2 + .../perry-codegen/src/type_analysis/refine.rs | 56 ++++++++++++++++++- .../perry-codegen/src/type_analysis_tests.rs | 51 +++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/crates/perry-codegen/src/type_analysis.rs b/crates/perry-codegen/src/type_analysis.rs index 16f6d4ace7..1c27da0dbd 100644 --- a/crates/perry-codegen/src/type_analysis.rs +++ b/crates/perry-codegen/src/type_analysis.rs @@ -51,6 +51,8 @@ pub(crate) use predicates::{ // `super::*`) can keep calling `tuple_index_literal` directly. #[cfg(test)] pub(crate) use predicates::tuple_index_literal; +#[cfg(test)] +pub(crate) use refine::is_imported_native_constructor_class; pub(crate) use refine::{ compute_auto_captures, declared_array_property_claim, is_crypto_digest_chain, is_global_constructor_expr, is_process_namespace_version_property, proven_type_from_init, diff --git a/crates/perry-codegen/src/type_analysis/refine.rs b/crates/perry-codegen/src/type_analysis/refine.rs index f9de5a2cad..94bf23519d 100644 --- a/crates/perry-codegen/src/type_analysis/refine.rs +++ b/crates/perry-codegen/src/type_analysis/refine.rs @@ -259,14 +259,66 @@ pub(crate) fn proven_type_from_init(ctx: &FnCtx<'_>, init: &Expr) -> Option + { + Some(HirType::Named(class_name.clone())) + } + // A user constructor can explicitly return a different object, so + // `new C` proves only Object, never C's class-specific layout. Expr::New { .. } => Some(HirType::Object(Default::default())), Expr::BigInt(_) => Some(HirType::BigInt), _ => None, } } +pub(crate) fn is_imported_native_constructor_class( + imported_class_sources: &std::collections::HashMap, + imported_class_original_names: &std::collections::HashMap, + class_name: &str, +) -> bool { + let alias_sources = imported_class_original_names + .iter() + .filter(|(_, original)| original.as_str() == class_name) + .filter_map(|(local, _)| imported_class_sources.get(local)); + let candidates = imported_class_sources + .get(class_name) + .into_iter() + .chain(alias_sources); + let mut resolved_module = None; + for source in candidates { + let module = source.strip_prefix("node:").unwrap_or(source); + if resolved_module.is_some_and(|resolved| resolved != module) { + // The canonical name can be imported from multiple sources in one + // module. Once HIR has erased an alias to the canonical name, that + // shape is ambiguous and must not become a class-specific proof. + return false; + } + resolved_module = Some(module); + } + let Some(module) = resolved_module else { + return false; + }; + + perry_api_manifest::API_MANIFEST.iter().any(|entry| { + entry.module == module + && entry.name == class_name + && matches!(entry.kind, perry_api_manifest::ApiKind::Class) + }) +} + /// Refine an `Any`-typed local's static type based on its initializer /// expression. Returns Some(Type) when we can statically prove the /// initializer produces a more specific type, so the `Stmt::Let` diff --git a/crates/perry-codegen/src/type_analysis_tests.rs b/crates/perry-codegen/src/type_analysis_tests.rs index 1eb2639659..0d694d86e5 100644 --- a/crates/perry-codegen/src/type_analysis_tests.rs +++ b/crates/perry-codegen/src/type_analysis_tests.rs @@ -2,6 +2,57 @@ use super::*; use perry_hir::infer_expr_type; use std::collections::{HashMap, HashSet}; +#[test] +fn imported_native_constructor_proof_recovers_canonical_alias() { + let aliased_sources = HashMap::from([("Sk".to_string(), "net".to_string())]); + let aliased_names = HashMap::from([("Sk".to_string(), "Socket".to_string())]); + assert!(is_imported_native_constructor_class( + &aliased_sources, + &aliased_names, + "Socket" + )); + + let direct_sources = HashMap::from([("Socket".to_string(), "node:net".to_string())]); + assert!(is_imported_native_constructor_class( + &direct_sources, + &HashMap::new(), + "Socket" + )); +} + +#[test] +fn imported_native_constructor_proof_rejects_non_native_or_non_class_imports() { + let local_sources = HashMap::from([("Sk".to_string(), "./dep".to_string())]); + let socket_alias = HashMap::from([("Sk".to_string(), "Socket".to_string())]); + assert!(!is_imported_native_constructor_class( + &local_sources, + &socket_alias, + "Socket" + )); + + let net_sources = HashMap::from([("c".to_string(), "net".to_string())]); + let function_alias = HashMap::from([("c".to_string(), "connect".to_string())]); + assert!(!is_imported_native_constructor_class( + &net_sources, + &function_alias, + "connect" + )); + + let ambiguous_sources = HashMap::from([ + ("Sk".to_string(), "net".to_string()), + ("LocalSocket".to_string(), "./dep".to_string()), + ]); + let ambiguous_aliases = HashMap::from([ + ("Sk".to_string(), "Socket".to_string()), + ("LocalSocket".to_string(), "Socket".to_string()), + ]); + assert!(!is_imported_native_constructor_class( + &ambiguous_sources, + &ambiguous_aliases, + "Socket" + )); +} + #[test] fn hir_inferred_refinable_type_reuses_codegen_local_types() { let mut local_types = HashMap::new(); From 706c31b739e91cef6af7b4250f15c2b29ca4819d Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Mon, 17 Aug 2026 05:10:31 +0200 Subject: [PATCH 2/2] docs: add changelog for PR 8278 --- changelog.d/8278-aliased-native-constructor-proofs.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog.d/8278-aliased-native-constructor-proofs.md diff --git a/changelog.d/8278-aliased-native-constructor-proofs.md b/changelog.d/8278-aliased-native-constructor-proofs.md new file mode 100644 index 0000000000..6891d09044 --- /dev/null +++ b/changelog.d/8278-aliased-native-constructor-proofs.md @@ -0,0 +1,7 @@ +### fix(codegen): preserve aliased native constructor proofs + +Native classes imported under a local alias now retain their canonical +constructor proof through codegen. Reading `net.Socket` methods as values from +an aliased instance therefore returns bound functions, matching the unaliased +import. The proof remains gated to unambiguous native class entries in the API +manifest. Fixes #8222.