From b06b407c0dd4c02da38f8f97b9438141e81c63a9 Mon Sep 17 00:00:00 2001 From: Chris Rybicki Date: Mon, 26 Aug 2024 15:02:04 -0400 Subject: [PATCH] chore(compiler): store FQN information for types (#7047) --- Cargo.lock | 7 +- apps/wingcli-v2/Cargo.toml | 2 +- apps/wingcli-v2/src/main.rs | 53 +++++-- libs/wingc/examples/compile.rs | 2 +- libs/wingc/src/dtsify/extern_dtsify.rs | 4 +- .../src/dtsify/snapshots/declarations.snap | 2 +- libs/wingc/src/lib.rs | 74 +++++---- libs/wingc/src/lifting.rs | 2 +- libs/wingc/src/lsp/sync.rs | 30 +++- libs/wingc/src/parser.rs | 53 ++++++- libs/wingc/src/test_utils.rs | 4 +- libs/wingc/src/type_check.rs | 145 +++++++++++++++--- libs/wingc/src/type_check/jsii_importer.rs | 8 +- libs/wingc/src/type_check/symbol_env.rs | 2 + libs/wingcompiler/src/compile.ts | 4 +- ..._extend_non_entry.test.w_compile_tf-aws.md | 2 +- .../bring_local.test.w_compile_tf-aws.md | 10 +- .../bring_local_dir.test.w_compile_tf-aws.md | 8 +- ...ring_wing_library.test.w_compile_tf-aws.md | 14 +- ..._preflight_object.test.w_compile_tf-aws.md | 6 +- ...t_shared_resource.test.w_compile_tf-aws.md | 4 +- .../new_in_static.test.w_compile_tf-aws.md | 2 +- .../new_in_static_lib.w_compile_tf-aws.md | 2 +- .../struct_from_json.test.w_compile_tf-aws.md | 2 +- tools/hangar/src/unsupported.test.ts | 2 +- 25 files changed, 325 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72b67505c2b..32e2bb3a522 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,11 +1022,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1657,7 +1658,7 @@ dependencies = [ [[package]] name = "wingcli" -version = "0.74.53" +version = "0.83.2" dependencies = [ "anstyle", "camino", diff --git a/apps/wingcli-v2/Cargo.toml b/apps/wingcli-v2/Cargo.toml index f700753167b..da473bd7919 100644 --- a/apps/wingcli-v2/Cargo.toml +++ b/apps/wingcli-v2/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wingcli" -version = "0.74.53" +version = "0.83.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/apps/wingcli-v2/src/main.rs b/apps/wingcli-v2/src/main.rs index 3107bff85c3..d1f20bd4078 100644 --- a/apps/wingcli-v2/src/main.rs +++ b/apps/wingcli-v2/src/main.rs @@ -1,6 +1,12 @@ mod cli; -use std::{error::Error, io::Write, path::PathBuf, time::Instant}; +use std::{ + error::Error, + io::Write, + path::PathBuf, + sync::{Mutex, Once}, + time::Instant, +}; use camino::{Utf8Path, Utf8PathBuf}; use clap::Parser; @@ -8,7 +14,7 @@ use cli::{print_compiled, print_compiling, print_installing}; use home::home_dir; use lazy_static::lazy_static; use strum::{Display, EnumString}; -use wingc::compile; +use wingc::{compile, diagnostic::get_diagnostics}; lazy_static! { static ref HOME_PATH: PathBuf = home_dir().expect("Could not find home directory"); @@ -80,29 +86,29 @@ fn command_build(source_file: Utf8PathBuf, target: Option) -> Result<(), )); let work_dir = target_dir.join(".wing"); - // Print that work is being done + // Print "Compiling..." print_compiling(source_file.as_str()); let sdk_root = WING_CACHE_DIR.join("node_modules").join("@winglang").join("sdk"); - // Skip installing the SDK here if we're in a unit test since tests may run in parallel - // TODO: check if the SDK is up to date - if !sdk_root.exists() && !cfg!(test) { - install_sdk()?; - } - tracing::info!("Using SDK at {}", sdk_root); + install_sdk()?; + tracing::info!("Using Wing SDK at {}", sdk_root); // Special pragma used by wingc to find the SDK types if !cfg!(test) { std::env::set_var("WINGSDK_MANIFEST_ROOT", &sdk_root); } - let result = compile(&project_dir, &source_file, None, &work_dir); + let result = compile(&source_file, None, &work_dir); match result { Ok(_) => {} Err(error) => { tracing::error!(error = ?error, "Failed"); + let diagnostics = get_diagnostics(); + for diagnostic in diagnostics { + eprintln!("{}", diagnostic); + } return Err("Compiler error".into()); } } @@ -113,7 +119,23 @@ fn command_build(source_file: Utf8PathBuf, target: Option) -> Result<(), Ok(()) } +// Create a mutex to ensure that the SDK is only installed once +lazy_static! { + static ref INSTALL_SDK_MUTEX: Mutex<()> = Mutex::new(()); +} + +static INSTALL_SDK_INIT: Once = Once::new(); + fn install_sdk() -> Result<(), Box> { + let _guard = INSTALL_SDK_MUTEX.lock().unwrap(); + let mut result = Ok(()); + INSTALL_SDK_INIT.call_once(|| { + result = install_sdk_helper(); + }); + result +} + +fn install_sdk_helper() -> Result<(), Box> { print_installing("Wing SDK"); std::fs::create_dir_all(WING_CACHE_DIR.as_str())?; @@ -173,14 +195,16 @@ fn run_javascript_node(source_file: &Utf8Path, target_dir: &Utf8Path, target: Ta } fn initialize_logger() { - let enable_logs = std::env::var("WING_LOG").is_ok_and(|x| !x.is_empty() && x != "off" && x != "0"); + let enable_logs = std::env::var("DEBUG").is_ok_and(|x| !x.is_empty() && x != "off" && x != "0"); let enable_colours = std::env::var("WING_LOG_NOCOLOR").is_err(); + let show_thread_names = cfg!(test); if enable_logs { tracing_subscriber::fmt() .with_writer(std::io::stderr) .with_target(false) .with_ansi(enable_colours) .without_time() + .with_thread_names(show_thread_names) .init(); } } @@ -216,23 +240,22 @@ mod test { static INIT: Once = Once::new(); - fn initialize() { + fn setup() { INIT.call_once(|| { initialize_logger(); - install_sdk().expect("Failed to install SDK"); }); } #[test] fn test_compile_sim() { - initialize(); + setup(); let res = command_build("../../examples/tests/valid/hello.test.w".into(), Some(Target::Sim)); res.expect("Failed to compile to sim"); } #[test] fn test_compile_tfaws() { - initialize(); + setup(); let res = command_build("../../examples/tests/valid/hello.test.w".into(), Some(Target::TfAws)); res.expect("Failed to compile to tf-aws"); } diff --git a/libs/wingc/examples/compile.rs b/libs/wingc/examples/compile.rs index 0525d17e392..f93b70943b5 100644 --- a/libs/wingc/examples/compile.rs +++ b/libs/wingc/examples/compile.rs @@ -15,7 +15,7 @@ pub fn main() { let source_path = Utf8Path::new(&args[1]).canonicalize_utf8().unwrap(); let target_dir: Utf8PathBuf = env::current_dir().unwrap().join("target").try_into().unwrap(); - let _ = compile(source_path.parent().unwrap(), &source_path, None, &target_dir); + let _ = compile(&source_path, None, &target_dir); let mut diags = get_diagnostics(); if !diags.is_empty() { // Sort error messages by line number (ascending) diff --git a/libs/wingc/src/dtsify/extern_dtsify.rs b/libs/wingc/src/dtsify/extern_dtsify.rs index 71c28779759..7fbcd359eba 100644 --- a/libs/wingc/src/dtsify/extern_dtsify.rs +++ b/libs/wingc/src/dtsify/extern_dtsify.rs @@ -189,8 +189,8 @@ impl<'a> ExternDTSifier<'a> { fn resolve_named_type(&mut self, type_: TypeRef, is_inflight: bool) -> String { let fqn = match &*type_ { Type::Class(c) => c.fqn.as_ref().unwrap_or(&c.name.span.file_id), - Type::Interface(i) => i.fqn.as_ref().unwrap_or(&i.name.span.file_id), - Type::Struct(s) => s.fqn.as_ref().unwrap_or(&s.name.span.file_id), + Type::Interface(i) => &i.fqn, + Type::Struct(s) => &s.fqn, Type::Enum(e) => &e.name.span.file_id, _ => panic!("Not a named type"), }; diff --git a/libs/wingc/src/dtsify/snapshots/declarations.snap b/libs/wingc/src/dtsify/snapshots/declarations.snap index f4ea2933368..e0f6734b4a0 100644 --- a/libs/wingc/src/dtsify/snapshots/declarations.snap +++ b/libs/wingc/src/dtsify/snapshots/declarations.snap @@ -179,7 +179,7 @@ class ParentClass extends $stdlib.std.Resource { }); } } -class Child extends ParentClass { +class Child extends (globalThis.$ClassFactory.resolveType("rootpkg.ParentClass") ?? ParentClass) { constructor($scope, $id, ) { super($scope, $id); } diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index 5267c4c9863..d8d7c72578d 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -212,9 +212,9 @@ pub unsafe extern "C" fn wingc_compile(ptr: u32, len: u32) -> u64 { let args = ptr_to_str(ptr, len); let split = args.split(";").collect::>(); - if split.len() != 3 { + if split.len() != 2 { report_diagnostic(Diagnostic { - message: format!("Expected 3 arguments to wingc_compile, got {}", split.len()), + message: format!("Expected 2 arguments to wingc_compile, got {}", split.len()), span: None, annotations: vec![], hints: vec![], @@ -224,10 +224,6 @@ pub unsafe extern "C" fn wingc_compile(ptr: u32, len: u32) -> u64 { } let source_path = Utf8Path::new(split[0]); let output_dir = split.get(1).map(|s| Utf8Path::new(s)).expect("output dir not provided"); - let project_dir = split - .get(2) - .map(|s| Utf8Path::new(s)) - .expect("project dir not provided"); if !source_path.exists() { report_diagnostic(Diagnostic { @@ -240,7 +236,7 @@ pub unsafe extern "C" fn wingc_compile(ptr: u32, len: u32) -> u64 { return WASM_RETURN_ERROR; } - let results = compile(project_dir, source_path, None, output_dir); + let results = compile(source_path, None, output_dir); if let Ok(results) = results { string_to_combined_ptr(serde_json::to_string(&results).unwrap()) @@ -254,6 +250,7 @@ pub fn type_check( types: &mut Types, file: &File, file_graph: &FileGraph, + library_roots: &mut IndexMap, jsii_types: &mut TypeSystem, jsii_imports: &mut Vec, ) { @@ -267,7 +264,7 @@ pub fn type_check( types.set_scope_env(scope, env); - let mut tc = TypeChecker::new(types, file, file_graph, jsii_types, jsii_imports); + let mut tc = TypeChecker::new(types, file, file_graph, library_roots, jsii_types, jsii_imports); tc.add_jsii_module_to_env( &mut env, WINGSDK_ASSEMBLY_NAME.to_string(), @@ -283,18 +280,45 @@ pub fn type_check( tc.add_this(&mut env); } - tc.type_check_file_or_dir(file, scope); + tc.type_check_file_or_dir(scope); } -pub fn compile( - project_dir: &Utf8Path, - source_path: &Utf8Path, - source_text: Option, - out_dir: &Utf8Path, -) -> Result { - let source_package = as_wing_library(project_dir).unwrap_or_else(|| DEFAULT_PACKAGE_NAME.to_string()); +/// Infer the root directory of the current Wing application or library. +/// +/// Check the current file's directory for a wing.toml file or package.json file, +/// and continue searching up the directory tree until we find one. +/// If we run out of parent directories, fall back to the first directory we found. +pub fn find_nearest_wing_project_dir(source_path: &Utf8Path) -> Utf8PathBuf { + let initial_dir: Utf8PathBuf = if source_path.is_dir() { + source_path.to_owned() + } else { + source_path.parent().unwrap_or_else(|| Utf8Path::new("/")).to_owned() + }; + let mut current_dir = initial_dir.as_path(); + loop { + if current_dir.join("wing.toml").exists() { + return current_dir.to_owned(); + } + if current_dir.join("package.json").exists() { + return current_dir.to_owned(); + } + if current_dir == "/" { + break; + } + current_dir = current_dir.parent().unwrap_or_else(|| Utf8Path::new("/")); + } + return initial_dir; +} + +pub fn compile(source_path: &Utf8Path, source_text: Option, out_dir: &Utf8Path) -> Result { + let project_dir = find_nearest_wing_project_dir(source_path); + let source_package = as_wing_library(&project_dir).unwrap_or_else(|| DEFAULT_PACKAGE_NAME.to_string()); let source_path = normalize_path(source_path, None); - let source_file = File::new(&source_path, source_package); + let source_file = File::new(&source_path, source_package.clone()); + + // A map from package names to their root directories + let mut library_roots: IndexMap = IndexMap::new(); + library_roots.insert(source_package, project_dir.to_owned()); // -- PARSING PHASE -- let mut files = Files::new(); @@ -306,6 +330,7 @@ pub fn compile( source_text, &mut files, &mut file_graph, + &mut library_roots, &mut tree_sitter_trees, &mut asts, ); @@ -342,6 +367,7 @@ pub fn compile( &mut types, &file, &file_graph, + &mut library_roots, &mut jsii_types, &mut jsii_imports, ); @@ -361,18 +387,6 @@ pub fn compile( asts.insert(file.path.to_owned(), scope); } - // Verify that the project dir is absolute - if !is_absolute_path(&project_dir) { - report_diagnostic(Diagnostic { - message: format!("Project directory must be absolute: {}", project_dir), - span: None, - annotations: vec![], - hints: vec![], - severity: DiagnosticSeverity::Error, - }); - return Err(()); - } - let mut jsifier = JSifier::new(&mut types, &files, &file_graph, &source_path, &out_dir); // -- LIFTING PHASE -- @@ -505,7 +519,7 @@ mod sanity { fs::remove_dir_all(&out_dir).expect("remove out dir"); } - let result = compile(&test_dir, &test_file, None, &out_dir); + let result = compile(&test_file, None, &out_dir); if result.is_err() { assert!( diff --git a/libs/wingc/src/lifting.rs b/libs/wingc/src/lifting.rs index 7503b92464c..0b600915ec4 100644 --- a/libs/wingc/src/lifting.rs +++ b/libs/wingc/src/lifting.rs @@ -160,7 +160,7 @@ impl<'a> LiftVisitor<'a> { let fqn = if let Some(class) = type_.as_ref().and_then(|t| t.as_class()) { class.fqn.clone() } else if let Some(iface) = type_.as_ref().and_then(|t| t.as_interface()) { - iface.fqn.clone() + Some(iface.fqn.clone()) } else { None }; diff --git a/libs/wingc/src/lsp/sync.rs b/libs/wingc/src/lsp/sync.rs index 725ab9d0c10..9586567a3bf 100644 --- a/libs/wingc/src/lsp/sync.rs +++ b/libs/wingc/src/lsp/sync.rs @@ -22,7 +22,7 @@ use crate::valid_json_visitor::ValidJsonVisitor; use crate::visit::Visit; use crate::wasm_util::extern_json_fn; use crate::{ast::Scope, type_check::Types}; -use crate::{type_check, DEFAULT_PACKAGE_NAME}; +use crate::{find_nearest_wing_project_dir, type_check, DEFAULT_PACKAGE_NAME}; /// The output of compiling a Wing project with one or more files pub struct ProjectData { @@ -30,6 +30,8 @@ pub struct ProjectData { pub files: Files, /// A graph that tracks the dependencies between files pub file_graph: FileGraph, + /// A map from library names to their root paths + pub library_roots: IndexMap, /// tree-sitter trees pub trees: IndexMap, /// AST for each file @@ -44,6 +46,7 @@ impl ProjectData { ProjectData { files: Files::new(), file_graph: FileGraph::default(), + library_roots: IndexMap::new(), trees: IndexMap::new(), asts: IndexMap::new(), jsii_imports: Vec::new(), @@ -137,6 +140,7 @@ fn partial_compile( reset_diagnostics(); let source_path = Utf8Path::from_path(source_path).expect("invalid unicode path"); + let project_dir = find_nearest_wing_project_dir(source_path); let source_path = normalize_path(source_path, None); let source_package = if let Some(file) = project_data @@ -152,11 +156,30 @@ fn partial_compile( }; let source_file = File::new(source_path, source_package); + // If this is a file from a project or package we haven't seen before, + // add it to the library roots + if !project_data.library_roots.contains_key(source_package) { + project_data + .library_roots + .insert(source_package.to_string(), project_dir); + } else if is_parent_of(&project_dir, project_data.library_roots.get(source_package).unwrap()) { + // The information about the library root could be wrong - for example, + // the user might open `foo/bar.w`` in their IDE first, and then + // `other.w` after. Without any other information, we will first assume that + // `foo/` is the root of the library, and then after seeing the other file, + // we will update our assumption to `./` through this branch. + // This is kludgey. + project_data + .library_roots + .insert(source_package.to_string(), project_dir); + } + let topo_sorted_files = parse_wing_project( &source_file, Some(source_text), &mut project_data.files, &mut project_data.file_graph, + &mut project_data.library_roots, &mut project_data.trees, &mut project_data.asts, ); @@ -189,6 +212,7 @@ fn partial_compile( &mut types, &file, &project_data.file_graph, + &mut project_data.library_roots, jsii_types, &mut project_data.jsii_imports, ); @@ -235,6 +259,10 @@ pub fn check_utf8(path: PathBuf) -> Utf8PathBuf { path.try_into().expect("invalid unicode path") } +fn is_parent_of(parent: &Utf8Path, child: &Utf8Path) -> bool { + child.starts_with(parent) && parent != child +} + #[cfg(test)] pub mod test_utils { use regex::Regex; diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index aaad5ff4807..9a73f7d39b7 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use phf::{phf_map, phf_set}; use regex::Regex; use std::cell::RefCell; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::ops::Range; use std::{fs, str, vec}; use tree_sitter::Node; @@ -160,6 +160,7 @@ pub fn parse_wing_project( init_text: Option, files: &mut Files, file_graph: &mut FileGraph, + library_roots: &mut IndexMap, tree_sitter_trees: &mut IndexMap, asts: &mut IndexMap, ) -> Vec { @@ -173,7 +174,15 @@ pub fn parse_wing_project( tree_sitter_trees, asts, ), - false => parse_wing_file(&init_file, init_text, files, file_graph, tree_sitter_trees, asts), + false => parse_wing_file( + &init_file, + init_text, + files, + file_graph, + library_roots, + tree_sitter_trees, + asts, + ), }; // Store a stack of files that still need parsing @@ -198,7 +207,15 @@ pub fn parse_wing_project( // Parse the file or directory let dependent_wing_paths = match file_or_dir.path.is_dir() { true => parse_wing_directory(&file_or_dir, &source_ref, files, file_graph, tree_sitter_trees, asts), - false => parse_wing_file(&file_or_dir, None, files, file_graph, tree_sitter_trees, asts), + false => parse_wing_file( + &file_or_dir, + None, + files, + file_graph, + library_roots, + tree_sitter_trees, + asts, + ), }; // Add the dependent files to the stack of files to parse @@ -234,6 +251,7 @@ fn parse_wing_file( source_text: Option, files: &mut Files, file_graph: &mut FileGraph, + library_roots: &mut IndexMap, tree_sitter_trees: &mut IndexMap, asts: &mut IndexMap, ) -> Vec<(File, WingSpan)> { @@ -262,7 +280,10 @@ fn parse_wing_file( // Parse the source text into an AST let parser = Parser::new(&source_text.as_bytes(), source_file.to_owned()); - let (scope, dependent_wing_paths) = parser.parse(&tree_sitter_root); + let (scope, dependent_wing_paths, found_library_roots) = parser.parse(&tree_sitter_root); + for (name, path) in found_library_roots { + library_roots.insert(name, path); + } // Update our collections of trees and ASTs and our file graph tree_sitter_trees.insert(source_file.path.to_owned(), tree_sitter_tree); @@ -383,9 +404,13 @@ pub struct Parser<'a> { in_json: RefCell, is_in_mut_json: RefCell, is_in_loop: RefCell, + /// Track all files (and where they appear in the source) that have been found while parsing the current file /// These will need to be eventually parsed (or diagnostics will be reported if they don't exist) referenced_wing_files: RefCell>, + + /// Track the roots of all libraries that have been found while parsing the current file + found_library_roots: RefCell>, } struct ParseErrorBuilder<'s> { @@ -428,10 +453,11 @@ impl<'s> Parser<'s> { in_json: RefCell::new(0), is_in_mut_json: RefCell::new(false), referenced_wing_files: RefCell::new(Vec::new()), + found_library_roots: RefCell::new(HashMap::new()), } } - pub fn parse(self, root: &Node) -> (Scope, Vec<(File, WingSpan)>) { + pub fn parse(self, root: &Node) -> (Scope, Vec<(File, WingSpan)>, HashMap) { let scope = match root.kind() { "source" => self.build_scope(&root, Phase::Preflight), _ => Scope::empty(), @@ -451,7 +477,11 @@ impl<'s> Parser<'s> { self.report_unhandled_errors(&root); - (scope, self.referenced_wing_files.into_inner()) + ( + scope, + self.referenced_wing_files.into_inner(), + self.found_library_roots.into_inner(), + ) } fn add_error(&self, message: impl ToString, node: &Node) { @@ -1103,6 +1133,11 @@ impl<'s> Parser<'s> { .err(); })?; + self + .found_library_roots + .borrow_mut() + .insert(module_name_parsed.clone(), module_dir.clone()); + // If the package.json has a `wing` field, then we treat it as a Wing library if let Some(libname) = as_wing_library(&Utf8Path::new(&module_dir)) { return if let Some(alias) = alias { @@ -1163,6 +1198,7 @@ impl<'s> Parser<'s> { // check if a trusted library exists with this name let source_dir = Utf8Path::new(&self.source_file.path).parent().unwrap(); + let package_name = format!("{}/{}", TRUSTED_LIBRARY_NPM_NAMESPACE, module_name.name); let module_dir = wingii::util::package_json::find_dependency_directory( &format!("{}/{}", TRUSTED_LIBRARY_NPM_NAMESPACE, module_name.name), &source_dir, @@ -1179,6 +1215,11 @@ impl<'s> Parser<'s> { .err(); })?; + self + .found_library_roots + .borrow_mut() + .insert(package_name, module_dir.clone()); + // make sure the trusted library is also parsed let module_file = File::new( &module_dir, diff --git a/libs/wingc/src/test_utils.rs b/libs/wingc/src/test_utils.rs index 7de5a646afe..7fb66bfdc38 100644 --- a/libs/wingc/src/test_utils.rs +++ b/libs/wingc/src/test_utils.rs @@ -86,9 +86,9 @@ fn compile_code(code: &str, as_dir: bool) -> String { // Write lib.w to the project dir because compiling a directory requires an actual file to exist std::fs::write(project_dir.join("lib.w"), &code).unwrap(); - compile(project_dir, &project_dir, None, &out_dir) + compile(&project_dir, None, &out_dir) } else { - compile(project_dir, &project_dir.join("main.w"), Some(code.clone()), &out_dir) + compile(&project_dir.join("main.w"), Some(code.clone()), &out_dir) }; let mut snap = vec![]; diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index b0853a1eadc..a4cd7000953 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -304,6 +304,9 @@ pub struct Namespace { /// Where we can resolve this namespace from pub module_path: ResolveSource, + + /// The fully qualified name of this namespace + pub fqn: String, } #[derive(Debug)] @@ -351,6 +354,7 @@ pub struct Class { // uid is used for Wing classes and is always 0 for JSII classes to avoid snapshot noise. pub uid: usize, } + impl Class { pub(crate) fn set_lifts(&mut self, lifts: Lifts) { self.lifts = Some(lifts); @@ -370,7 +374,7 @@ impl Class { #[derivative(Debug)] pub struct Interface { pub name: Symbol, - pub fqn: Option, + pub fqn: String, pub docs: Docs, pub extends: Vec, // Must be a Type::Interface type pub phase: Phase, @@ -469,7 +473,7 @@ pub struct ArgListTypes { #[derivative(Debug)] pub struct Struct { pub name: Symbol, - pub fqn: Option, + pub fqn: String, pub docs: Docs, pub extends: Vec, // Must be a Type::Struct type #[derivative(Debug = "ignore")] @@ -485,6 +489,7 @@ impl Display for Struct { #[derive(Debug)] pub struct Enum { pub name: Symbol, + pub fqn: String, pub docs: Docs, /// Variant name and optional documentation pub values: IndexMap>, @@ -1873,6 +1878,10 @@ pub struct TypeChecker<'a> { /// The file graph of the entire project compilation. file_graph: &'a FileGraph, + /// A map from package names to their root directories. + /// This is used for resolving FQNs. + library_roots: &'a IndexMap, + /// JSII Manifest descriptions to be imported. /// May be reused between compilations jsii_imports: &'a mut Vec, @@ -1880,6 +1889,9 @@ pub struct TypeChecker<'a> { /// The JSII type system jsii_types: &'a mut TypeSystem, + // A sanity check to ensure we don't generate the same FQN multiple times. + generated_fqns: HashSet, + is_in_mut_json: bool, ctx: VisitContext, @@ -1890,6 +1902,7 @@ impl<'a> TypeChecker<'a> { types: &'a mut Types, source_file: &'a File, file_graph: &'a FileGraph, + library_roots: &'a IndexMap, jsii_types: &'a mut TypeSystem, jsii_imports: &'a mut Vec, ) -> Self { @@ -1899,12 +1912,28 @@ impl<'a> TypeChecker<'a> { jsii_types, source_file, file_graph, + library_roots, jsii_imports, + generated_fqns: HashSet::new(), is_in_mut_json: false, ctx: VisitContext::new(), } } + fn current_package_root(&self) -> &Utf8Path { + self + .library_roots + .get(&self.source_file.package) + .expect("No package root found") + } + + /// Calculate a prefix for all FQNs of types in the current file. + /// It assumes a flat file structure: all types that are given a FQN must be defined at the top-level. + fn base_fqn_for_current_file(&self) -> String { + let package_root = self.current_package_root(); + calculate_fqn_for_namespace(&self.source_file.package, package_root, &self.source_file.path) + } + /// Recursively check if a type is or contains a type inference. /// /// Returns true if any inferences were found. @@ -3563,27 +3592,27 @@ It should primarily be used in preflight or in inflights that are guaranteed to first_expected_type } - pub fn type_check_file_or_dir(&mut self, source_file: &File, scope: &Scope) { + pub fn type_check_file_or_dir(&mut self, scope: &Scope) { CompilationContext::set(CompilationPhase::TypeChecking, &scope.span); self.type_check_scope(scope); - if source_file.path.is_dir() { - self.type_check_dir(source_file); + if self.source_file.path.is_dir() { + self.type_check_dir(); return; } // Save the file's symbol environment to `self.types.source_file_envs` // (replacing any existing ones if there was already a SymbolEnv from a previous compilation) let scope_env = self.types.get_scope_env(scope); - self - .types - .source_file_envs - .insert(source_file.path.to_owned(), SymbolEnvOrNamespace::SymbolEnv(scope_env)); + self.types.source_file_envs.insert( + self.source_file.path.to_owned(), + SymbolEnvOrNamespace::SymbolEnv(scope_env), + ); } - pub fn type_check_dir(&mut self, source_file: &File) { + pub fn type_check_dir(&mut self) { // Get a list of all children paths (files or directories) through the file graph - let children = self.file_graph.dependencies_of(source_file); + let children = self.file_graph.dependencies_of(self.source_file); // Obtain each child's symbol environment or namespace // If it's a namespace (i.e. it's a directory), wrap it in a symbol env @@ -3614,16 +3643,19 @@ It should primarily be used in preflight or in inflights that are guaranteed to } Some(SymbolEnvOrNamespace::Error(diagnostic)) => { self.types.source_file_envs.insert( - source_file.path.to_owned(), + self.source_file.path.to_owned(), SymbolEnvOrNamespace::Error(diagnostic.clone()), ); return; } None => { self.types.source_file_envs.insert( - source_file.path.to_owned(), + self.source_file.path.to_owned(), SymbolEnvOrNamespace::Error(Diagnostic { - message: format!("Could not bring \"{}\" due to cyclic bring statements", source_file,), + message: format!( + "Could not bring \"{}\" due to cyclic bring statements", + self.source_file, + ), span: None, annotations: vec![], hints: vec![], @@ -3645,9 +3677,12 @@ It should primarily be used in preflight or in inflights that are guaranteed to if seen_public_symbols.contains(key) { self.types.source_file_envs.insert( - source_file.path.to_owned(), + self.source_file.path.to_owned(), SymbolEnvOrNamespace::Error(Diagnostic { - message: format!("Symbol \"{}\" has multiple definitions in \"{}\"", key, source_file), + message: format!( + "Symbol \"{}\" has multiple definitions in \"{}\"", + key, self.source_file + ), span: None, annotations: vec![], hints: vec![], @@ -3661,15 +3696,16 @@ It should primarily be used in preflight or in inflights that are guaranteed to } let ns = self.types.add_namespace(Namespace { - name: source_file.path.file_stem().unwrap().to_string(), + name: self.source_file.path.file_stem().unwrap().to_string(), envs: child_envs, - source_package: source_file.package.clone(), + source_package: self.source_file.package.clone(), module_path: ResolveSource::WingFile, + fqn: self.base_fqn_for_current_file(), }); self .types .source_file_envs - .insert(source_file.path.to_owned(), SymbolEnvOrNamespace::Namespace(ns)); + .insert(self.source_file.path.to_owned(), SymbolEnvOrNamespace::Namespace(ns)); } fn type_check_scope(&mut self, scope: &Scope) { @@ -3806,11 +3842,14 @@ It should primarily be used in preflight or in inflights that are guaranteed to return; } }; + let package_root = self.current_package_root(); + let fqn = calculate_fqn_for_namespace(&self.source_file.package, &package_root, path); let ns = self.types.add_namespace(Namespace { name: path.to_string(), envs: vec![brought_env], source_package: self.source_file.package.clone(), module_path: ResolveSource::WingFile, + fqn, }); if let Err(e) = env.define( identifier.as_ref().unwrap(), @@ -3934,7 +3973,7 @@ It should primarily be used in preflight or in inflights that are guaranteed to // Create the struct type with the empty environment let struct_type = self.types.add_type(Type::Struct(Struct { name: name.clone(), - fqn: None, + fqn: format!("{}.{}", self.base_fqn_for_current_file(), st.name), extends: extends_types.clone(), env: dummy_env, docs: doc.as_ref().map_or(Docs::default(), |s| Docs::with_summary(s)), @@ -3991,7 +4030,7 @@ It should primarily be used in preflight or in inflights that are guaranteed to // Create the interface type with the empty environment let interface_spec = Interface { name: iface.name.clone(), - fqn: None, + fqn: format!("{}.{}", self.base_fqn_for_current_file(), iface.name), docs: doc.as_ref().map_or(Docs::default(), |s| Docs::with_summary(s)), env: dummy_env, extends: extend_interfaces.clone(), @@ -4015,6 +4054,7 @@ It should primarily be used in preflight or in inflights that are guaranteed to fn hoist_enum_definition(&mut self, enu: &AstEnum, env: &mut SymbolEnv, doc: &Option) { let enum_type_ref = self.types.add_type(Type::Enum(Enum { name: enu.name.clone(), + fqn: format!("{}.{}", self.base_fqn_for_current_file(), enu.name), values: enu.values.clone(), docs: doc.as_ref().map_or(Docs::default(), |s| Docs::with_summary(s)), })); @@ -4562,10 +4602,29 @@ It should primarily be used in preflight or in inflights that are guaranteed to } } + // Only public classes are guaranteed to be unique across the package and + // can be referenced by their fully qualified name. + let fqn = if ast_class.access == AccessModifier::Public { + let package_root = self.current_package_root(); + let base_fqn = calculate_fqn_for_namespace(&self.source_file.package, package_root, &self.source_file.path); + Some(format!("{}.{}", base_fqn, ast_class.name)) + } else { + None + }; + + // Check if the FQN is already used + if let Some(fqn) = &fqn { + if self.generated_fqns.contains(fqn) { + self.spanned_error(stmt, format!("The fully qualified name {} is already in use", fqn)); + } else { + self.generated_fqns.insert(fqn.clone()); + } + } + // Create the resource/class type and add it to the current environment (so class implementation can reference itself) let class_spec = Class { name: ast_class.name.clone(), - fqn: None, + fqn, env: dummy_env, parent: parent_class, implements: impl_interfaces.clone(), @@ -4835,7 +4894,7 @@ It should primarily be used in preflight or in inflights that are guaranteed to // types used in its methods are serializable and immutable if impl_interfaces .iter() - .any(|i| i.as_interface().unwrap().fqn.as_deref() == Some(WINGSDK_SIM_IRESOURCE_FQN)) + .any(|i| i.as_interface().unwrap().fqn == WINGSDK_SIM_IRESOURCE_FQN) { for (method_name, method_def) in ast_class.methods.iter() { let method_type = method_types.get(&method_name).unwrap(); @@ -5990,8 +6049,8 @@ It should primarily be used in preflight or in inflights that are guaranteed to let fqn = match &*type_to_maybe_replace { Type::Class(c) => c.fqn.as_ref(), - Type::Interface(i) => i.fqn.as_ref(), - Type::Struct(s) => s.fqn.as_ref(), + Type::Interface(i) => Some(&i.fqn), + Type::Struct(s) => Some(&s.fqn), _ => None, }; @@ -7284,6 +7343,42 @@ fn lookup_known_type(name: &'static str, env: &SymbolEnv) -> TypeRef { .expect(&format!("Expected known type \"{}\" to be a type", name)) } +/// Given the root of a package and a file path within that package, calculate the +/// fully qualified name to associate with any namespaces representing that file. +/// +/// ``` +/// use wingc::type_check::calculate_fqn_for_namespace; +/// +/// let fqn = calculate_fqn_for_namespace("@winglibs/dynamodb", "/foo/bar".into(), "/foo/bar/baz/impl.w".into()); +/// assert_eq!(fqn, "@winglibs/dynamodb.baz".to_string()); +/// +/// let fqn2 = calculate_fqn_for_namespace("@winglibs/dynamodb", "/foo/bar".into(), "/foo/bar/baz/".into()); +/// assert_eq!(fqn2, "@winglibs/dynamodb.baz".to_string()); +/// +/// let fqn3 = calculate_fqn_for_namespace("@winglibs/dynamodb", "/foo/bar".into(), "/foo/bar/".into()); +/// assert_eq!(fqn3, "@winglibs/dynamodb".to_string()); +/// ``` +pub fn calculate_fqn_for_namespace(package_name: &str, package_root: &Utf8Path, path: &Utf8Path) -> String { + let assembly = package_name; + if !path.starts_with(package_root) { + panic!( + "File path \"{}\" is not within the package root \"{}\"", + path, package_root + ); + } + let path = if path.as_str().ends_with(".w") { + path.parent().expect("Expected a parent directory") + } else { + path + }; + let relative_path = path.strip_prefix(package_root).expect("not a prefix"); + if relative_path == Utf8Path::new("") { + return assembly.to_string(); + } + let namespace = relative_path.as_str().replace("/", "."); + format!("{}.{}", assembly, namespace) +} + #[derive(Debug)] enum ResolveReferenceResult { Variable(VariableInfo), diff --git a/libs/wingc/src/type_check/jsii_importer.rs b/libs/wingc/src/type_check/jsii_importer.rs index 74373bcb218..f6b9e3a1c41 100644 --- a/libs/wingc/src/type_check/jsii_importer.rs +++ b/libs/wingc/src/type_check/jsii_importer.rs @@ -186,6 +186,7 @@ impl<'a> JsiiImporter<'a> { envs: vec![ns_env], source_package: assembly.name.clone(), module_path: ResolveSource::ExternalModule(assembly.name.clone()), + fqn: assembly.name.clone(), }); self .wing_types @@ -235,6 +236,7 @@ impl<'a> JsiiImporter<'a> { envs: vec![ns_env], source_package: assembly.name.clone(), module_path: ResolveSource::ExternalModule(module_path), + fqn: format!("{}.{}", lookup_str.clone(), namespace_name), }); parent_ns .envs @@ -269,6 +271,7 @@ impl<'a> JsiiImporter<'a> { let enum_type_ref = self.wing_types.add_type(Type::Enum(Enum { name: enum_symbol.clone(), + fqn: enum_fqn.to_string(), docs: Docs::from(&jsii_enum.docs), values, })); @@ -387,7 +390,7 @@ impl<'a> JsiiImporter<'a> { let mut wing_type = match is_struct { true => self.wing_types.add_type(Type::Struct(Struct { name: new_type_symbol.clone(), - fqn: Some(jsii_interface_fqn.to_string()), + fqn: jsii_interface_fqn.to_string(), // Will be replaced below extends: vec![], docs: Docs::from(&jsii_interface.docs), @@ -402,7 +405,7 @@ impl<'a> JsiiImporter<'a> { })), false => self.wing_types.add_type(Type::Interface(Interface { name: new_type_symbol.clone(), - fqn: Some(jsii_interface_fqn.to_string()), + fqn: jsii_interface_fqn.to_string(), // Will be replaced below extends: vec![], docs: Docs::from(&jsii_interface.docs), @@ -1028,6 +1031,7 @@ impl<'a> JsiiImporter<'a> { envs: vec![ns_env], source_package: assembly.name.clone(), module_path: ResolveSource::ExternalModule(assembly.name.clone()), + fqn: assembly.name.clone(), }); self .wing_types diff --git a/libs/wingc/src/type_check/symbol_env.rs b/libs/wingc/src/type_check/symbol_env.rs index 591806f3ae8..9d3bdfca0a5 100644 --- a/libs/wingc/src/type_check/symbol_env.rs +++ b/libs/wingc/src/type_check/symbol_env.rs @@ -756,12 +756,14 @@ mod tests { envs: vec![ns1_env], source_package: DEFAULT_PACKAGE_NAME.to_string(), module_path: ResolveSource::WingFile, + fqn: "pkg1.ns1".to_string(), }); let ns2 = types.add_namespace(Namespace { name: "ns2".to_string(), envs: vec![ns2_env], source_package: DEFAULT_PACKAGE_NAME.to_string(), module_path: ResolveSource::WingFile, + fqn: "pkg1.ns1".to_string(), }); // Define ns2 in n1's env diff --git a/libs/wingcompiler/src/compile.ts b/libs/wingcompiler/src/compile.ts index 21b37986675..71ec4f208c2 100644 --- a/libs/wingcompiler/src/compile.ts +++ b/libs/wingcompiler/src/compile.ts @@ -332,9 +332,7 @@ async function compileForPreflight(props: { diagnostics.push(JSON.parse(data_str)); } - const arg = `${normalPath(props.entrypointFile)};${normalPath(props.workDir)};${normalPath( - props.wingDir - )}`; + const arg = `${normalPath(props.entrypointFile)};${normalPath(props.workDir)}`; props.log?.(`invoking %s with: "%s"`, WINGC_COMPILE, arg); let compilerOutput: string | number = ""; try { diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md index 14e322c6b35..9ea26b0f30e 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md @@ -50,7 +50,7 @@ class $Root extends $stdlib.std.Resource { let $preflightTypesMap = {}; const lib = $helpers.bringJs(`${__dirname}/preflight.extendnonentrypoint-1.cjs`, $preflightTypesMap); $helpers.nodeof(this).root.$preflightTypesMap = $preflightTypesMap; - const f = new lib.Foo(this, "Foo"); + const f = globalThis.$ClassFactory.new("rootpkg.Foo", lib.Foo, this, "Foo"); } } const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "bring_extend_non_entry.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] }); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md index f87bc3b1c35..957cdfea1f9 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md @@ -351,19 +351,19 @@ class $Root extends $stdlib.std.Resource { return ` require("${$helpers.normalPath(__dirname)}/inflight.$Closure2-3.cjs")({ $expect_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"))}, - $file2_Q: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(file2.Q, "", "Q"))}, + $file2_Q: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir.Q") ?? file2.Q, "", "Q"))}, }) `; } get _liftMap() { return ({ "handle": [ - [$stdlib.core.toLiftableModuleType(file2.Q, "", "Q"), ["greet"]], [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"), ["equal"]], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir.Q") ?? file2.Q, "", "Q"), ["greet"]], ], "$inflight_init": [ - [$stdlib.core.toLiftableModuleType(file2.Q, "", "Q"), []], [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir.Q") ?? file2.Q, "", "Q"), []], ], }); } @@ -405,8 +405,8 @@ class $Root extends $stdlib.std.Resource { }); } } - const store = new file1.Store(this, "Store"); - const q = new file2.Q(this, "Q"); + const store = globalThis.$ClassFactory.new("rootpkg.Store", file1.Store, this, "Store"); + const q = globalThis.$ClassFactory.new("rootpkg.subdir.Q", file2.Q, this, "Q"); (expect.Util.equal((file2.Q.preflightGreet("foo")), "Hello foo")); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:add data to store", new $Closure1(this, "$Closure1")); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:greet", new $Closure2(this, "$Closure2")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md index 40619b4963c..d5e8329578d 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md @@ -106,13 +106,13 @@ class $Root extends $stdlib.std.Resource { const w = $helpers.bringJs(`${__dirname}/preflight.widget-1.cjs`, $preflightTypesMap); const subdir = $helpers.bringJs(`${__dirname}/preflight.subdir2-6.cjs`, $preflightTypesMap); $helpers.nodeof(this).root.$preflightTypesMap = $preflightTypesMap; - const widget1 = new w.Widget(this, "widget1"); + const widget1 = globalThis.$ClassFactory.new("rootpkg.subdir2.inner.Widget", w.Widget, this, "widget1"); $helpers.assert($helpers.eq((widget1.compute()), 42), "widget1.compute() == 42"); - const foo = new subdir.Foo(this, "Foo"); + const foo = globalThis.$ClassFactory.new("rootpkg.subdir2.Foo", subdir.Foo, this, "Foo"); $helpers.assert($helpers.eq((foo.foo()), "foo"), "foo.foo() == \"foo\""); - const bar = new subdir.Bar(this, "Bar"); + const bar = globalThis.$ClassFactory.new("rootpkg.subdir2.Bar", subdir.Bar, this, "Bar"); $helpers.assert($helpers.eq((bar.bar()), "bar"), "bar.bar() == \"bar\""); - const widget2 = new subdir.inner.Widget(this, "widget2"); + const widget2 = globalThis.$ClassFactory.new("rootpkg.subdir2.inner.Widget", subdir.inner.Widget, this, "widget2"); $helpers.assert($helpers.eq((widget2.compute()), 42), "widget2.compute() == 42"); $helpers.assert($helpers.eq((foo.checkWidget(widget2)), 1379), "foo.checkWidget(widget2) == 1379"); } diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_wing_library.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_wing_library.test.w_compile_tf-aws.md index e43a2f38324..22ddc8f6cfa 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_wing_library.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_wing_library.test.w_compile_tf-aws.md @@ -156,22 +156,22 @@ class $Root extends $stdlib.std.Resource { static _toInflightType() { return ` require("${$helpers.normalPath(__dirname)}/inflight.$Closure1-3.cjs")({ - $fixture_Store: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(fixture.Store, "", "Store"))}, + $fixture_Store: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglibs/testfixture.Store") ?? fixture.Store, "", "Store"))}, }) `; } get _liftMap() { return ({ "handle": [ - [$stdlib.core.toLiftableModuleType(fixture.Store, "", "Store"), ["makeKeyInflight"]], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglibs/testfixture.Store") ?? fixture.Store, "", "Store"), ["makeKeyInflight"]], ], "$inflight_init": [ - [$stdlib.core.toLiftableModuleType(fixture.Store, "", "Store"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglibs/testfixture.Store") ?? fixture.Store, "", "Store"), []], ], }); } } - new fixture.Store(this, "Store"); + globalThis.$ClassFactory.new("@winglibs/testfixture.Store", fixture.Store, this, "Store"); const fave_num = fixture.FavoriteNumbers.SEVEN; const fave_num2 = testfixture.FavoriteNumbers.SEVEN; const fave_num3 = testfixture2.FavoriteNumbers.SEVEN; @@ -237,7 +237,7 @@ class Store extends $stdlib.std.Resource { static _toInflightType() { return ` require("${$helpers.normalPath(__dirname)}/inflight.Store-2.cjs")({ - $myutil_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(myutil.Util, "", "Util"))}, + $myutil_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglibs/testfixture.subdir.Util") ?? myutil.Util, "", "Util"))}, }) `; } @@ -251,12 +251,12 @@ class Store extends $stdlib.std.Resource { get _liftMap() { return ({ "set": [ - [$stdlib.core.toLiftableModuleType(myutil.Util, "", "Util"), ["double"]], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglibs/testfixture.subdir.Util") ?? myutil.Util, "", "Util"), ["double"]], [this.data, ["put"]], [this.handlers, []], ], "$inflight_init": [ - [$stdlib.core.toLiftableModuleType(myutil.Util, "", "Util"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglibs/testfixture.subdir.Util") ?? myutil.Util, "", "Util"), []], [this.data, []], [this.handlers, []], ], diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md index b4e473ef3db..439b2583e82 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md @@ -447,7 +447,7 @@ class $Root extends $stdlib.std.Resource { static _toInflightType() { return ` require("${$helpers.normalPath(__dirname)}/inflight.$Closure6-5.cjs")({ - $subdir_InflightClass: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(subdir.InflightClass, "", "InflightClass"))}, + $subdir_InflightClass: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"))}, }) `; } @@ -455,11 +455,11 @@ class $Root extends $stdlib.std.Resource { return ({ "handle": [ [$helpers.preflightClassSingleton(this, 5), ["method"]], - [$stdlib.core.toLiftableModuleType(subdir.InflightClass, "", "InflightClass"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"), []], ], "$inflight_init": [ [$helpers.preflightClassSingleton(this, 5), []], - [$stdlib.core.toLiftableModuleType(subdir.InflightClass, "", "InflightClass"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"), []], ], }); } diff --git a/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md index 66aa2b794ab..309599ab482 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md @@ -407,8 +407,8 @@ class $Root extends $stdlib.std.Resource { } } const bucket = globalThis.$ClassFactory.new("@winglang/sdk.cloud.Bucket", cloud.Bucket, this, "Bucket"); - const b1 = new MyBucket(this, "b1", bucket); - const b2 = new MyBucket(this, "b2", bucket); + const b1 = globalThis.$ClassFactory.new("rootpkg.MyBucket", MyBucket, this, "b1", bucket); + const b2 = globalThis.$ClassFactory.new("rootpkg.MyBucket", MyBucket, this, "b2", bucket); const api = globalThis.$ClassFactory.new("@winglang/sdk.cloud.Api", cloud.Api, this, "Api"); (api.get("/", new $Closure1(this, "$Closure1"))); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:call endpoint", new $Closure2(this, "$Closure2")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md index f7a03b4883d..ae2ca35ab4a 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md @@ -338,7 +338,7 @@ class LibClass extends $stdlib.std.Resource { super($scope, $id); } static createFoo($scope, id) { - return new Foo($scope, id); + return globalThis.$ClassFactory.new("rootpkg.Foo", Foo, $scope, id); } static _toInflightType() { return ` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md index d06d636cfce..caf8656b25e 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md @@ -57,7 +57,7 @@ class LibClass extends $stdlib.std.Resource { super($scope, $id); } static createFoo($scope, id) { - return new Foo($scope, id); + return globalThis.$ClassFactory.new("rootpkg.Foo", Foo, $scope, id); } static _toInflightType() { return ` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md index 19360173050..b9db451237c 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md @@ -485,7 +485,7 @@ class $Root extends $stdlib.std.Resource { (std.Number.fromJson("cool", { unsafe: true })); $macros.__Struct_fromJson(false, Student, ({"obviously": "not a student"}), { unsafe: true }); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:unsafe flight", new $Closure5(this, "$Closure5")); - new otherExternalStructs.UsesStructInImportedFile(this, "UsesStructInImportedFile"); + globalThis.$ClassFactory.new("rootpkg.subdir.UsesStructInImportedFile", otherExternalStructs.UsesStructInImportedFile, this, "UsesStructInImportedFile"); } } const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "struct_from_json.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] }); diff --git a/tools/hangar/src/unsupported.test.ts b/tools/hangar/src/unsupported.test.ts index 7f9774cb5c2..1701436b894 100644 --- a/tools/hangar/src/unsupported.test.ts +++ b/tools/hangar/src/unsupported.test.ts @@ -30,7 +30,7 @@ test("unsupported resource in target", async ({ expect }) => { }, }); - expect(sanitizeOutput(result.stderr)).toMatchInlineSnapshot('"Error: A Google Cloud region must be specified through the GOOGLE_REGION environment variable."'); + expect(sanitizeOutput(result.stderr)).toMatchInlineSnapshot(`"Error: A Google Cloud region must be specified through the GOOGLE_REGION environment variable."`); }); function sanitizeOutput(inputString: string): string {