···68686969 match nsid {
7070 Some(namespace) => {
7171- // Fetch single namespace
7272- fetch_lexicon(&namespace, &project_root)?;
7171+ // Fetch single namespace with transitive dependencies
7272+ let lockfile_path = project_root.join("mlf-lock.toml");
7373+ let mut lockfile = LockFile::load(&lockfile_path).unwrap_or_else(|_| LockFile::new());
7474+7575+ // Load config to check if transitive deps are enabled
7676+ let config_path = project_root.join("mlf.toml");
7777+ let config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?;
7878+7979+ fetch_lexicon_with_lock(&namespace, &project_root, &mut lockfile)?;
8080+8181+ // Handle transitive dependencies if enabled
8282+ if config.dependencies.allow_transitive_deps {
8383+ println!("\n→ Checking for transitive dependencies...");
8484+ fetch_transitive_dependencies(
8585+ &project_root,
8686+ &mut lockfile,
8787+ config.dependencies.optimize_transitive_fetches
8888+ )?;
8989+ }
9090+9191+ // Save lockfile
9292+ lockfile.save(&lockfile_path).map_err(FetchError::NoProjectRoot)?;
9393+ println!("\n→ Updated mlf-lock.toml");
73947495 // Save to mlf.toml if --save flag is provided
7596 if save {
···182203 }
183204 }
184205185185- // If transitive dependencies are enabled, iteratively fetch missing deps
206206+ // If transitive dependencies are enabled, fetch them
186207 if allow_transitive {
187187- let mut iteration = 0;
188188- let max_iterations = 10; // Prevent infinite loops
208208+ fetch_transitive_dependencies(&project_root, &mut lockfile, config.dependencies.optimize_transitive_fetches)?;
209209+ }
189210190190- loop {
191191- iteration += 1;
192192- if iteration > max_iterations {
193193- eprintln!("\nWarning: Reached maximum iteration limit for transitive dependencies");
194194- break;
195195- }
211211+ // Save the lockfile
212212+ lockfile.save(&lockfile_path).map_err(FetchError::NoProjectRoot)?;
213213+ println!("\n→ Updated mlf-lock.toml");
196214197197- // Collect unresolved references
198198- let unresolved = match collect_unresolved_references(project_root) {
199199- Ok(refs) => refs,
200200- Err(e) => {
201201- eprintln!("\nWarning: Failed to analyze dependencies: {}", e);
202202- break;
203203- }
204204- };
215215+ if !errors.is_empty() {
216216+ eprintln!(
217217+ "\n{} dependency(ies) fetched successfully, {} error(s):",
218218+ success_count,
219219+ errors.len()
220220+ );
221221+ for (dep, error) in &errors {
222222+ eprintln!(" {} - {}", dep, error);
223223+ }
224224+ return Err(FetchError::HttpError(format!(
225225+ "Failed to fetch {} dependencies",
226226+ errors.len()
227227+ )));
228228+ }
229229+230230+ println!("\n✓ Successfully fetched all {} dependencies", success_count);
231231+ Ok(())
232232+}
233233+234234+/// Fetch transitive dependencies by iteratively resolving unresolved references
235235+fn fetch_transitive_dependencies(
236236+ project_root: &std::path::Path,
237237+ lockfile: &mut LockFile,
238238+ optimize_fetches: bool
239239+) -> Result<(), FetchError> {
240240+ let mut fetched_nsids = HashSet::new();
241241+ // Track NSIDs from lockfile as already fetched
242242+ for nsid in lockfile.lexicons.keys() {
243243+ fetched_nsids.insert(nsid.clone());
244244+ }
245245+246246+ let mut iteration = 0;
247247+ const MAX_ITERATIONS: usize = 10;
205248206206- // Filter out NSIDs we've already fetched or tried to fetch
207207- let new_deps: HashSet<String> = unresolved
208208- .into_iter()
209209- .filter(|nsid| !fetched_nsids.contains(nsid))
210210- .collect();
249249+ loop {
250250+ iteration += 1;
251251+ if iteration > MAX_ITERATIONS {
252252+ eprintln!("\nWarning: Reached maximum iteration limit for transitive dependencies");
253253+ break;
254254+ }
211255212212- if new_deps.is_empty() {
256256+ // Collect unresolved references
257257+ let unresolved = match collect_unresolved_references(project_root) {
258258+ Ok(refs) => refs,
259259+ Err(e) => {
260260+ eprintln!("\nWarning: Failed to analyze dependencies: {}", e);
213261 break;
214262 }
263263+ };
215264216216- // Determine whether to optimize transitive fetches
217217- let should_optimize = config.dependencies.optimize_transitive_fetches;
265265+ // Filter out NSIDs we've already fetched or tried to fetch
266266+ let new_deps: HashSet<String> = unresolved
267267+ .into_iter()
268268+ .filter(|nsid| !fetched_nsids.contains(nsid))
269269+ .collect();
270270+271271+ if new_deps.is_empty() {
272272+ break;
273273+ }
218274219219- if should_optimize {
220220- // Optimize the fetch patterns to reduce number of fetches
221221- let optimized_patterns = optimize_fetch_patterns(&new_deps);
275275+ if optimize_fetches {
276276+ // Optimize the fetch patterns to reduce number of fetches
277277+ let optimized_patterns = optimize_fetch_patterns(&new_deps);
222278223223- println!("\n→ Found {} unresolved reference(s), fetching {} optimized pattern(s)...",
224224- new_deps.len(), optimized_patterns.len());
279279+ println!("\n→ Found {} unresolved reference(s), fetching {} optimized pattern(s)...",
280280+ new_deps.len(), optimized_patterns.len());
225281226226- // Track which patterns are wildcards and their constituent NSIDs
227227- let mut wildcard_failures: Vec<(String, Vec<String>)> = Vec::new();
282282+ // Track which patterns are wildcards and their constituent NSIDs
283283+ let mut wildcard_failures: Vec<(String, Vec<String>)> = Vec::new();
228284229229- for pattern in optimized_patterns {
230230- let is_wildcard = pattern.ends_with(".*");
231231- println!("\nFetching transitive dependency: {}", pattern);
232232- fetched_nsids.insert(pattern.clone());
285285+ for pattern in optimized_patterns {
286286+ let is_wildcard = pattern.ends_with(".*");
287287+ println!("\nFetching transitive dependency: {}", pattern);
288288+ fetched_nsids.insert(pattern.clone());
233289234234- match fetch_lexicon_with_lock(&pattern, project_root, &mut lockfile) {
235235- Ok(()) => {
236236- success_count += 1;
237237- }
238238- Err(e) => {
239239- eprintln!(" Warning: Failed to fetch {}: {}", pattern, e);
290290+ match fetch_lexicon_with_lock(&pattern, project_root, lockfile) {
291291+ Ok(()) => {}
292292+ Err(e) => {
293293+ eprintln!(" Warning: Failed to fetch {}: {}", pattern, e);
240294241241- // If this was a wildcard that failed, collect the individual NSIDs for retry
242242- if is_wildcard {
243243- let pattern_prefix = pattern.strip_suffix(".*").unwrap();
244244- let matching_nsids: Vec<String> = new_deps.iter()
245245- .filter(|nsid| nsid.starts_with(pattern_prefix))
246246- .cloned()
247247- .collect();
295295+ // If this was a wildcard that failed, collect the individual NSIDs for retry
296296+ if is_wildcard {
297297+ let pattern_prefix = pattern.strip_suffix(".*").unwrap();
298298+ let matching_nsids: Vec<String> = new_deps.iter()
299299+ .filter(|nsid| nsid.starts_with(pattern_prefix))
300300+ .cloned()
301301+ .collect();
248302249249- if !matching_nsids.is_empty() {
250250- wildcard_failures.push((pattern.clone(), matching_nsids));
251251- }
303303+ if !matching_nsids.is_empty() {
304304+ wildcard_failures.push((pattern.clone(), matching_nsids));
252305 }
253306 }
254307 }
255308 }
309309+ }
256310257257- // Retry failed wildcards with individual NSIDs
258258- if !wildcard_failures.is_empty() {
259259- println!("\n→ Retrying failed wildcard patterns with individual NSIDs...");
311311+ // Retry failed wildcards with individual NSIDs
312312+ if !wildcard_failures.is_empty() {
313313+ println!("\n→ Retrying failed wildcard patterns with individual NSIDs...");
260314261261- for (failed_pattern, nsids) in wildcard_failures {
262262- println!(" Retrying {} NSIDs from failed pattern: {}", nsids.len(), failed_pattern);
315315+ for (failed_pattern, nsids) in wildcard_failures {
316316+ println!(" Retrying {} NSIDs from failed pattern: {}", nsids.len(), failed_pattern);
263317264264- for nsid in nsids {
265265- if !fetched_nsids.contains(&nsid) {
266266- println!(" Fetching: {}", nsid);
267267- fetched_nsids.insert(nsid.clone());
318318+ for nsid in nsids {
319319+ if !fetched_nsids.contains(&nsid) {
320320+ println!(" Fetching: {}", nsid);
321321+ fetched_nsids.insert(nsid.clone());
268322269269- match fetch_lexicon_with_lock(&nsid, project_root, &mut lockfile) {
270270- Ok(()) => {
271271- success_count += 1;
272272- }
273273- Err(e) => {
274274- eprintln!(" Warning: Failed to fetch {}: {}", nsid, e);
275275- }
323323+ match fetch_lexicon_with_lock(&nsid, project_root, lockfile) {
324324+ Ok(()) => {}
325325+ Err(e) => {
326326+ eprintln!(" Warning: Failed to fetch {}: {}", nsid, e);
276327 }
277328 }
278329 }
279330 }
280331 }
281281- } else {
282282- // Fetch individually without optimization (safer, more predictable)
283283- println!("\n→ Found {} unresolved reference(s), fetching individually...",
284284- new_deps.len());
332332+ }
333333+ } else {
334334+ // Fetch individually without optimization (safer, more predictable)
335335+ println!("\n→ Found {} unresolved reference(s), fetching individually...",
336336+ new_deps.len());
285337286286- for nsid in &new_deps {
287287- println!("\nFetching transitive dependency: {}", nsid);
288288- fetched_nsids.insert(nsid.clone());
338338+ for nsid in &new_deps {
339339+ println!("\nFetching transitive dependency: {}", nsid);
340340+ fetched_nsids.insert(nsid.clone());
289341290290- match fetch_lexicon_with_lock(nsid, project_root, &mut lockfile) {
291291- Ok(()) => {
292292- success_count += 1;
293293- }
294294- Err(e) => {
295295- // Don't fail the entire fetch for transitive deps
296296- eprintln!(" Warning: Failed to fetch {}: {}", nsid, e);
297297- }
342342+ match fetch_lexicon_with_lock(nsid, project_root, lockfile) {
343343+ Ok(()) => {}
344344+ Err(e) => {
345345+ // Don't fail the entire fetch for transitive deps
346346+ eprintln!(" Warning: Failed to fetch {}: {}", nsid, e);
298347 }
299348 }
300349 }
301350 }
302351 }
303352304304- // Save the lockfile
305305- lockfile.save(&lockfile_path).map_err(FetchError::NoProjectRoot)?;
306306- println!("\n→ Updated mlf-lock.toml");
307307-308308- if !errors.is_empty() {
309309- eprintln!(
310310- "\n{} dependency(ies) fetched successfully, {} error(s):",
311311- success_count,
312312- errors.len()
313313- );
314314- for (dep, error) in &errors {
315315- eprintln!(" {} - {}", dep, error);
316316- }
317317- return Err(FetchError::HttpError(format!(
318318- "Failed to fetch {} dependencies",
319319- errors.len()
320320- )));
321321- }
322322-323323- println!("\n✓ Successfully fetched all {} dependencies", success_count);
324353 Ok(())
325354}
326355···874903 return Ok(HashSet::new());
875904 }
876905877877- // Build a workspace from all fetched MLF files
878878- let mut workspace = Workspace::new();
906906+ // Build a workspace with std library to avoid fetching std types
907907+ let mut workspace = Workspace::with_std()
908908+ .map_err(|e| FetchError::IoError(std::io::Error::new(
909909+ std::io::ErrorKind::Other,
910910+ format!("Failed to load standard library: {:?}", e)
911911+ )))?;
879912 let mut unresolved = HashSet::new();
880913881914 // Recursively find all .mlf files
+3-2
mlf-cli/src/generate/mlf.rs
···417417 .collect();
418418419419 if !param_strs.is_empty() {
420420- output.push_str(¶m_strs.join(","));
420420+ output.push_str(¶m_strs.join(",\n "));
421421 }
422422 }
423423 }
···514514 .collect();
515515516516 if !param_strs.is_empty() {
517517- output.push_str(¶m_strs.join(","));
517517+ output.push_str(¶m_strs.join(",\n "));
518518 }
519519 }
520520 }
···652652 }
653653654654 // Use last segment of NSID for "main" definitions
655655+ // Keywords are now allowed by the parser, so just escape with backticks
655656 let def_name = if name == "main" {
656657 escape_name(last_segment)
657658 } else {
+1-1
mlf-lang/src/parser.rs
···421421 fn parse_def_type(&mut self, docs: Vec<DocComment>, annotations: Vec<Annotation>) -> Result<Item, ParseError> {
422422 let start = self.expect(LexToken::Def)?;
423423 self.expect(LexToken::Type)?;
424424- let name = self.parse_ident()?;
424424+ let name = self.parse_ident()?; // Backticked keywords are already converted to Ident by lexer
425425 self.expect(LexToken::Equals)?;
426426 let ty = self.parse_type()?;
427427 let end = self.expect(LexToken::Semicolon)?;
+264-42
mlf-lang/src/workspace.rs
···2525struct ImportTable {
2626 mappings: BTreeMap<String, ImportedSymbol>,
2727 used_imports: BTreeSet<String>,
2828+ // Maps namespace alias to full namespace path
2929+ // e.g., "example" -> "com.example"
3030+ namespace_aliases: BTreeMap<String, String>,
2831}
29323033#[derive(Debug, Clone, PartialEq)]
···168171 return Err(errors);
169172 }
170173171171- // Check for unused imports
174174+ // Check for unused imports (warnings only, don't fail)
172175 if let Err(mut unused_import_errors) = self.check_unused_imports() {
173176 errors.append(&mut unused_import_errors);
174177 }
···177180 errors.append(&mut typecheck_errors);
178181 }
179182180180- if errors.is_empty() {
183183+ // Filter out warnings (UnusedImport) from blocking errors
184184+ let blocking_errors: Vec<ValidationError> = errors.errors.into_iter()
185185+ .filter(|e| !matches!(e, ValidationError::UnusedImport { .. }))
186186+ .collect();
187187+188188+ if blocking_errors.is_empty() {
181189 Ok(())
182190 } else {
183183- Err(errors)
191191+ Err(ValidationErrors { errors: blocking_errors })
184192 }
185193 }
186194···875883 }
876884 };
877885878878- if !self.modules.contains_key(&target_namespace) {
886886+ // Check if the target namespace exists or if there are modules with that prefix
887887+ let namespace_exists = self.modules.contains_key(&target_namespace);
888888+ let has_children = self.modules.keys().any(|ns| ns.starts_with(&alloc::format!("{}.", target_namespace)));
889889+890890+ if !namespace_exists && !has_children {
879891 errors.push(ValidationError::UndefinedReference {
880892 name: target_namespace.clone(),
881893 span: use_stmt.path.span,
···884896 return Err(errors);
885897 }
886898899899+ let namespace_alias_to_add: Option<(String, String)> = match &use_stmt.imports {
900900+ UseImports::All => {
901901+ // Check for implicit main resolution
902902+ // If namespace suffix matches a type name, import only that type
903903+ // Otherwise, this is a namespace alias for path shortening
904904+ let namespace_suffix = target_namespace.split('.').last().unwrap_or(&target_namespace);
905905+906906+ if let Some(target_module) = self.modules.get(&target_namespace) {
907907+ // Module exists - check if there's a type matching the namespace suffix
908908+ if target_module.symbols.types.contains_key(namespace_suffix) {
909909+ // Implicit main resolution: no namespace alias, just type import
910910+ None
911911+ } else {
912912+ // No type matching namespace suffix - this is a namespace alias
913913+ // Create alias: namespace_suffix -> target_namespace
914914+ // e.g., "use com.example;" creates alias "example" -> "com.example"
915915+ Some((namespace_suffix.to_string(), target_namespace.clone()))
916916+ }
917917+ } else {
918918+ // Module doesn't exist but has children - create namespace alias
919919+ // e.g., "use com.example;" with only "com.example.defs" module
920920+ Some((namespace_suffix.to_string(), target_namespace.clone()))
921921+ }
922922+ }
923923+ UseImports::Items(_) => {
924924+ // Items imports don't create namespace aliases
925925+ None
926926+ }
927927+ };
928928+887929 let imports_to_add: Vec<(String, ImportedSymbol)> = match &use_stmt.imports {
888930 UseImports::All => {
889889- // Import all types from the namespace
890890- let target_module = self.modules.get(&target_namespace).unwrap();
891891- target_module.symbols.types.keys()
892892- .map(|type_name| {
931931+ // Check for implicit main resolution
932932+ // If namespace suffix matches a type name, import only that type
933933+ let namespace_suffix = target_namespace.split('.').last().unwrap_or(&target_namespace);
934934+935935+ if let Some(target_module) = self.modules.get(&target_namespace) {
936936+ // Check if there's a type matching the namespace suffix
937937+ if target_module.symbols.types.contains_key(namespace_suffix) {
938938+ // Implicit main resolution: import only the type matching the namespace suffix
893939 let imported = ImportedSymbol {
894940 original_path: use_stmt.path.segments.iter()
895941 .map(|s| s.name.clone())
896896- .chain(core::iter::once(type_name.clone()))
897942 .collect(),
898898- local_name: type_name.clone(),
943943+ local_name: namespace_suffix.to_string(),
899944 span: use_stmt.path.span,
900945 };
901901- (type_name.clone(), imported)
902902- })
903903- .collect()
946946+ vec![(namespace_suffix.to_string(), imported)]
947947+ } else {
948948+ // Namespace alias only, no type imports
949949+ vec![]
950950+ }
951951+ } else {
952952+ // Module doesn't exist - namespace alias only
953953+ vec![]
954954+ }
904955 }
905956 UseImports::Items(items) => {
906957 // Import specific items from the namespace
···9741025 module.imports.mappings.insert(local_name, imported);
9751026 }
976102710281028+ // Add namespace alias if one was created
10291029+ if let Some((alias, full_namespace)) = namespace_alias_to_add {
10301030+ module.imports.namespace_aliases.insert(alias, full_namespace);
10311031+ }
10321032+9771033 if errors.is_empty() {
9781034 Ok(())
9791035 } else {
···14791535 return Err(errors);
14801536 }
1481153714821482- let target_namespace = path.segments[..path.segments.len() - 1]
14831483- .iter()
14841484- .map(|s| s.name.as_str())
14851485- .collect::<Vec<_>>()
14861486- .join(".");
15381538+ // Multi-segment path: check for namespace alias
15391539+ // If the first segment is a namespace alias, expand it
15401540+ let first_segment = &path.segments[0].name;
14871541 let type_name = &path.segments[path.segments.len() - 1].name;
1488154215431543+ let target_namespace = if let Some(module) = self.modules.get(current_namespace) {
15441544+ if let Some(full_ns) = module.imports.namespace_aliases.get(first_segment) {
15451545+ // Found a namespace alias! Expand it
15461546+ // e.g., "example.defs.foo" with alias "example" -> "com.example"
15471547+ // The namespace part is "example.defs" which expands to "com.example.defs"
15481548+ // (excluding the last segment "foo" which is the type name)
15491549+ let middle_segments: Vec<&str> = path.segments[1..path.segments.len() - 1]
15501550+ .iter()
15511551+ .map(|s| s.name.as_str())
15521552+ .collect();
15531553+ if middle_segments.is_empty() {
15541554+ // e.g., "example.foo" -> "com.example"
15551555+ full_ns.clone()
15561556+ } else {
15571557+ // e.g., "example.defs.foo" -> "com.example.defs"
15581558+ alloc::format!("{}.{}", full_ns, middle_segments.join("."))
15591559+ }
15601560+ } else {
15611561+ // No alias, use the path as-is (excluding type name)
15621562+ path.segments[..path.segments.len() - 1]
15631563+ .iter()
15641564+ .map(|s| s.name.as_str())
15651565+ .collect::<Vec<_>>()
15661566+ .join(".")
15671567+ }
15681568+ } else {
15691569+ path.segments[..path.segments.len() - 1]
15701570+ .iter()
15711571+ .map(|s| s.name.as_str())
15721572+ .collect::<Vec<_>>()
15731573+ .join(".")
15741574+ };
15751575+14891576 // First try: normal resolution (namespace + type)
14901577 if let Some(module) = self.modules.get(&target_namespace) {
14911578 if module.symbols.types.contains_key(type_name) {
···14961583 // Second try: implicit main resolution
14971584 // If com.atproto.repo.strongRef fails, try treating the full path as a namespace
14981585 // and look for a type named "strongRef" (matching the namespace suffix)
14991499- let full_namespace = &full_path;
15001500- if let Some(module) = self.modules.get(full_namespace) {
15011501- let namespace_suffix = full_namespace.split('.').last().unwrap_or(full_namespace);
15861586+ // Need to use the expanded path if an alias was used
15871587+ let expanded_full_path = if target_namespace.is_empty() {
15881588+ type_name.to_string()
15891589+ } else {
15901590+ alloc::format!("{}.{}", target_namespace, type_name)
15911591+ };
15921592+ if let Some(module) = self.modules.get(&expanded_full_path) {
15931593+ let namespace_suffix = expanded_full_path.split('.').last().unwrap_or(&expanded_full_path);
15021594 if namespace_suffix == type_name && module.symbols.types.contains_key(type_name) {
15031595 return Ok(());
15041596 }
···1580167215811673 // Prelude should be accessible
15821674 assert!(ws.modules.contains_key("prelude"));
15831583- }
15841584-15851585- #[test]
15861586- fn test_use_import_all() {
15871587- let mut ws = Workspace::new();
15881588-15891589- let a = parse_lexicon("record foo {} inline type bar = string;").unwrap();
15901590- ws.add_module("a".into(), a).unwrap();
15911591-15921592- let b = parse_lexicon("use a; record baz { x: foo, y: bar, }").unwrap();
15931593- ws.add_module("b".into(), b).unwrap();
15941594-15951595- assert!(ws.resolve().is_ok());
15961675 }
1597167615981677 #[test]
···21062185 let b = parse_lexicon("use a.foo as Foo; record bar {}").unwrap();
21072186 ws.add_module("b".into(), b).unwrap();
2108218721882188+ // UnusedImport is now a warning, not a blocking error
21092189 let result = ws.resolve();
21102110- assert!(result.is_err());
21112111- let errors = result.unwrap_err();
21122112- assert!(errors.errors.iter().any(|e| matches!(e, ValidationError::UnusedImport { .. })));
21902190+ assert!(result.is_ok());
21132191 }
2114219221152193 #[test]
···21362214 let b = parse_lexicon("use a; record baz {}").unwrap();
21372215 ws.add_module("b".into(), b).unwrap();
2138221622172217+ // UnusedImport is now a warning, not a blocking error
21392218 let result = ws.resolve();
21402140- assert!(result.is_err());
21412141- let errors = result.unwrap_err();
21422142- // Should have 2 unused import errors (foo and bar)
21432143- let unused_count = errors.errors.iter().filter(|e| matches!(e, ValidationError::UnusedImport { .. })).count();
21442144- assert_eq!(unused_count, 2);
22192219+ assert!(result.is_ok());
22202220+ }
22212221+22222222+ #[test]
22232223+ fn test_use_implicit_main_resolution() {
22242224+ let mut ws = Workspace::new();
22252225+22262226+ // Create a namespace where the namespace suffix matches a type name
22272227+ // No @main annotation needed for implicit main resolution
22282228+ let profile_ns = parse_lexicon(r#"
22292229+ def type color = {
22302230+ red!: integer,
22312231+ green!: integer,
22322232+ blue!: integer,
22332233+ };
22342234+22352235+ record profile {
22362236+ color: color,
22372237+ }
22382238+ "#).unwrap();
22392239+ ws.add_module("place.stream.chat.profile".into(), profile_ns).unwrap();
22402240+22412241+ // Import using implicit main resolution - should only import profile, not color
22422242+ let bookmark = parse_lexicon(r#"
22432243+ use place.stream.chat.profile;
22442244+22452245+ record bookmark {
22462246+ owner!: profile,
22472247+ }
22482248+ "#).unwrap();
22492249+ ws.add_module("com.example.bookmark".into(), bookmark).unwrap();
22502250+22512251+ // Should resolve without unused import warning for color
22522252+ let result = ws.resolve();
22532253+ assert!(result.is_ok());
22542254+22552255+ // Verify that only profile was imported (implicit main resolution)
22562256+ let imports = ws.get_imports("com.example.bookmark");
22572257+ assert_eq!(imports.len(), 1);
22582258+ assert_eq!(imports[0].0, "profile");
22592259+ }
22602260+22612261+ #[test]
22622262+ fn test_use_namespace_alias() {
22632263+ let mut ws = Workspace::new();
22642264+22652265+ // Create a namespace where the suffix doesn't match a type name
22662266+ let defs = parse_lexicon(r#"
22672267+ def type foo = string;
22682268+ def type bar = integer;
22692269+ "#).unwrap();
22702270+ ws.add_module("com.example.defs".into(), defs).unwrap();
22712271+22722272+ // Using "use com.example;" creates a namespace alias "example" -> "com.example"
22732273+ // This allows referencing types via the shortened path
22742274+ let app = parse_lexicon(r#"
22752275+ use com.example;
22762276+22772277+ record thing {
22782278+ x!: example.defs.foo,
22792279+ y!: example.defs.bar,
22802280+ }
22812281+ "#).unwrap();
22822282+ ws.add_module("com.example.app".into(), app).unwrap();
22832283+22842284+ // Should resolve successfully using the namespace alias
22852285+ let result = ws.resolve();
22862286+ if let Err(ref e) = result {
22872287+ eprintln!("Errors: {:?}", e);
22882288+ }
22892289+ assert!(result.is_ok());
22902290+22912291+ // Verify that no types were imported (it's just a namespace alias)
22922292+ let imports = ws.get_imports("com.example.app");
22932293+ assert_eq!(imports.len(), 0);
22942294+ }
22952295+22962296+ #[test]
22972297+ fn test_use_namespace_alias_nested() {
22982298+ let mut ws = Workspace::new();
22992299+23002300+ // Create nested namespaces
23012301+ let actor_defs = parse_lexicon(r#"
23022302+ def type profileView = {
23032303+ did!: string,
23042304+ handle!: string,
23052305+ };
23062306+ "#).unwrap();
23072307+ ws.add_module("app.bsky.actor.defs".into(), actor_defs).unwrap();
23082308+23092309+ let feed_post = parse_lexicon(r#"
23102310+ def type post = {
23112311+ text!: string,
23122312+ };
23132313+ "#).unwrap();
23142314+ ws.add_module("app.bsky.feed.post".into(), feed_post).unwrap();
23152315+23162316+ // Use namespace alias to shorten references
23172317+ let like = parse_lexicon(r#"
23182318+ use app.bsky;
23192319+23202320+ record like {
23212321+ subject!: bsky.feed.post,
23222322+ actor!: bsky.actor.defs.profileView,
23232323+ }
23242324+ "#).unwrap();
23252325+ ws.add_module("app.bsky.feed.like".into(), like).unwrap();
23262326+23272327+ let result = ws.resolve();
23282328+ if let Err(ref e) = result {
23292329+ eprintln!("Errors: {:?}", e);
23302330+ }
23312331+ assert!(result.is_ok());
23322332+ }
23332333+23342334+ #[test]
23352335+ fn test_use_namespace_reference_full_path() {
23362336+ let mut ws = Workspace::new();
23372337+23382338+ // Create a namespace where the suffix doesn't match a type name
23392339+ let defs = parse_lexicon(r#"
23402340+ def type foo = string;
23412341+ def type bar = integer;
23422342+ "#).unwrap();
23432343+ ws.add_module("com.example.defs".into(), defs).unwrap();
23442344+23452345+ // Using "use com.example.defs;" where "defs" is not a type name
23462346+ // creates a namespace alias "defs" -> "com.example.defs"
23472347+ let app = parse_lexicon(r#"
23482348+ use com.example.defs;
23492349+23502350+ record thing {
23512351+ x!: defs.foo,
23522352+ y!: defs.bar,
23532353+ }
23542354+ "#).unwrap();
23552355+ ws.add_module("com.example.app".into(), app).unwrap();
23562356+23572357+ // Should resolve successfully using the namespace alias
23582358+ let result = ws.resolve();
23592359+ if let Err(ref e) = result {
23602360+ eprintln!("Errors: {:?}", e);
23612361+ }
23622362+ assert!(result.is_ok());
23632363+23642364+ // Verify that no types were imported (it's just a namespace alias)
23652365+ let imports = ws.get_imports("com.example.app");
23662366+ assert_eq!(imports.len(), 0);
21452367 }
21462368}